1 /* $Author: hansonr $
2 * $Date: 2021-11-28 10:58:48 -0600 (Sun, 28 Nov 2021) $
3 * $Revision: 22265 $
4 *
5 * Copyright (C) 2002-2005  The Jmol Development Team
6 *
7 * Contact: jmol-developers@lists.sf.net
8 *
9 *  This library is free software; you can redistribute it and/or
10 *  modify it under the terms of the GNU Lesser General Public
11 *  License as published by the Free Software Foundation; either
12 *  version 2.1 of the License, or (at your option) any later version.
13 *
14 *  This library is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 *  Lesser General Public License for more details.
18 *
19 *  You should have received a copy of the GNU Lesser General Public
20 *  License along with this library; if not, write to the Free Software
21 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23 
24 package org.jmol.script;
25 
26 import org.jmol.util.Escape;
27 import org.jmol.util.CommandHistory;
28 import org.jmol.util.Logger;
29 import org.jmol.viewer.FileManager;
30 import org.jmol.viewer.JC;
31 import org.jmol.viewer.Viewer;
32 import org.jmol.api.Interface;
33 import org.jmol.i18n.GT;
34 import javajs.util.BS;
35 import org.jmol.modelset.BondSet;
36 import org.jmol.modelset.Group;
37 
38 import javajs.util.AU;
39 import javajs.util.Lst;
40 import javajs.util.M34;
41 import javajs.util.SB;
42 import javajs.util.M4;
43 import javajs.util.PT;
44 
45 import java.util.Hashtable;
46 
47 import java.util.Map;
48 
49 public class ScriptCompiler extends ScriptTokenParser {
50 
51   /*
52    * The Compiler class is really two parts --
53    *
54    * Compiler.class          going from characters to tokens
55    * CompilationTokenParser  further syntax checking and modifications
56    *
57    * The data structures follow the following sequences:
58    *
59    * String script ==> Vector lltoken[][] --> Token[][] aatokenCompiled[][]
60    *
61    * A given command goes through the sequence:
62    *
63    * String characters --> Token token --> Vector ltoken[] --> Token[][] aatokenCompiled[][]
64    *
65    */
66 
67   /**
68    * @param vwr
69    *
70    */
ScriptCompiler(Viewer vwr)71   public ScriptCompiler(Viewer vwr) {
72     this.vwr = vwr;
73   }
74 
75   private String filename;
76   private boolean isSilent;
77 
78   // returns:
79 
80   private Map<String, ScriptFunction> contextFunctions;
81   private Map<String, SV> contextVariables;
82   private T[][] aatokenCompiled;
83   private short[] lineNumbers;
84   private int[][] lineIndices;
85 
86   private int lnLength = 8;
87   private boolean preDefining;
88   private boolean isShowScriptOutput;
89   private boolean isCheckOnly;
90   private boolean haveComments;
91   private boolean isPrivateFunc; // private function...
92   private boolean isPrivateScript; // "//@private"
93 
94   String scriptExtensions;
95 
96   private ScriptFunction thisFunction;
97 
98   private ScriptFlowContext flowContext;
99   private Lst<T> ltoken;
100   private Lst<T[]> lltoken;
101   private Lst<T> vBraces;
102 
103   private int ichBrace;
104   private int cchToken;
105   private int cchScript;
106 
107   private int nSemiSkip;
108   private int parenCount;
109   private int braceCount;
110   private int setBraceCount;
111   private int bracketCount;
112   private int ptSemi;
113   private int forPoint3;
114   private int setEqualPt;
115   private int iBrace;
116 
117   private boolean iHaveQuotedString;
118   private boolean isEndOfCommand;
119   private boolean needRightParen;
120   private boolean endOfLine;
121 
122   private String comment;
123 
124   private final static int OK = 0;
125   private final static int OK2 = 1;
126   private final static int CONTINUE = 2;
127   private final static int EOL = 3;
128   private final static int ERROR = 4;
129   private final static int RESTART = 5;
130 
131   private int tokLastMath;
132   private boolean checkImpliedScriptCmd;
133 
134   private Lst<ScriptFunction> vFunctionStack;
135   private boolean allowMissingEnd;
136 
137   private boolean isShowCommand;
138   private boolean isComment;
139   private boolean isUserToken;
140   private boolean implicitString;
141 
142   private int tokInitialPlusPlus;
143   private int afterWhite;
144   private boolean isDotDot;
145   private String ident, identLC;
146   private Lst<T> vPush = new Lst<T>();
147   private int pushCount;
148   private ScriptFlowContext forceFlowContext;
149 
compile(String filename, String script, boolean isPredefining, boolean isSilent, boolean debugScript, boolean isCheckOnly)150   synchronized ScriptContext compile(String filename, String script,
151                                      boolean isPredefining, boolean isSilent,
152                                      boolean debugScript, boolean isCheckOnly) {
153     this.isCheckOnly = isCheckOnly;
154     this.filename = filename;
155     this.isSilent = isSilent;
156     this.script = script;
157     logMessages = (!isSilent && !isPredefining && debugScript);
158     preDefining = (filename == "#predefine");
159     boolean doFull = true;
160     boolean isOK = compile0(doFull);
161     atokenInfix = null;
162     if (!isOK)
163       handleError();
164     ScriptContext sc = new ScriptContext();
165     isOK = (iBrace == 0 && parenCount == 0 && braceCount == 0 && bracketCount == 0);
166     sc.isComplete = isOK;
167     sc.script = script;
168     sc.scriptExtensions = scriptExtensions;
169     sc.errorType = errorType;
170     if (errorType != null) {
171       sc.iCommandError = iCommand;
172       setAaTokenCompiled();
173     }
174     sc.saveTokens(aatokenCompiled);
175     sc.errorMessage = errorMessage;
176     sc.errorMessageUntranslated = (errorMessageUntranslated == null ? errorMessage
177         : errorMessageUntranslated);
178     if (allowMissingEnd && sc.errorMessage != null
179         && sc.errorMessageUntranslated.indexOf("missing END") >= 0)
180       sc.errorMessage = sc.errorMessageUntranslated;
181     sc.lineIndices = lineIndices;
182     sc.lineNumbers = lineNumbers;
183     sc.vars = contextVariables;
184     return sc;
185   }
186 
newContextVariable(String ident)187   private void newContextVariable(String ident) {
188     theToken = T.o(T.identifier, ident);
189     if (pushCount > 0) {
190       ContextToken ct = (ContextToken) vPush.get(pushCount - 1);
191       ct.addName(ident);
192       if (ct.tok != T.trycmd)
193         return;
194     }
195     if (thisFunction == null) {
196       if (contextVariables == null)
197         contextVariables = new Hashtable<String, SV>();
198       addContextVariable(contextVariables, ident);
199     } else {
200       thisFunction.addVariable(ident, false);
201     }
202   }
203 
addContextVariable(Map<String, SV> contextVariables, String name)204   static void addContextVariable(Map<String, SV> contextVariables, String name) {
205     contextVariables.put(name, SV.newS("").setName(name));
206   }
207 
isContextVariable(String ident)208   private boolean isContextVariable(String ident) {
209     for (int i = vPush.size(); --i >= 0;) {
210       ContextToken ct = (ContextToken) vPush.get(i);
211       if (ct.contextVariables != null && ct.contextVariables.containsKey(ident))
212         return true;
213     }
214     return (thisFunction != null ? thisFunction.isVariable(ident)
215         : contextVariables != null && contextVariables.containsKey(ident));
216   }
217 
218   /**
219    * allows for three kinds of comments. NOTE: closing involves asterisks and
220    * slash together, but that can't be shown here.
221    *
222    * 1) /** .... ** / super-comment 2) /* ..... * / may be INSIDE /**....** /).
223    * 3) \n//.....\n single-line comments -- like #, but removed entirely The
224    * reason is that /* ... * / will appear as standard in MOVETO command but we
225    * still might want to escape it, so around that you can have /** .... ** /
226    *
227    * The terminator is not necessary -- so you can quickly escape anything in a
228    * file after /** or /*
229    *
230    * In addition, we can have [/*|/**] .... **** Jmol Embedded Script ****
231    * [script commands] [** /|* /] Then ONLY that script is taken. This is a
232    * powerful and simple way then to include Jmol scripting in any file --
233    * including, for example, HTML as an HTML comment. Just send the whole file
234    * to Jmol, and it will find its script!
235    *
236    * @param script
237    * @return cleaned script
238    */
cleanScriptComments(String script)239   private String cleanScriptComments(String script) {
240     if (script.indexOf('\u00A0') >= 0)
241       script = script.replace('\u00A0', ' ');
242     if (script.indexOf('\u201C') >= 0)
243       script = script.replace('\u201C', '"');
244     if (script.indexOf('\u201D') >= 0)
245       script = script.replace('\u201D', '"');
246     if (script.indexOf('\uFEFF') >= 0)
247       script = script.replace('\uFEFF', ' ');
248     int pt = (script.indexOf("\1##"));
249     if (pt >= 0) {
250       // these are for jmolConsole and scriptEditor
251       scriptExtensions = script.substring(pt + 1);
252       script = script.substring(0, pt);
253       allowMissingEnd = (scriptExtensions.indexOf("##noendcheck") >= 0); // when typing
254     }
255     haveComments = (script.indexOf("#") >= 0); // speeds processing
256     return FileManager.getEmbeddedScript(script);
257   }
258 
addTokenToPrefix(T token)259   private void addTokenToPrefix(T token) {
260     if (logMessages)
261       Logger.info("addTokenToPrefix" + lineCurrent + " " + iCommand + " " + token);
262     ltoken.addLast(token);
263     if (token.tok != T.nada)
264       lastToken = token;
265   }
266 
267   private boolean haveENDIF;
268 
compile0(boolean isFull)269   private boolean compile0(boolean isFull) {
270     haveENDIF = false;
271     script = cleanScriptComments(script);
272     ichToken = script.indexOf(JC.STATE_VERSION_STAMP);
273     isStateScript = (ichToken >= 0);
274     if (isStateScript) {
275       ptSemi = script.indexOf(";", ichToken);
276       if (ptSemi >= ichToken)
277         ScriptManager.setStateScriptVersion(vwr,
278             script
279                 .substring(ichToken + JC.STATE_VERSION_STAMP.length(), ptSemi)
280                 .trim());
281     }
282     cchScript = script.length();
283 
284     main: while (true) {
285       vFunctionStack = new Lst<ScriptFunction>();
286       htUserFunctions = new Hashtable<String, Boolean>();
287       // these four will be returned:
288       contextVariables = null;
289       lineNumbers = null;
290       lineIndices = null;
291       aatokenCompiled = null;
292       thisFunction = null;
293       flowContext = null;
294       errorType = null;
295       errorMessage = null;
296       errorMessageUntranslated = null;
297       errorLine = null;
298       isPrivateScript = false;
299       isPrivateFunc = false;
300       nSemiSkip = 0;
301       ichToken = 0;
302       ichCurrentCommand = 0;
303       ichComment = 0;
304       ichBrace = 0;
305       lineCurrent = 1;
306       iCommand = 0;
307       tokLastMath = 0;
308       lastToken = T.tokenOff;
309       vBraces = new Lst<T>();
310       vPush = new Lst<T>();
311       pushCount = 0;
312       iBrace = 0;
313       braceCount = 0;
314       parenCount = 0;
315       isDotDot = false;
316       ptSemi = -10;
317       cchToken = 0;
318       lnLength = 8;
319       lineNumbers = new short[lnLength];
320       lineIndices = new int[lnLength][2];
321       isNewSet = isSetBrace = false;
322       ptNewSetModifier = 1;
323       isShowScriptOutput = false;
324       iHaveQuotedString = false;
325       checkImpliedScriptCmd = false;
326       lltoken = new Lst<T[]>();
327       ltoken = new Lst<T>();
328       tokCommand = T.nada;
329       lastFlowCommand = null;
330       tokenAndEquals = null;
331       tokInitialPlusPlus = T.nada;
332       setBraceCount = 0;
333       bracketCount = 0;
334       forPoint3 = -1;
335       setEqualPt = Integer.MAX_VALUE;
336       endOfLine = false;
337       comment = null;
338       isEndOfCommand = false;
339       needRightParen = false;
340       lastFlowCommand = null;
341       forceFlowContext = null;
342 
343       theTok = T.nada;
344       short iLine = 1;
345 
346       for (; true; ichToken += cchToken) {
347         if ((nTokens = ltoken.size()) == 0) {
348           if (thisFunction != null && thisFunction.chpt0 == 0)
349             thisFunction.chpt0 = ichToken;
350           ichCurrentCommand = ichToken;
351           iLine = lineCurrent;
352         }
353         if (lookingAtLeadingWhitespace())
354           continue;
355         endOfLine = false;
356         if (!isEndOfCommand) {
357           endOfLine = lookingAtEndOfLine();
358           switch (endOfLine ? OK : lookingAtComment()) {
359           case CONTINUE: //  short /*...*/ or comment to completely ignore
360             continue;
361           case EOL: // /* .... \n ... */ -- flag as end of line but ignore
362             isEndOfCommand = true;
363             continue;
364           case OK2: // really just line-ending comment -- mark it for later inclusion
365             isEndOfCommand = true;
366             // start-of line comment -- include as Token.nada
367             comment = script.substring(ichToken, ichToken + cchToken).trim();
368             break;
369           }
370           isEndOfCommand = isEndOfCommand || endOfLine || lookingAtTerminator();
371         }
372 
373         if (isEndOfCommand) {
374           isEndOfCommand = false;
375           switch (processTokenList(iLine, isFull)) {
376           case CONTINUE:
377             continue;
378           case ERROR:
379             return false;
380           }
381           checkImpliedScriptCmd = false;
382           if (ichToken < cchScript)
383             continue;
384           if (flowContext != null) {
385             ichCurrentCommand = ichToken = cchScript;
386             while (flowContext != null) {
387               fixFlowAddLine(flowContext);
388               if (!haveENDIF && flowContext.checkForceEndIf(0)) {
389                 forceFlowEnd(flowContext.token);
390                 processTokenList(iLine, isFull);
391               } else {
392                 lineCurrent = (short) flowContext.lineStart;
393                 iCommand = flowContext.pt0;
394                 ichCurrentCommand = lineIndices[iCommand][0];
395                 ichToken = ichEnd = lineIndices[iCommand][1];
396                 return errorStr(
397                     ERROR_missingEnd,
398                     (flowContext.function == null ? T
399                         .nameOf(flowContext.token.tok) : flowContext.function
400                         .getSignature()));
401               }
402             }
403             lltoken.addLast(new T[] { T.o(T.nada, "// end of script") });
404           }
405           setAaTokenCompiled();
406           return true;
407         }
408 
409         if (nTokens > 0 && !isDotDot) {
410           switch (checkSpecialParameterSyntax()) {
411           case CONTINUE:
412             continue;
413           case ERROR:
414             return false;
415           }
416         }
417         if (lookingAtLookupToken(ichToken)) {
418           switch (parseKnownToken()) {
419           case CONTINUE:
420             continue;
421           case ERROR:
422             return false;
423           case RESTART:
424             haveENDIF = true;
425             continue main;
426           }
427           switch (parseCommandParameter(iLine, isFull)) {
428           case CONTINUE:
429             continue;
430           case ERROR:
431             return false;
432           case RESTART:
433             haveENDIF = true;
434             continue main;
435           }
436           addTokenToPrefix(theToken);
437           continue;
438         }
439         if (nTokens == 0 || (isNewSet || isSetBrace)
440             && nTokens == ptNewSetModifier) {
441           if (nTokens == 0) {
442             if (lookingAtString(true)) {
443               addTokenToPrefix(setCommand(T.tokenScript));
444               cchToken = 0;
445               continue;
446             }
447             if (lookingAtImpliedString(true, true, true))
448               ichEnd = ichToken + cchToken;
449           }
450           return commandExpected();
451         }
452         return errorStr(ERROR_unrecognizedToken,
453             script.substring(ichToken, ichToken + 1));
454       }
455     }
456   }
457 
setAaTokenCompiled()458   private void setAaTokenCompiled() {
459     aatokenCompiled = lltoken.toArray(new T[lltoken.size()][]);
460   }
461 
lookingAtLeadingWhitespace()462   private boolean lookingAtLeadingWhitespace() {
463     int ichT = ichToken;
464     while (isSpaceOrTab(charAt(ichT)))
465       ++ichT;
466     if (isLineContinuation(ichT, true))
467       ichT += 1 + nCharNewLine(ichT + 1);
468     cchToken = ichT - ichToken;
469     if (cchToken == 0)
470       return false;
471     afterWhite = ichT;
472     return true;
473   }
474 
isLineContinuation(int ichT, boolean checkMathop)475   private boolean isLineContinuation(int ichT, boolean checkMathop) {
476     boolean isEscaped = (ichT + 2 < cchScript && script.charAt(ichT) == '\\'
477         && nCharNewLine(ichT + 1) > 0 || !isShowScriptOutput && checkMathop
478         && lookingAtMathContinuation(ichT));
479     if (isEscaped)
480       lineCurrent++;
481     return isEscaped;
482   }
483 
lookingAtMathContinuation(int ichT)484   private boolean lookingAtMathContinuation(int ichT) {
485     int n;
486     if ((n = nCharNewLine(ichT)) == 0 || lastToken.tok == T.leftbrace)
487       return false;
488     if (parenCount > 0 || bracketCount > 0)
489       return true;
490     switch (tokCommand) {
491     case T.function:
492     case T.parallel:
493       flowContext.forceEndIf = false;
494       return false;
495     case T.forcmd:
496     case T.whilecmd:
497     case T.ifcmd:
498       //$FALL-THROUGH$
499     case T.elsecmd:
500     case T.elseif:
501       if (!haveENDIF) {
502       // end of a line, after (...), no {
503         flowContext.addLine = 1;
504         flowContext.forceEndIf = true;
505       }
506       return false;
507     case T.set:
508       if (nTokens > 1 && ltoken.get(1).tok == T.echo)
509         return false;
510       //$FALL-THROUGH$
511     case T.print:
512     case T.log:
513       break;
514     default:
515       return false;
516     }
517     if (lastToken.tok == tokLastMath)
518       return true;
519     ichT += n;
520     while (isSpaceOrTab(charAt(ichT)))
521       ++ichT;
522     return (lookingAtLookupToken(ichT) && tokLastMath == 1);
523   }
524 
525   /**
526    * Look for end of script or a new line. Set ichEnd to this point or end of
527    * string; if found, set cchToken to the number of eol characters;
528    *
529    * @return true if eol
530    */
lookingAtEndOfLine()531   private boolean lookingAtEndOfLine() {
532     if (ichToken >= cchScript) {
533       ichEnd = cchScript;
534       return true;
535     }
536     return ((cchToken = nCharNewLine(ichEnd = ichToken)) > 0);
537   }
538 
539   /**
540    * Check for line ending at this point in script.
541    *
542    * @param ichT
543    * @return 1 if \n or \r, 2 if \r\n, or 0 otherwise (including end of script)
544    */
nCharNewLine(int ichT)545   private int nCharNewLine(int ichT) {
546     char ch;
547     return ((ch = charAt(ichT)) != '\r' ? (ch == '\n' ? 1 : 0)
548         : charAt(++ichT) == '\n' ? 2 : 1);
549   }
550 
551   /**
552    * Look for valid terminating semicolon -- one not within for(), for example.
553    *
554    * @return true if valid semi
555    */
lookingAtTerminator()556   private boolean lookingAtTerminator() {
557     boolean isSemi = (script.charAt(ichToken) == ';');
558     if (isSemi && nTokens > 0)
559       ptSemi = nTokens;
560     if (!isSemi || nSemiSkip-- > 0)
561       return false;
562     cchToken = 1;
563     return true;
564   }
565 
lookingAtComment()566   private int lookingAtComment() {
567     char ch = script.charAt(ichToken);
568     int ichT = ichToken;
569     int ichFirstSharp = -1;
570 
571     // return CONTINUE: totally ignore
572     // return EOL: treat as line end, even though it isn't
573     // return OK: no comment here
574 
575     /*
576      * New in Jmol 11.1.9: we allow for output from the set showScript command
577      * to be used as input. These lines start with $ and have a [...] phrase
578      * after them. Their presence switches us to this new mode where we use
579      * those statements as our commands and any line WITHOUT those as comments.
580      *
581      * adjusted to only do this if $ starts the FIRST line of a script
582      * and to accept standard $ ... entries clipped from the console  Jmol 13.3.6
583      *
584      */
585     if (ichToken == ichCurrentCommand && ch == '$'
586         && (isShowScriptOutput || ichToken == 0)) {
587       isShowScriptOutput = true;
588       isShowCommand = true;
589       if (charAt(++ichT) == '[')
590         while (ch != ']' && !eol(ch = charAt(ichT)))
591           ++ichT;
592       cchToken = ichT - ichToken;
593       return CONTINUE;
594     } else if (isShowScriptOutput && !isShowCommand) {
595       ichFirstSharp = ichT;
596     }
597     if (ch == '/' && ichT + 1 < cchScript)
598       switch (script.charAt(++ichT)) {
599       case '/':
600         ichFirstSharp = ichToken;
601         ichEnd = ichT - 1;
602         break;
603       case '*':
604         ichEnd = ichT - 1;
605         String terminator = ((ch = charAt(++ichT)) == '*' ? "**/" : "*/");
606         ichT = script.indexOf(terminator, ichToken + 2);
607         if (ichT < 0) {
608           ichToken = cchScript;
609           return EOL;
610         }
611         // ichT points at char after /*, whatever that is. So even /***/ will be caught
612         incrementLineCount(script.substring(ichToken, ichT));
613         cchToken = ichT + (ch == '*' ? 3 : 2) - ichToken;
614         return CONTINUE;
615       default:
616         return OK;
617       }
618 
619     boolean isSharp = (ichFirstSharp < 0);
620     if (isSharp && !haveComments)
621       return OK;
622 
623     // old way:
624     // first, find the end of the statement and scan for # (sharp) signs
625 
626     if (ichComment > ichT)
627       ichT = ichComment;
628     for (; ichT < cchScript; ichT++) {
629       if (eol(ch = script.charAt(ichT))) {
630         ichEnd = ichT;
631         if (ichT > 0 && isLineContinuation(ichT - 1, false)) {
632           ichT += nCharNewLine(ichT);
633           continue;
634         }
635         if (!isSharp && ch == ';')
636           continue;
637         break;
638       }
639       if (ichFirstSharp >= 0)
640         continue;
641       if (ch == '#')
642         ichFirstSharp = ichT;
643     }
644     if (ichFirstSharp < 0) // there were no sharps found
645       return OK;
646     ichComment = ichFirstSharp;
647     /****************************************************************
648      * check for #jc comment if it occurs anywhere in the statement, then the
649      * statement is not executed. This allows statements which are executed in
650      * RasMol but are comments in Jmol
651      ****************************************************************/
652 
653     if (isSharp && nTokens == 0 && cchScript - ichFirstSharp >= 3
654         && script.charAt(ichFirstSharp + 1) == 'j'
655         && script.charAt(ichFirstSharp + 2) == 'c') {
656       // statement contains a #jc before then end ... strip it all
657       cchToken = ichT - ichToken;
658       return CONTINUE;
659     }
660 
661     // if the sharp was not the first character then it isn't a comment
662     if (ichFirstSharp != ichToken)
663       return OK;
664 
665     /****************************************************************
666      * check for leading #jx <space> or <tab> if you see it, then only strip
667      * those 4 characters. if they put in #jx <newline> then they are not going
668      * to execute anything, and the regular code will take care of it
669      ****************************************************************/
670     if (isSharp && cchScript > ichToken + 3
671         && script.charAt(ichToken + 1) == 'j'
672         && script.charAt(ichToken + 2) == 'x'
673         && isSpaceOrTab(script.charAt(ichToken + 3))) {
674       cchToken = 4; // #jx[\s\t]
675       return CONTINUE;
676     }
677 
678     if (ichT == ichToken)
679       return OK;
680 
681     // first character was a sharp, but was not #jx ... strip it all
682     cchToken = ichT - ichToken;
683     if (!isSharp && cchToken == 10
684     		&& script.substring(ichToken, ichT).equals("//@private")) {
685    	  isPrivateScript = true;
686     }
687     return (nTokens == 0 ? OK2 : CONTINUE);
688   }
689 
charAt(int i)690   private char charAt(int i) {
691     return (i < cchScript ? script.charAt(i) : '\0');
692   }
693 
processTokenList(short iLine, boolean doCompile)694   private int processTokenList(short iLine, boolean doCompile) {
695     int n = ltoken.size();
696     if (n > 0 || comment != null) {
697       if (n == 0) {
698         // just a comment
699         ichCurrentCommand = ichToken;
700         if (comment != null) {
701           isComment = true;
702           addTokenToPrefix(T.o(T.nada, comment));
703         }
704       } else if (setBraceCount > 0 && endOfLine && ichToken < cchScript) {
705         return CONTINUE;
706       }
707       if (wasImpliedScript())
708         return CONTINUE;
709       if (isNewSet
710           && n > 2
711           && tokAt(2) == T.per
712           && (tokAt(3) == T.sort || tokAt(3) == T.reverse || tokAt(3) == T.push || tokAt(3) == T.pop)) {
713         // check for x.sort or x.reverse or a.push(xxx)
714         // x.sort / x.reverse ==> x = x.sort / x = x.reverse
715         ltoken.set(0, T.tokenSet);
716         ltoken.add(1, tokAt(3) == T.pop ? T.tokenAll : ltoken.get(1));
717       } else if (tokInitialPlusPlus != T.nada) {
718         // check for ++x or --x
719         if (!isNewSet)
720           checkNewSetCommand();
721         tokenizePlusPlus(tokInitialPlusPlus, true);
722         ichCurrentCommand -= 2;
723       }
724       iCommand = lltoken.size();
725       if (thisFunction != null && thisFunction.cmdpt0 < 0) {
726         thisFunction.cmdpt0 = iCommand;
727       }
728       if (n == 1 && braceCount == 1) {
729         // ...{...
730         if (lastFlowCommand == null) {
731           parenCount = setBraceCount = braceCount = 0;
732           ltoken.removeItemAt(0);
733           T t = ContextToken.newContext(true);
734           addTokenToPrefix(setCommand(t));
735           pushContext(t);
736           addBrace(tokenCommand);
737         } else {
738           parenCount = setBraceCount = 0;
739           setCommand(lastFlowCommand);
740           if (lastFlowCommand.tok != T.process && (tokAt(0) == T.leftbrace))
741             ltoken.removeItemAt(0);
742           lastFlowCommand = null;
743           forceFlowContext = flowContext;
744 //          lastFlowImplicitEnd = flowContext.nextFlowImplicitEnd;
745         }
746       }
747       if (bracketCount > 0 || setBraceCount > 0 || parenCount > 0
748           || braceCount == 1 && !checkFlowStartBrace(true)) {
749         error(n == 1 ? ERROR_commandExpected : ERROR_endOfCommandUnexpected);
750         return ERROR;
751       }
752       if (needRightParen) {
753         addTokenToPrefix(T.tokenRightParen);
754         needRightParen = false;
755       }
756 
757       if (tokAt(1) == T.id && T.tokAttr(tokCommand, T.shapeCommand)) {
758         // ensure isosurface ID xxxx that xxxx is a string
759         switch (tokAt(2)) {
760         case T.nada:
761         case T.string:
762         case T.define:
763           break;
764         default:
765           T t = ltoken.removeItemAt(2);
766           ltoken.add(
767               2,
768               T.o(T.string,
769                   t.tok == T.integer ? "" + t.intValue : t.value.toString()));
770         }
771       }
772 
773       if (ltoken.size() > 0) {
774         if (doCompile && !compileCommand())
775           return ERROR;
776         if (logMessages) {
777           Logger.info("-------------------------------------");
778         }
779         boolean doEval = true;
780         switch (tokCommand) {
781         case T.trycmd:
782         case T.parallel:
783         case T.function: // formerly "noeval"
784         case T.end:
785           // end switch may have - or + intValue, depending upon default or not
786           // end function and the function call itself has intValue 0,
787           // but the FUNCTION declaration itself will have MAX_VALUE intValue
788           doEval = (atokenInfix.length > 0 && atokenInfix[0].intValue != Integer.MAX_VALUE);
789           break;
790         }
791         if (doEval) {
792           if (iCommand == lnLength) {
793             lineNumbers = AU.doubleLengthShort(lineNumbers);
794             int[][] lnI = new int[lnLength * 2][2];
795             System.arraycopy(lineIndices, 0, lnI, 0, lnLength);
796             lineIndices = lnI;
797             lnLength *= 2;
798           }
799           lineNumbers[iCommand] = lineNumbers[lineNumbers.length - 1] = iLine;
800           lineIndices[iCommand][0] = ichCurrentCommand;
801           lineIndices[iCommand][1] = Math.max(ichCurrentCommand, Math.min(
802               cchScript, ichEnd == ichCurrentCommand ? ichToken : ichEnd));
803           lltoken.addLast(atokenInfix);
804           iCommand = lltoken.size();
805         }
806         if (tokCommand == T.set)
807           lastFlowCommand = null;
808       }
809       setCommand(null);
810       comment = null;
811       iHaveQuotedString = isNewSet = isSetBrace = needRightParen = false;
812       ptNewSetModifier = 1;
813       ltoken.clear();
814       nTokens = nSemiSkip = 0;
815       tokInitialPlusPlus = T.nada;
816       tokenAndEquals = null;
817       ptSemi = -10;
818       forPoint3 = -1;
819       setEqualPt = Integer.MAX_VALUE;
820 
821     }
822     boolean isOneLine = (flowContext != null && flowContext.addLine == 0); // if (....) xxxxx;
823     boolean isEndFlow = ((endOfLine || !isOneLine) && !haveENDIF
824           && flowContext != null
825           && flowContext.checkForceEndIf(-1));
826     if (endOfLine) {
827       if (isEndFlow) {
828         if (isComment) {
829           if (!isOneLine) {
830             flowContext.addLine++;
831             flowContext.forceEndIf = true;
832           }
833         } else if (n > 0 && !haveENDIF || isOneLine) {
834           // looking for
835           // for (...)
836           //  print ...
837           //
838           // but not empty line after for:
839           //
840           // for (...)
841           //
842           //  print ...
843           //
844 
845           forceFlowEnd(flowContext.token);
846           if (!isOneLine) {
847             forceFlowContext.forceEndIf = true;
848           }
849         }
850         isEndOfCommand = true;
851         cchToken = 0;
852         ichCurrentCommand = ichToken;
853         return CONTINUE;
854       }
855       isComment = false;
856       isShowCommand = false;
857       ++lineCurrent;
858     } else if (isEndFlow) {
859       // looking at something like
860       //
861       // for (...)
862       //  print ... ;
863       //
864       forceFlowEnd(flowContext.token);
865       forceFlowContext.forceEndIf = true;
866     }
867     if (ichToken >= cchScript) {
868       // check for end of all brace work
869       setCommand(T.tokenAll);
870       theTok = T.nada;
871       switch (checkFlowEndBrace()) {
872       case ERROR:
873         return ERROR;
874       case CONTINUE:
875         isEndOfCommand = true;
876         cchToken = 0;
877         return CONTINUE;
878       }
879       ichToken = cchScript;
880       return OK; // main loop exit
881     }
882     return OK;
883   }
884 
885   /**
886    * @param t could be { or } or a command such as FOR or WHILE
887    */
addBrace(T t)888   private void addBrace(T t) {
889     vBraces.addLast(t);
890     iBrace++;
891   }
892 
pushContext(T t)893   private void pushContext(T t) {
894     pushCount++;
895     vPush.addLast(t);
896   }
897 
898   /**
899    * Check for improperly parsed implied script command:
900    *
901    * only two tokens:
902    *
903    * [implied script] xxx.SORT/REVERSE/PUSH/POP
904    *
905    * more than two tokens:
906    *
907    * xxxx.spt(3,4,5)
908    *
909    * @return true if found
910    */
wasImpliedScript()911   private boolean wasImpliedScript() {
912     if (checkImpliedScriptCmd && nTokens >= 2 && (tokCommand == T.script || tokCommand == T.macro)) {
913       String s = (nTokens == 2 ? lastToken.value.toString().toUpperCase()
914           : null);
915       if (nTokens > 2 ? !(tokAt(2) == T.leftparen && ltoken.get(1).value.toString().endsWith(".spt"))
916           : s.endsWith(".SORT")
917           || s.endsWith(".REVERSE") || s.endsWith(".POP")
918           || s.indexOf(".SORT(") >= 0 || s.indexOf(".REVERSE(") >= 0
919           || s.indexOf(".POP(") >= 0 || s.indexOf(".PUSH(") >= 0
920           || s.endsWith("++") || s.endsWith("--") || s.endsWith("=")
921           || tokInitialPlusPlus != T.nada) {
922         ichToken = ichCurrentCommand;
923         nTokens = 0;
924         ltoken.clear();
925         cchToken = 0;
926         tokCommand = T.nada;
927         return true;
928       }
929     }
930     return false;
931   }
932 
compileCommand()933   private boolean compileCommand() {
934     switch (ltoken.size()) {
935     case 0:
936       // comment
937       atokenInfix = new T[0];
938       return true;
939     case 4:
940       // check for a command name in name.spt
941       if (isNewSet && tokenAt(2).value.equals(".")
942           && tokenAt(3).value.equals("spt")) {
943         String fname = tokenAt(1).value + "." + tokenAt(3).value;
944         ltoken.clear();
945         addTokenToPrefix(T.tokenScript);
946         addTokenToPrefix(T.o(T.string, fname));
947         isNewSet = false;
948       }
949     }
950     setCommand(tokenAt(0));
951     int size = ltoken.size();
952     if (size == 1 && T.tokAttr(tokCommand, T.defaultON))
953       addTokenToPrefix(T.tokenOn);
954     if (tokenAndEquals != null) {
955       int j;
956       int i = 0;
957       for (i = 1; i < size; i++) {
958         if ((j = tokAt(i)) == T.andequals)
959           break;
960       }
961       size = i;
962       i++;
963       if (ltoken.size() < i) {
964         Logger.error("COMPILER ERROR! - andEquals ");
965       } else {
966         for (j = 1; j < size; j++, i++)
967           ltoken.add(i, tokenAt(j));
968         ltoken.set(size, T.tokenEquals);
969         ltoken.add(i, tokenAndEquals);
970         ltoken.add(++i, T.tokenLeftParen);
971         addTokenToPrefix(T.tokenRightParen);
972       }
973     }
974 
975     atokenInfix = ltoken.toArray(new T[size = ltoken.size()]);
976 
977 //    if (logMessages) {
978 //      Logger.debug("token list:");
979 //      for (int i = 0; i < atokenInfix.length; i++)
980 //        Logger.debug(i + ": " + atokenInfix[i]);
981 //      Logger.debug("vBraces list:");
982 //      for (int i = 0; i < vBraces.size(); i++)
983 //        Logger.debug(i + ": " + vBraces.get(i));
984 //      Logger.debug("-------------------------------------");
985 //    }
986 
987     // compile expressions  (ScriptCompilerTokenParser.java)
988     return compileExpressions();
989 
990   }
991 
tokenAt(int i)992   private T tokenAt(int i) {
993     return ltoken.get(i);
994   }
995 
996   @Override
tokAt(int i)997   protected int tokAt(int i) {
998     return (i < ltoken.size() ? tokenAt(i).tok : T.nada);
999   }
1000 
setCommand(T token)1001   private T setCommand(T token) {
1002     tokenCommand = token;
1003     if (token == null) {
1004       tokCommand = T.nada;
1005     } else {
1006       tokCommand = tokenCommand.tok;
1007       isMathExpressionCommand = (tokCommand == T.identifier || T.tokAttr(
1008           tokCommand, T.mathExpressionCommand));
1009       isSetOrDefine = (tokCommand == T.set || tokCommand == T.define);
1010       isCommaAsOrAllowed = T.tokAttr(tokCommand, T.atomExpressionCommand);
1011       implicitString = T.tokAttr(tokCommand, T.implicitStringCommand);
1012     }
1013     return token;
1014   }
1015 
replaceCommand(T token)1016   private void replaceCommand(T token) {
1017     ltoken.removeItemAt(0);
1018     ltoken.add(0, setCommand(token));
1019   }
1020 
getPrefixToken()1021   private int getPrefixToken() {
1022     ident = script.substring(ichToken, ichToken + cchToken);
1023     identLC = ident.toLowerCase();
1024     boolean isUserVar = lastToken.tok != T.per && !isDotDot
1025         && isContextVariable(identLC);
1026     String myName = ident;//(isUserVar ? ident : null);
1027     String preserveCase = null;
1028     if (nTokens == 0) {
1029       isUserToken = isUserVar;
1030     }
1031     if (nTokens == 1
1032         && (tokCommand == T.function || tokCommand == T.parallel || tokCommand == T.var)
1033 
1034         || nTokens != 0 && isUserVar
1035 
1036         || !isDotDot && isUserFunction(identLC)
1037         && ((preserveCase = ident) != null)
1038 
1039         && (thisFunction == null || !thisFunction.name.equals(identLC))) {
1040       // we need to allow:
1041 
1042       // var color = "xxx"
1043       // color @color
1044       // print color
1045       // etc.
1046       // BUT we also should allow
1047       // color = ...
1048       // color += ...
1049       // color[ ...
1050 
1051       ident = (preserveCase == null ? identLC : preserveCase);
1052       theToken = null;
1053     } else if (ident.length() == 1 || lastToken.tok == T.colon) {
1054       // hack to support case sensitive alternate locations and chains
1055       // but now with reading of multicharacter chains, how does that
1056       // work?
1057       // if an identifier is a single character long, then
1058       // allocate a new Token with the original character preserved
1059       if ((theToken = T.getTokenFromName(ident)) == null
1060           && (theToken = T.getTokenFromName(identLC)) != null)
1061         theToken = T.tv(theToken.tok, theToken.intValue, ident);
1062     } else {
1063       // 5/6/2017 The problem here was that the variable "fix" is
1064       // changed to "fixed" by getTokenFromName.
1065       theToken = T.getTokenFromName(identLC);
1066       if (isUserVar && theToken != null && !theToken.value.toString().equalsIgnoreCase(identLC)) {
1067         theToken = null;
1068       }
1069       if (theToken != null)
1070         switch (lastToken.tok) {
1071         case T.per:
1072         case T.leftsquare:
1073         case T.comma:
1074           // looking out for hash names
1075           theToken = T.o(theToken.tok, ident);
1076         }
1077     }
1078 
1079     if (theToken == null) {
1080       // myName is just in case this is being used as a key to a hash
1081       theToken = SV.newSV(
1082           (identLC.indexOf("property_") == 0 ? T.property : T.identifier),
1083           Integer.MAX_VALUE, ident).setName(myName);
1084     }
1085     return theTok = theToken.tok;
1086   }
1087 
1088   /**
1089    *
1090    * Check for special parameters, including:
1091    *
1092    * +, -, \, *, /, &, |, =, period, or [, single or double quote,
1093    * command-specific parameters, $.... identifiers, exponential notation,
1094    * decimal numbers, sequence codes, integers, bitsets ({....}) or [{....}], or
1095    * matrices
1096    *
1097    * @return OK, CONTINUE, or ERROR
1098    *
1099    */
checkSpecialParameterSyntax()1100   private int checkSpecialParameterSyntax() {
1101     if (lookingAtString(!implicitString)) {
1102       if (cchToken < 0)
1103         return ERROR(ERROR_endOfCommandUnexpected);
1104       String str = getUnescapedStringLiteral(
1105               lastToken != null && !iHaveQuotedString && lastToken.tok != T.inline
1106                   && (tokCommand == T.set && nTokens == 2
1107                       && lastToken.tok == T.defaultdirectory || tokCommand == T.load
1108                       || tokCommand == T.background || tokCommand == T.script
1109                       || tokCommand == T.macro));
1110       iHaveQuotedString = true;
1111       if ((tokCommand == T.load || tokCommand == T.cgo)
1112           && lastToken.tok == T.data
1113           || tokCommand == T.data && str.indexOf("@") < 0) {
1114         if (!getData(str)) {
1115           return ERROR(ERROR_missingEnd, "data");
1116         }
1117       } else {
1118         addTokenToPrefix(T.o(T.string, str));
1119         if (implicitString) {
1120           ichEnd = ichToken + cchToken;
1121           isEndOfCommand = true;
1122         }
1123       }
1124       return CONTINUE;
1125     }
1126     char ch;
1127     if (nTokens == ptNewSetModifier) {
1128       ch = script.charAt(ichToken);
1129       boolean isAndEquals = ("+-\\*/&|=".indexOf(ch) >= 0);
1130       boolean isOperation = (isAndEquals || ch == '.' || ch == '[');
1131       char ch2 = charAt(ichToken + 1);
1132       if (!isNewSet && isUserToken && isOperation
1133           && (ch == '=' || ch2 == ch || ch2 == '=')) {
1134         isNewSet = true;
1135         // Var data = ""
1136         // data = 5
1137         // data++
1138         // data += what
1139 
1140       }
1141       if (isNewSet || tokCommand == T.set
1142           || T.tokAttr(tokCommand, T.setparam)) {
1143         if (ch == '=')
1144           setEqualPt = ichToken;
1145 
1146         // axes, background, define, display, echo, frank, hbond, history,
1147         // set, var
1148         // can all appear with or without "set" in front of them. These
1149         // are then
1150         // both commands and parameters for the SET command, but only if
1151         // they are
1152         // the FIRST parameter of the set command.
1153         if (T.tokAttr(tokCommand, T.setparam) && ch == '='
1154             || (isNewSet || isSetBrace) && isOperation) {
1155           setCommand(isAndEquals ? T.tokenSet
1156               : ch == '[' && !isSetBrace || ch == '.' && ch2 == '.'
1157                   ? T.tokenSetArray
1158                   : T.tokenSetProperty);
1159           ltoken.add(0, tokenCommand);
1160           cchToken = 1;
1161           switch (ch) {
1162           case '[':
1163             tokLastMath = 1;
1164             addTokenToPrefix(T.tokenArrayOpen);
1165             bracketCount++;
1166             return CONTINUE;
1167           case '.':
1168             if (ch2 == '.') {
1169               addTokenToPrefix(T.tokenArrayOpen);
1170               cchToken = 2;
1171               isDotDot = true;
1172               return CONTINUE;
1173             }
1174             addTokenToPrefix(T.o(T.per, "."));
1175             return CONTINUE;
1176           case '-':
1177           case '+':
1178           case '*':
1179           case '/':
1180           case '\\':
1181           case '&':
1182           case '|':
1183             if (ch2 == 0)
1184               return ERROR(ERROR_endOfCommandUnexpected);
1185             if (ch2 != ch && ch2 != '=')
1186               return ERROR(ERROR_badContext, "\"" + ch + "\"");
1187             break;
1188           default:
1189             lastToken = T.tokenMinus; // just to allow for {(....)}
1190             return CONTINUE;
1191           }
1192         }
1193       }
1194     }
1195     out: switch (tokCommand) {
1196     case T.show:
1197       // allowing for show domains and validation to take up rest of line
1198       switch (lastToken.tok) {
1199       case T.image:
1200       case T.symop:
1201       case T.property:
1202       case T.mo:
1203         if (nTokens == 2)
1204           iHaveQuotedString = true;
1205         break;
1206       case T.domains:
1207       case T.validation:
1208         break;
1209       default:
1210         if (!iHaveQuotedString && nTokens != 2)
1211           return OK;
1212         break;
1213       }
1214       //$FALL-THROUGH$
1215     case T.load:
1216     case T.script:
1217     case T.macro:
1218     case T.getproperty:
1219       if (script.charAt(ichToken) == '@') {
1220         iHaveQuotedString = true;
1221         return OK;
1222       }
1223       switch (tokCommand) {
1224       case T.macro:
1225         haveMacro = true;
1226         break out;
1227       case T.load:
1228         boolean isAppend = (tokAt(1) == T.append);
1229         if (nTokens == 1 || isAppend && (nTokens == 2 || nTokens == 3 && tokAt(2) == T.integer)) {
1230           if (isAppend && nTokens == 2 && PT.isDigit(charAt(ichToken)))
1231                 break out;
1232           boolean isDataBase = Viewer.isDatabaseCode(charAt(ichToken));
1233           if (lookingAtLoadFormat(isDataBase)) {
1234             String strFormat = script.substring(ichToken, ichToken + cchToken);
1235             T token = T.getTokenFromName(strFormat.toLowerCase());
1236             switch (token == null ? T.nada : token.tok) {
1237             case T.var:
1238             case T.menu:
1239             case T.orientation:
1240             case T.append:
1241             case T.history:
1242             case T.mutate:
1243             case T.nbo:
1244               if (nTokens != 1)
1245                 return ERROR;
1246               //$FALL-THROUGH$
1247             case T.data:
1248             case T.file:
1249             case T.inline:
1250             case T.model:
1251             case T.smiles:
1252             case T.trajectory:
1253             case T.async:
1254             case T.audio:
1255               addTokenToPrefix(token);
1256               break;
1257             default:
1258               // skip entirely if not recognized
1259               int tok = (isDataBase ? T.string
1260                   : PT.isOneOf(strFormat = strFormat.toLowerCase(),
1261                       JC.LOAD_ATOM_DATA_TYPES) ? T.identifier : 0);
1262               if (tok != 0) {
1263                 addTokenToPrefix(T.o(tok, strFormat));
1264                 iHaveQuotedString = (tok == T.string);
1265               }
1266             }
1267             return CONTINUE;
1268           }
1269           break;
1270         }
1271         BS bs;
1272         if (script.charAt(ichToken) == '{' || parenCount > 0)
1273           break out;
1274         if ((bs = lookingAtBitset()) != null) {
1275           addTokenToPrefix(T.o(T.bitset, bs));
1276           return CONTINUE;
1277         }
1278       }
1279       if (!iHaveQuotedString
1280           && lookingAtImpliedString(tokCommand == T.show, tokCommand == T.load,
1281               nTokens > 1 || tokCommand != T.script && tokCommand != T.macro)) {
1282         String str = script.substring(ichToken, ichToken + cchToken);
1283         if (tokCommand == T.script) {
1284           if (str.startsWith("javascript:")) {
1285             lookingAtImpliedString(true, true, true);
1286             str = script.substring(ichToken, ichToken + cchToken);
1287           } else if (str.toUpperCase().indexOf(".PUSH(") >= 0) {
1288             cchToken = 0;
1289             iHaveQuotedString = true;
1290             return CONTINUE;
1291           }
1292         }
1293         iHaveQuotedString = true;
1294         addTokenToPrefix(T.o(T.string, str));
1295         return CONTINUE;
1296       }
1297       break;
1298     case T.sync:
1299       if (nTokens == 1 && lookForSyncID()) {
1300         String ident = script.substring(ichToken, ichToken + cchToken);
1301         int iident = PT.parseInt(ident);
1302         if (iident == Integer.MIN_VALUE || Math.abs(iident) < 1000)
1303           addTokenToPrefix(T.o(T.identifier, ident));
1304         else
1305           addTokenToPrefix(T.i(iident));
1306         return CONTINUE;
1307       }
1308       break;
1309     case T.write:
1310       // write image 300 300 filename
1311       // write script filename
1312       // write spt filename
1313       // write jpg filename
1314       // write filename
1315       if (nTokens == 2 && lastToken.tok == T.frame)
1316         iHaveQuotedString = true;
1317       if (!iHaveQuotedString) {
1318         if (script.charAt(ichToken) == '@') {
1319           iHaveQuotedString = true;
1320           return OK;
1321         }
1322         if (lookingAtImpliedString(true, true, true)) {
1323           String str = script.substring(ichToken, ichToken + cchToken);
1324           int pt = str.indexOf(" as ");
1325           if (pt > 0)
1326             str = str.substring(0, cchToken = pt);
1327           if (str.indexOf(" ") < 0 && str.indexOf(".") >= 0) {
1328             addTokenToPrefix(T.o(T.string, str));
1329             iHaveQuotedString = true;
1330             return CONTINUE;
1331           }
1332         }
1333       }
1334       break;
1335     }
1336     // cd echo goto help hover javascript label message pause
1337     // possibly script
1338     implicitString &= (nTokens == 1);
1339     if (implicitString && !((tokCommand == T.script || tokCommand == T.macro)
1340         && iHaveQuotedString) && lookingAtImpliedString(true, true, true)) {
1341       String str = script.substring(ichToken, ichToken + cchToken);
1342       if (tokCommand == T.label
1343           && PT.isOneOf(str.toLowerCase(), ";on;off;hide;display;"))
1344         addTokenToPrefix(T.getTokenFromName(str.toLowerCase()));
1345       else
1346         addTokenToPrefix(T.o(T.string, str));
1347       return CONTINUE;
1348     }
1349     if (lookingAtObjectID()) {
1350       addTokenToPrefix(T.getTokenFromName("$"));
1351       addTokenToPrefix(
1352           T.o(T.identifier, script.substring(ichToken, ichToken + cchToken)));
1353       return CONTINUE;
1354     }
1355     float value;
1356     if (!Float.isNaN(value = lookingAtExponential())) {
1357       addNumber(T.decimal, Integer.MAX_VALUE, Float.valueOf(value));
1358       return CONTINUE;
1359     }
1360     if (lookingAtDecimal()) {
1361       value = PT.fVal(script.substring(ichToken, ichToken + cchToken));
1362       int intValue = (ScriptParam.getFloatEncodedInt(script.substring(ichToken,
1363           ichToken + cchToken)));
1364       addNumber(T.decimal, intValue, Float.valueOf(value));
1365       return CONTINUE;
1366     }
1367     if (lookingAtSeqcode()) {
1368       ch = script.charAt(ichToken);
1369       try {
1370         int seqNum = (ch == '*' || ch == '^' ? Integer.MAX_VALUE
1371             : Integer
1372                 .parseInt(script.substring(ichToken, ichToken + cchToken - 2)));
1373         char insertionCode = script.charAt(ichToken + cchToken - 1);
1374         if (insertionCode == '^')
1375           insertionCode = ' ';
1376         if (seqNum < 0) {
1377           seqNum = -seqNum;
1378           addTokenToPrefix(T.tokenMinus);
1379         }
1380         int seqcode = Group.getSeqcodeFor(seqNum, insertionCode);
1381         addTokenToPrefix(T.tv(T.inscode, seqcode, "seqcode"));
1382       } catch (NumberFormatException nfe) {
1383         return ERROR(ERROR_invalidExpressionToken, "" + ch);
1384       }
1385       return CONTINUE;
1386     }
1387     int val = lookingAtInteger();
1388     if (val != Integer.MAX_VALUE) {
1389       String intString = script.substring(ichToken, ichToken + cchToken);
1390       if (tokCommand == T.breakcmd || tokCommand == T.continuecmd) {
1391         if (nTokens != 1)
1392           return ERROR(ERROR_badArgumentCount);
1393         ScriptFlowContext f = (flowContext == null ? null
1394             : flowContext.getBreakableContext(val = Math.abs(val)));
1395         if (f == null)
1396           return ERROR(ERROR_badContext, (String) tokenCommand.value);
1397         tokenAt(0).intValue = f.pt0; // copy
1398       }
1399       // BAD IDEA! Just check the string for this!
1400       // if (val == 0 && intString.equals("-0"))
1401       //      addTokenToPrefix(T.tokenMinus);
1402       addNumber(T.integer, val, intString);
1403       return CONTINUE;
1404     }
1405     if (!isMathExpressionCommand && parenCount == 0
1406         || lastToken.tok != T.identifier && !tokenAttr(lastToken, T.mathfunc)) {
1407       // here if:
1408       //   structure helix ({...})
1409       //   frame align ({...})
1410       //   polyhedra BONDS ({...})
1411       //   isosurface select ({...})
1412       //   isosurface within({...})
1413       // NOT
1414       //   myfunc({...})
1415       //   mathFunc({...})
1416       // if you want to use a bitset there, you must use
1417       // bitsets properly: x.distance( ({1 2 3}) )
1418       boolean isBondOrMatrix = (script.charAt(ichToken) == '[');
1419       BS bs = lookingAtBitset();
1420       if (bs != null) {
1421         addTokenToPrefix(
1422             T.o(T.bitset, isBondOrMatrix ? BondSet.newBS(bs, null) : bs));
1423         return CONTINUE;
1424       }
1425       if (isBondOrMatrix) {
1426         Object m = lookingAtMatrix();
1427         if (m instanceof M34) {
1428           addTokenToPrefix(T.o((m instanceof M4 ? T.matrix4f : T.matrix3f), m));
1429           return CONTINUE;
1430         }
1431       }
1432     }
1433     return OK;
1434   }
1435 
addNumber(int tok, int i, Object v)1436   private void addNumber(int tok, int i, Object v) {
1437     // if after white space, we FORCE the integer
1438     addTokenToPrefix(afterWhite == ichToken ? SV.newSV(tok, i, v) : T.tv(tok,
1439         i, v));
1440   }
1441 
lookingAtMatrix()1442   private Object lookingAtMatrix() {
1443     int ipt;
1444     Object m;
1445     if (ichToken + 4 >= cchScript
1446         || script.charAt(ichToken) != '['
1447         || script.charAt(ichToken + 1) != '['
1448         || (ipt = script.indexOf("]]", ichToken)) < 0
1449         || (m = Escape.unescapeMatrix(script.substring(ichToken, ipt + 2))) == null)
1450       return null;
1451     cchToken = ipt + 2 - ichToken;
1452     return m;
1453   }
1454 
parseKnownToken()1455   private int parseKnownToken() {
1456 
1457     int tok = getPrefixToken();
1458 
1459     // specific token-based issues depend upon where we are in the command
1460 
1461     T token;
1462 
1463     if (isDotDot) {
1464       if (tok == T.leftsquare) {
1465         bracketCount++;
1466       } else {
1467         addTokenToPrefix(T.o(T.string, ident));
1468         addTokenToPrefix(T.tokenArrayClose);
1469       }
1470       isDotDot = false;
1471       return CONTINUE;
1472     }
1473     if (tokLastMath != 0)
1474       tokLastMath = tok;
1475     if (flowContext != null && flowContext.token.tok == T.switchcmd
1476         && flowContext.var != null && tok != T.casecmd && tok != T.defaultcmd
1477         && lastToken.tok != T.switchcmd)
1478       return ERROR(ERROR_badContext, ident);
1479     if (lastToken.tok == T.define && tok != T.leftbrace && nTokens != 1) {
1480       addTokenToPrefix(tok == T.define ? lastToken : T.o(T.string, ident));
1481       return CONTINUE;
1482     }
1483     switch (tok) {
1484     case T.identifier:
1485       if (nTokens == 0 && !checkImpliedScriptCmd) {
1486         if (ident.charAt(0) == '\'') {
1487           addTokenToPrefix(setCommand(T.tokenScript));
1488           cchToken = 0;
1489           return CONTINUE;
1490         }
1491         if (charAt(ichToken + cchToken) == '.') {
1492           addTokenToPrefix(setCommand(T.tokenScript));
1493           nTokens = 1;
1494           cchToken = 0;
1495           checkImpliedScriptCmd = true;
1496           return CONTINUE;
1497         }
1498       }
1499       break;
1500     case T.andequals:
1501       if (nSemiSkip == forPoint3 && nTokens == ptSemi + 2) {
1502         token = lastToken;
1503         addTokenToPrefix(T.tokenEquals);
1504         addTokenToPrefix(token);
1505         token = T.getTokenFromName(ident.substring(0, 1));
1506         addTokenToPrefix(token);
1507         addTokenToPrefix(T.tokenLeftParen);
1508         needRightParen = true;
1509         return CONTINUE;
1510       }
1511       // check to see if we have a command name that
1512       // was not registered yet as a local variable:
1513       // var color = 3
1514       // color += 3
1515       checkNewSetCommand();
1516       if (tokCommand == T.set) {
1517         tokenAndEquals = T.getTokenFromName(ident.substring(0, 1));
1518         setEqualPt = ichToken;
1519         return OK;
1520       }
1521       if (tokCommand == T.slab || tokCommand == T.depth) {
1522         addTokenToPrefix(tokenCommand);
1523         replaceCommand(T.tokenSet);
1524         tokenAndEquals = T.getTokenFromName(ident.substring(0, 1));
1525         setEqualPt = ichToken;
1526         return OK;
1527       }
1528       // otherwise ignore
1529       return CONTINUE;
1530     case T.minusMinus:
1531     case T.plusPlus:
1532       if (afterWhite == ichToken || afterMath == ichToken)
1533         theToken = T.tv(theToken.tok, -1, theToken.value);
1534       if (!isNewSet && nTokens == 1)
1535         checkNewSetCommand();
1536       if (isNewSet && parenCount == 0 && bracketCount == 0
1537           && ichToken <= setEqualPt) {
1538         tokenizePlusPlus(tok, false);
1539         return CONTINUE;
1540       } else if (nSemiSkip == forPoint3 && nTokens == ptSemi + 2) {
1541         token = lastToken;
1542         addTokenToPrefix(T.tokenEquals);
1543         addTokenToPrefix(token);
1544         addTokenToPrefix(tok == T.minusMinus ? T.tokenMinus : T.tokenPlus);
1545         addTokenToPrefix(T.i(1));
1546         return CONTINUE;
1547       }
1548       break;
1549     case T.opEQ:
1550       if (parenCount == 0 && bracketCount == 0)
1551         setEqualPt = ichToken;
1552       break;
1553     case T.per:
1554       if (tokCommand == T.set && parenCount == 0 && bracketCount == 0
1555           && ichToken < setEqualPt && ltoken.size() > 1
1556           && ltoken.get(1).tok == T.leftbrace) {
1557         // {a}[3].xxx = ...
1558         // {a}[3][4].xxx = ...
1559         // initial brace has been lost, however?
1560         ltoken.set(0, T.tokenSetProperty);
1561         ltoken.add(1, T.tokenExpressionBegin);
1562         addTokenToPrefix(T.tokenExpressionEnd);
1563         setEqualPt = 0;
1564       }
1565       break;
1566     case T.leftbrace:
1567       if (++braceCount == 1 && parenCount == 0 && checkFlowStartBrace(false)) {
1568         // specifically for else {  but not elseIf ( ) {
1569         isEndOfCommand = true;
1570         ScriptFlowContext f = (flowContext != null && flowContext.addLine == 0
1571             || forceFlowContext == null ? flowContext : forceFlowContext);
1572         if (f != null) {
1573           f.addLine = 0;
1574           f.forceEndIf = false;
1575           lastToken = T.tokenLeftBrace;
1576           forceFlowContext = f;
1577         }
1578         return CONTINUE;
1579       }
1580       parenCount++;
1581       break;
1582     case T.leftparen:
1583       parenCount++;
1584       // the select() function uses dual semicolon notation
1585       // but we must differentiate from isosurface select(...) and set
1586       // picking select
1587       if (nTokens > 1
1588           && (lastToken.tok == T.select || lastToken.tok == T.forcmd || lastToken.tok == T.ifcmd))
1589         nSemiSkip += 2;
1590       break;
1591     case T.rightbrace:
1592       if (iBrace > 0 && parenCount == 0 && braceCount == 0) {
1593         ichBrace = ichToken;
1594         if (nTokens == 0) {
1595           braceCount = parenCount = 1;
1596         } else {
1597           if (!wasImpliedScript()) {
1598             // because if we need to repeat due to a.xxx = ....
1599             // then we don't want to force the end yet
1600             //  for (a in {selected}) { wrap(a, vnm, f) }
1601 
1602             braceCount = parenCount = nSemiSkip = 0;
1603             addBrace(theToken);
1604             isEndOfCommand = true;
1605             ichEnd = ichToken;
1606           }
1607           return CONTINUE;
1608         }
1609       }
1610       braceCount--;
1611       //$FALL-THROUGH$
1612     case T.rightparen:
1613       if (--parenCount < 0)
1614         return ERROR(ERROR_tokenUnexpected, ident);
1615       // we need to remove the semiskip if parentheses or braces have been
1616       // closed. 11.5.46
1617       if (parenCount == 0)
1618         nSemiSkip = 0;
1619       if (needRightParen) {
1620         addTokenToPrefix(T.tokenRightParen);
1621         needRightParen = false;
1622       }
1623       break;
1624     case T.leftsquare:
1625       if (ichToken > 0 && PT.isWhitespace(script.charAt(ichToken - 1)))
1626         addTokenToPrefix(T.tokenSpaceBeforeSquare);
1627       bracketCount++;
1628       break;
1629     case T.rightsquare:
1630       bracketCount--;
1631       if (bracketCount < 0)
1632         return ERROR(ERROR_tokenUnexpected, "]");
1633       break;
1634     case T.perper:
1635       isDotDot = true;
1636       addTokenToPrefix(T.tokenArrayOpen);
1637       return CONTINUE;
1638     }
1639     // check for token in [start:3,end:5]
1640     // x.end
1641     // x..end
1642     switch (lastToken.tok) {
1643     case T.per:
1644     case T.perper:
1645     case T.comma:
1646     case T.leftsquare:
1647       return OK;
1648     }
1649     switch (tok) {
1650     case T.privat:
1651       isPrivateFunc = true;
1652       return CONTINUE;
1653     case T.end:
1654       if (tokCommand == T.cgo || tokCommand == T.capture && nTokens == 1)
1655         return OK;
1656       if (!haveENDIF)
1657         return RESTART;
1658       // end for, end function
1659       //$FALL-THROUGH$
1660     case T.endifcmd:
1661       if (flowContext != null)
1662         flowContext.forceEndIf = false;
1663       //$FALL-THROUGH$
1664     case T.elsecmd:
1665       if (nTokens > 0) {
1666         isEndOfCommand = true;
1667         cchToken = 0;
1668         return CONTINUE;
1669       }
1670       break;
1671     case T.forcmd:
1672     case T.casecmd:
1673     case T.defaultcmd:
1674     case T.elseif:
1675     case T.ifcmd:
1676     case T.switchcmd:
1677     case T.whilecmd:
1678     case T.catchcmd:
1679       if (nTokens > 1 && tokCommand != T.set && nSemiSkip == 0) {
1680         isEndOfCommand = true;
1681         if (flowContext != null)
1682           flowContext.forceEndIf = true;
1683         cchToken = 0;
1684         return CONTINUE;
1685       }
1686       break;
1687     }
1688     return OK;
1689   }
1690 
tokenizePlusPlus(int tok, boolean isPlusPlusX)1691   private void tokenizePlusPlus(int tok, boolean isPlusPlusX) {
1692     //   ++ipt;   or ipt++
1693     if (isPlusPlusX) {
1694       setCommand(T.tokenSet);
1695       if (nTokens == 1)
1696         ltoken.add(0, tokenCommand);
1697     }
1698     nTokens = ltoken.size();
1699     addTokenToPrefix (T.tokenEquals);
1700     setEqualPt = 0;
1701     for (int i = 1; i < nTokens; i++)
1702       addTokenToPrefix(ltoken.get(i));
1703     addTokenToPrefix(tok == T.minusMinus ? T.tokenMinus : T.tokenPlus);
1704     addTokenToPrefix(T.i(1));
1705   }
1706 
checkNewSetCommand()1707   private boolean checkNewSetCommand() {
1708     String name = ltoken.get(0).value.toString();
1709     if (!isContextVariable(name.toLowerCase()))
1710       return false;
1711     T t = setNewSetCommand(false, name);
1712     setCommand(T.tokenSet);
1713     ltoken.add(0, tokenCommand);
1714     ltoken.set(1, t);
1715     return true;
1716   }
1717 
parseCommandParameter(short iLine, boolean isFull)1718   private int parseCommandParameter(short iLine, boolean isFull) {
1719     // PART II:
1720     //
1721     // checking tokens based on the current command
1722     // all command starts are handled by case Token.nada
1723 
1724     nTokens = ltoken.size();
1725     switch (tokCommand) {
1726     case T.nada:
1727       // first token in command
1728       lastToken = T.tokenOff;
1729       ichCurrentCommand = ichEnd = ichToken;
1730       setCommand(theToken);
1731       if (logMessages)
1732         Logger.info("compiling " + theToken);
1733       boolean isFlowCmd = T.tokAttr(tokCommand, T.flowCommand);
1734       if (isFlowCmd) {
1735         lastFlowCommand = tokenCommand;
1736       }
1737       // before processing this command, check to see if we have completed
1738       // a right-brace.
1739       int ret = checkFlowEndBrace();
1740       if (ret == ERROR)
1741         return ERROR;
1742       else if (ret == CONTINUE) {
1743         // yes, so re-read this one
1744         isEndOfCommand = true;
1745         cchToken = 0;
1746         if (theTok == T.leftparen)
1747           parenCount--;
1748         return CONTINUE;
1749       }
1750       switch (theTok) {
1751       case T.leftbrace:
1752         break;
1753       case T.colon:
1754         braceCount++;
1755         isEndOfCommand = true;
1756         return OK;
1757       case T.end:
1758         return OK;
1759       case T.elsecmd:
1760       case T.elseif:
1761         // unexpectedly allows if (x) { print x else print y}
1762         fixFlowAddLine(flowContext);
1763         if (lltoken.get(iCommand - 1)[0].tok == T.end
1764             && forceFlowContext != null && forceFlowContext.forceEndIf
1765             && forceFlowContext.addLine > 0
1766             && isFlowIfContextOK(forceFlowContext)) {
1767           flowContext = forceFlowContext;
1768           flowContext.forceEndIf = true;
1769           lltoken.removeItemAt(--iCommand);
1770         } else if (flowContext != null && flowContext.addLine > 0) {
1771           while (flowContext != null && !isFlowIfContextOK(flowContext)) {
1772             if (flowContext.checkForceEndIf(0)) {
1773               forceFlowEnd(flowContext.token);
1774               processTokenList(iLine, isFull);
1775               fixFlowAddLine(flowContext);
1776               setCommand(theToken);
1777               theTok = theToken.tok;
1778             } else {
1779               break;
1780             }
1781           }
1782         }
1783         // why is this?
1784         //        if (lastFlowContext != null && lastFlowContext.forceEndIf
1785         //            && lastFlowContext.addLine > 0) {
1786         //          flowContext = lastFlowContext;
1787         //          flowContext.forceEndIf = true;
1788         //          if (isFlowIfContextOK(flowContext, tokCommand) && lltoken.get(iCommand - 1)[0].tok == T.end)
1789         //            lltoken.remove(--iCommand);
1790         //        }
1791         //$FALL-THROUGH$
1792       default:
1793         if (isFlowCmd) {
1794           // if ... continue ....
1795           switch (checkFlowCommand((String) tokenCommand.value)) {
1796           case ERROR:
1797             return ERROR;
1798           case CONTINUE:
1799             return CONTINUE;
1800           case RESTART:
1801             return RESTART;
1802           case OK:
1803             theToken = tokenCommand;
1804             if (theTok == T.casecmd) {
1805               addTokenToPrefix(tokenCommand);
1806               theToken = T.tokenLeftParen;
1807             }
1808             return OK;
1809           }
1810           // OK2 -- for break and continue; continue on as for any other standard command
1811         }
1812         if (flowContext != null && !haveENDIF && flowContext.addLine > 0) {
1813           // increment the pointer indicating where to insert "end XXXX"
1814           fixFlowAddLine(flowContext);
1815           while (flowContext != null) {
1816             if (flowContext.checkForceEndIf(0)) {
1817               forceFlowEnd(flowContext.token);
1818               processTokenList(iLine, isFull);
1819               setCommand(theToken);
1820               theTok = theToken.tok;
1821             } else {
1822               break;
1823             }
1824           }
1825         }
1826         if (theTok == T.rightbrace) {
1827           // if }, just push onto vBrace, but NOT onto ltoken
1828           // no longer necessary to force end
1829           forceFlowContext = null;
1830           addBrace(tokenCommand);
1831           tokCommand = T.nada;
1832           return CONTINUE;
1833         }
1834         lastFlowCommand = null;
1835       }
1836 
1837       if (theTok == T.opAnd) {
1838         //& introduces a resume context
1839         setCommand(theToken = T.o(T.resume, "resume"));
1840         addTokenToPrefix(theToken);
1841         theToken = T.o(T.context, "context");
1842         return OK;
1843       }
1844       if (T.tokAttr(tokCommand, T.scriptCommand))
1845         break;
1846 
1847       // not the standard command
1848       // isSetBrace: {xxx}.yyy = or {xxx}[xx].
1849       // isNewSet: xxx =
1850       // but not xxx = where xxx is a known "set xxx" variant
1851       // such as "set hetero" or "set hydrogen" or "set solvent"
1852 
1853       isSetBrace = (theTok == T.leftbrace);
1854       if (isSetBrace) {
1855         if (!lookingAtSetBraceSyntax()) {
1856           isEndOfCommand = true;
1857           if (flowContext != null)
1858             flowContext.forceEndIf = false;
1859         }
1860       } else {
1861         switch (theTok) {
1862         case T.plusPlus:
1863         case T.minusMinus:
1864           tokInitialPlusPlus = theTok;
1865           tokCommand = T.nada;
1866           return CONTINUE;
1867         case T.identifier:
1868         case T.var:
1869         case T.define:
1870         case T.leftparen:
1871           break;
1872         default:
1873           if (!T.tokAttr(theTok, T.misc) && !T.tokAttr(theTok, T.setparam)
1874               && !isContextVariable(identLC)) {
1875             commandExpected();
1876             return ERROR;
1877           }
1878         }
1879       }
1880       theToken = setNewSetCommand(isSetBrace, ident);
1881       break;
1882     case T.catchcmd:
1883       switch (nTokens) {
1884       case 1:
1885         if (theTok != T.leftparen)
1886           return ERROR(ERROR_tokenExpected, "(");
1887         break;
1888       case 2:
1889         if (theTok != T.rightparen)
1890           ((ContextToken) tokenCommand).name0 = ident;
1891         newContextVariable(ident);
1892         break;
1893       case 3:
1894         if (theTok != T.rightparen)
1895           return ERROR(ERROR_tokenExpected, ")");
1896         isEndOfCommand = true;
1897         ichEnd = ichToken + 1;
1898         flowContext.setLine();
1899         break;
1900       default:
1901         return ERROR(ERROR_badArgumentCount);
1902       }
1903       break;
1904     case T.parallel:
1905     case T.function:
1906       if (tokenCommand.intValue == 0) {
1907         if (nTokens != 1)
1908           break; // anything after name is ok
1909         // user has given macro command
1910         tokenCommand.value = ident;
1911         return CONTINUE; // don't store name in stack
1912       }
1913       if (nTokens == 1) {
1914         if (thisFunction != null)
1915           vFunctionStack.add(0, thisFunction);
1916         thisFunction = (tokCommand == T.parallel ? (ScriptFunction) Interface
1917             .getInterface("org.jmol.script.ScriptParallelProcessor", null, null)
1918             : new ScriptFunction(ident, tokCommand));
1919         thisFunction.set(ident, tokCommand);
1920         thisFunction.isPrivate = isStateScript || isPrivateScript || isPrivateFunc;
1921         isPrivateFunc = false;
1922         htUserFunctions.put(ident, Boolean.TRUE);
1923         flowContext.setFunction(thisFunction);
1924         break; // function f
1925       }
1926       if (nTokens == 2) {
1927         if (theTok != T.leftparen)
1928           return ERROR(ERROR_tokenExpected, "(");
1929         break; // function f (
1930       }
1931       if (nTokens == 3 && theTok == T.rightparen)
1932         break; // function f ( )
1933       if (nTokens % 2 == 0) {
1934         // function f ( x , y )
1935         if (theTok != T.comma && theTok != T.rightparen)
1936           return ERROR(ERROR_tokenExpected, ")");
1937         break;
1938       }
1939       thisFunction.addVariable(ident, true);
1940       break;
1941     case T.casecmd:
1942       if (nTokens > 1 && parenCount == 0 && braceCount == 0
1943           && theTok == T.colon) {
1944         addTokenToPrefix(T.tokenRightParen);
1945         braceCount = 1;
1946         isEndOfCommand = true;
1947         cchToken = 0;
1948         return CONTINUE;
1949       }
1950       break;
1951     case T.defaultcmd:
1952       if (nTokens > 1) {
1953         braceCount = 1;
1954         isEndOfCommand = true;
1955         cchToken = 0;
1956         return CONTINUE;
1957       }
1958       break;
1959     case T.elsecmd:
1960       if (nTokens == 1 && theTok != T.ifcmd) {
1961         isEndOfCommand = true;
1962         cchToken = 0;
1963         return CONTINUE;
1964       }
1965       if (nTokens != 1 || theTok != T.ifcmd && theTok != T.leftbrace)
1966         return ERROR(ERROR_badArgumentCount);
1967       replaceCommand(flowContext.token = ContextToken
1968           .newCmd(T.elseif, "elseif"));
1969       tokCommand = T.elseif;
1970       return CONTINUE;
1971     case T.end:
1972       if (nTokens != 1)
1973         return ERROR(ERROR_badArgumentCount);
1974       if (!checkFlowEnd(theTok, ident, ichCurrentCommand, true))
1975         return ERROR;
1976       if (theTok == T.function || theTok == T.parallel) {
1977         return CONTINUE;
1978       }
1979       break;
1980     case T.forcmd:
1981       if (nTokens == 1) {
1982         if (theTok != T.leftparen)
1983           return ERROR(ERROR_unrecognizedToken, ident);
1984         forPoint3 = nSemiSkip = 0;
1985         nSemiSkip += 2;
1986         break;
1987       }
1988       if (nTokens == 3 && tokAt(2) == T.var) {
1989         newContextVariable(ident);
1990         break;
1991       }
1992       if ((nTokens == 3 || nTokens == 4) && theTok == T.in) {
1993         // for ( var x IN
1994         // for ( x IN
1995         nSemiSkip -= 2;
1996         forPoint3 = 2;
1997         addTokenToPrefix(theToken);
1998         theToken = T.tokenLeftParen;
1999         break;
2000       }
2001       //$FALL-THROUGH$
2002     case T.switchcmd:
2003     case T.whilecmd:
2004     case T.elseif:
2005     case T.ifcmd:
2006       if (nTokens <= 2 || braceCount != 0 || parenCount != 0)
2007         break;
2008       //$FALL-THROUGH$
2009     case T.process:
2010       isEndOfCommand = true;
2011       ichEnd = ichToken + 1;
2012       flowContext.setLine();
2013       break;
2014     case T.var:
2015       if (nTokens == 1) {
2016         replaceCommand(T.tokenSetVar);
2017         newContextVariable(ident);
2018         break;
2019       } else if (ident.equals(",")) {
2020         return CONTINUE;
2021       } else if (!PT.isLetter(ident.charAt(0))) {
2022         if (nTokens != 2 || ident.equals("["))
2023           return ERROR(ERROR_badArgumentCount);
2024         replaceCommand(T.tokenSet);
2025       } else {
2026         newContextVariable(ident);
2027         break;
2028       }
2029       //$FALL-THROUGH$
2030     case T.set:
2031       if (theTok == T.leftbrace)
2032         setBraceCount++;
2033       else if (theTok == T.rightbrace) {
2034         setBraceCount--;
2035         if (isSetBrace && setBraceCount == 0
2036             && ptNewSetModifier == Integer.MAX_VALUE)
2037           ptNewSetModifier = nTokens + 1;
2038       }
2039       if (nTokens == ptNewSetModifier) { // 1 when { is not present
2040         T token = tokenAt(0);
2041         if (theTok == T.leftparen || isUserFunction(token.value.toString())) {
2042           // mysub(xxx,xxx,xxx)
2043           ltoken.set(0, setCommand(T.tv(T.identifier, 0, token.value)));
2044           setBraceCount = 0;
2045           break;
2046         }
2047         if (theTok != T.identifier && theTok != T.andequals
2048             && theTok != T.define && (!T.tokAttr(theTok, T.setparam))) {
2049           if (isNewSet)
2050             commandExpected();
2051           else
2052             errorIntStr2(ERROR_unrecognizedParameter, "SET", ": " + ident);
2053           return ERROR;
2054         }
2055         if (nTokens == 1
2056             && (lastToken.tok == T.plusPlus || lastToken.tok == T.minusMinus)) {
2057           replaceCommand(T.tokenSet);
2058           addTokenToPrefix(lastToken);
2059           break;
2060         }
2061       }
2062       break;
2063     case T.load:
2064       if (theTok == T.define
2065           && (nTokens == 1 || lastToken.tok == T.filter || lastToken.tok == T.spacegroup)) {
2066         addTokenToPrefix(T.tokenDefineString);
2067         return CONTINUE;
2068       }
2069       if (theTok == T.as)
2070         iHaveQuotedString = false;
2071       break;
2072     case T.define:
2073       if (nTokens == 1) {
2074         // we are looking at the variable name
2075         if (theTok != T.identifier) {
2076           if (preDefining) {
2077             if (!T.tokAttr(theTok, T.predefinedset)) {
2078               errorStr2(
2079                   "ERROR IN Token.java or JmolConstants.java -- the following term was used in JmolConstants.java but not listed as predefinedset in Token.java: "
2080                       + ident, null);
2081               return ERROR;
2082             }
2083           } else if (T.tokAttr(theTok, T.predefinedset)) {
2084             Logger.warn("WARNING: predefined term '" + ident
2085                 + "' has been redefined by the user until the next file load.");
2086           } else if (!isCheckOnly && ident.length() > 1) {
2087             Logger
2088                 .warn("WARNING: redefining "
2089                     + ident
2090                     + "; was "
2091                     + theToken
2092                     + "not all commands may continue to be functional for the life of the applet!");
2093             theTok = theToken.tok = T.identifier;
2094             T.addToken(ident, theToken);
2095           }
2096         }
2097         addTokenToPrefix(theToken);
2098         lastToken = T.tokenComma;
2099         return CONTINUE;
2100       }
2101       if (nTokens == 2) {
2102         if (theTok == T.opEQ) {
2103           // we are looking at @x =.... just insert a SET command
2104           // and ignore the =. It's the same as set @x ...
2105           ltoken.add(0, T.tokenSet);
2106           return CONTINUE;
2107         }
2108       }
2109       //$FALL-THROUGH$
2110       //    case T.display:
2111       //    case T.hide:
2112       //    case T.restrict:
2113       //    case T.select:
2114       //    case T.delete:
2115       //      if (bracketCount == 0 && theTok != T.identifier
2116       //          && !T.tokAttr(theTok, T.expression) && !T.tokAttr(theTok, T.misc)
2117       //          && (theTok & T.minmaxmask) != theTok)
2118       //        return ERROR(ERROR_invalidExpressionToken, ident);
2119       break;
2120     //    case T.center:
2121     //      if (theTok != T.identifier && theTok != T.dollarsign
2122     //          && !T.tokAttr(theTok, T.expression))
2123     //        return ERROR(ERROR_invalidExpressionToken, ident);
2124     //      break;
2125     case T.plot3d:
2126     case T.pmesh:
2127     case T.isosurface:
2128       // isosurface ... name.xxx
2129       char ch = charAt(ichToken + cchToken);
2130       if (parenCount == 0 && bracketCount == 0 && ".:/\\+-!?".indexOf(ch) >= 0
2131           && !(ch == '-' && ident.equals("=")))
2132         checkUnquotedFileName();
2133       break;
2134     }
2135     return OK;
2136   }
2137 
setNewSetCommand(boolean isSetBrace, String ident)2138   private T setNewSetCommand(boolean isSetBrace, String ident) {
2139     tokCommand = T.set;
2140     isNewSet = (!isSetBrace && !isUserFunction(ident));
2141     setBraceCount = (isSetBrace ? 1 : 0);
2142     bracketCount = 0;
2143     setEqualPt = Integer.MAX_VALUE;
2144     ptNewSetModifier = (isNewSet ? (ident.equals("(") ? 2 : 1)
2145         : Integer.MAX_VALUE);
2146     // unfortunately we have to look here for defaultLattice, because it must not turn into a string.
2147     return ((isSetBrace || theToken.tok == T.leftparen
2148         || theToken.tok == T.defaultlattice || theToken.tok == T.plusPlus || theToken.tok == T.minusMinus) ? theToken
2149         : T.o(T.identifier, ident));
2150   }
2151 
checkUnquotedFileName()2152   private void checkUnquotedFileName() {
2153     int ichT = ichToken;
2154     char ch;
2155     while (++ichT < cchScript && !PT.isWhitespace(ch = script.charAt(ichT))
2156         && ch != '#' && ch != ';' && ch != '}') {
2157     }
2158     String name = script.substring(ichToken, ichT).replace('\\', '/');
2159     cchToken = ichT - ichToken;
2160     theToken = T.o(T.string, name);
2161   }
2162 
checkFlowStartBrace(boolean atEnd)2163   private boolean checkFlowStartBrace(boolean atEnd) {
2164     int tok = tokCommand;
2165     switch (tok) {
2166     default:
2167       if (T.tokAttr(tok, T.flowCommand)) {
2168         if (atEnd) {
2169           switch (tok) {
2170           case T.casecmd:
2171           case T.defaultcmd:
2172             break;
2173           default:
2174             flowContext.addLine = 0;
2175             addBrace(tokenCommand);
2176             lastFlowCommand = null;
2177             break;
2178           }
2179           parenCount = braceCount = 0;
2180         }
2181         return true;
2182       }
2183       //$FALL-THROUGH$
2184     case T.breakcmd:
2185     case T.continuecmd:
2186       return false;
2187     }
2188   }
2189 
2190   /**
2191    * process a pending explicit right brace }
2192    *
2193    * @return continuation status
2194    */
checkFlowEndBrace()2195   private int checkFlowEndBrace() {
2196 
2197     if (iBrace <= 0 || vBraces.get(iBrace - 1).tok != T.rightbrace)
2198       return OK;
2199     // time to execute end
2200     vBraces.removeItemAt(--iBrace);
2201     T token = vBraces.removeItemAt(--iBrace);
2202     if (theTok == T.leftbrace) {
2203       //
2204       // }
2205       // {
2206       //
2207       braceCount--;
2208       parenCount--;
2209     }
2210     if (token.tok == T.push) {
2211       vPush.removeItemAt(--pushCount);
2212       // close this context
2213       addTokenToPrefix(setCommand(ContextToken.newContext(false)));
2214       isEndOfCommand = true;
2215       return CONTINUE;
2216     }
2217     switch (flowContext == null ? 0 : flowContext.token.tok) {
2218     case T.ifcmd:
2219     case T.elseif:
2220     case T.elsecmd:
2221       if (tokCommand == T.elsecmd || tokCommand == T.elseif)
2222         return OK;
2223       break;
2224     case T.switchcmd:
2225     case T.casecmd:
2226     case T.defaultcmd:
2227       if (tokCommand == T.casecmd || tokCommand == T.defaultcmd)
2228         return OK;
2229     }
2230     return forceFlowEnd(token);
2231   }
forceFlowEnd(T token)2232   private int forceFlowEnd(T token) {
2233     T t0 = tokenCommand;
2234     forceFlowContext = flowContext;
2235     token = flowStart(token);
2236     if (!checkFlowEnd(token.tok, (String) token.value, ichBrace, false))
2237       return ERROR;
2238     switch (token.tok){
2239     case T.function:
2240     case T.parallel:
2241     case T.trycmd:
2242       break;
2243     default:
2244       addTokenToPrefix(token);
2245     }
2246     setCommand(t0);
2247     return CONTINUE;
2248   }
2249 
flowStart(T token)2250   private T flowStart(T token) {
2251     switch (token.tok) {
2252     case T.ifcmd:
2253     case T.elsecmd:
2254     case T.elseif:
2255       return T.tokenIf;
2256     case T.defaultcmd:
2257     case T.casecmd:
2258       return T.tokenSwitch;
2259     default:
2260       return T.getTokenFromName((String) token.value);
2261     }
2262 
2263   }
2264 
isBreakableContext(int tok)2265   static boolean isBreakableContext(int tok) {
2266     return tok == T.forcmd || tok == T.process || tok == T.whilecmd
2267         || tok == T.casecmd || tok == T.defaultcmd;
2268   }
2269 
checkFlowCommand(String ident)2270   private int checkFlowCommand(String ident) {
2271     int pt = lltoken.size();
2272     switch (tokCommand) {
2273     case T.endifcmd:
2274       if (!isFlowIfContextOK(flowContext)){
2275         if (!haveENDIF)
2276           return RESTART;
2277         errorStr(ERROR_badContext, ident);
2278         return ERROR;
2279       }
2280       flowContext.token.intValue = flowContext.setPt0(pt, false);
2281       setFlowEnd(tokCommand, ident);
2282       flowContext = flowContext.parent;
2283       return OK;
2284     case T.breakcmd:
2285     case T.continuecmd:
2286       ScriptFlowContext f = (flowContext == null ? null : flowContext
2287           .getBreakableContext(0));
2288       if (tokCommand == T.continuecmd)
2289         while (f != null && f.token.tok != T.forcmd
2290             && f.token.tok != T.whilecmd)
2291           f = f.parent;
2292       if (f == null) {
2293         errorStr(ERROR_badContext, ident);
2294         return ERROR;
2295       }
2296       setCommand(T.tv(tokCommand, f.pt0, ident)); //copy
2297       theToken = tokenCommand;
2298       return OK2;
2299     case T.function:
2300     case T.parallel:
2301       if (flowContext != null) {
2302         errorStr(ERROR_badContext, T.nameOf(tokCommand));
2303         return ERROR;
2304       }
2305       break;
2306 
2307     case T.ifcmd:
2308       //$FALL-THROUGH$
2309     case T.trycmd:
2310     case T.catchcmd:
2311     case T.forcmd:
2312     case T.process:
2313     case T.switchcmd:
2314     case T.whilecmd:
2315       break;
2316     case T.elseif:
2317     case T.elsecmd:
2318       if (flowContext != null && !isFlowIfContextOK(flowContext)) {
2319         flowContext = flowContext.parent;
2320       }
2321       if (!isFlowIfContextOK(flowContext)) {
2322         if (!haveENDIF)
2323           return RESTART;
2324         errorStr(ERROR_badContext, ident);
2325         return ERROR;
2326       }
2327       flowContext.token.intValue = flowContext.setPt0(pt, false);
2328       break;
2329     case T.casecmd:
2330     case T.defaultcmd:
2331       if (flowContext == null
2332           || flowContext.token.tok != T.switchcmd
2333           && flowContext.token.tok != T.casecmd
2334           && (tokCommand == T.defaultcmd ? flowContext.ptDefault > 0
2335               : flowContext.token.tok != T.defaultcmd)) {
2336         errorStr(ERROR_badContext, ident);
2337         return ERROR;
2338       }
2339       flowContext.token.intValue = flowContext.setPt0(pt,
2340           tokCommand == T.defaultcmd);
2341       break;
2342     }
2343     // we do need a new context token
2344     ContextToken ct = ContextToken.newCmd(tokCommand, tokenCommand.value);
2345     if (tokCommand == T.switchcmd)
2346       ct.addName("_var");
2347     setCommand(ct); //copy
2348     switch (tokCommand) {
2349     case T.trycmd:
2350       flowContext = new ScriptFlowContext(this, ct, pt, flowContext,
2351           ichCurrentCommand, lineCurrent);
2352       if (thisFunction != null)
2353         vFunctionStack.add(0, thisFunction);
2354       thisFunction = new ScriptFunction("", T.trycmd);
2355       flowContext.setFunction(thisFunction);
2356       pushContext(ct);
2357       break;
2358     case T.casecmd:
2359     case T.defaultcmd:
2360       ct.contextVariables = flowContext.token.contextVariables;
2361       //$FALL-THROUGH$
2362     case T.elsecmd:
2363     case T.elseif:
2364       flowContext.token = ct;
2365       break;
2366     case T.process:
2367     case T.forcmd:
2368     case T.whilecmd:
2369     case T.catchcmd:
2370       pushContext(ct);
2371       //$FALL-THROUGH$
2372     case T.ifcmd:
2373     case T.switchcmd:
2374     default:
2375       flowContext = new ScriptFlowContext(this, ct, pt, flowContext,
2376           ichCurrentCommand, lineCurrent);
2377     }
2378     return OK;
2379   }
2380 
2381   /**
2382    * generate a new end token with pointer to the start or to default as the intValue
2383    * and set it as the command token
2384    *
2385    * @param tokCommand end or endif
2386    * @param ident  "end" or "endif"
2387    */
setFlowEnd(int tokCommand, String ident)2388   private void setFlowEnd(int tokCommand, String ident) {
2389     setCommand(T.tv(tokCommand,
2390         (flowContext.ptDefault > 0 ? flowContext.ptDefault
2391             : -flowContext.pt0), ident)); //copy
2392   }
2393   /**
2394    * check for proper sequence: if...[any number of elseif]...[zero or one else]...[endif]
2395    * @param f
2396    * @return true if OK
2397    */
isFlowIfContextOK(ScriptFlowContext f)2398   private boolean isFlowIfContextOK(ScriptFlowContext f) {
2399     switch (f == null ? T.nada : f.token.tok) {
2400     case T.ifcmd:
2401     case T.elseif:
2402       return true;
2403     case T.elsecmd:
2404       return tokCommand != T.elsecmd;
2405     }
2406     return false;
2407   }
2408 
2409   /**
2410    *
2411    * @param tok the XXX in END XXX
2412    * @param ident
2413    * @param pt1
2414    * @param isExplicitEnd actual END IF or END FOR, etc.
2415    * @return true if no error
2416    */
checkFlowEnd(int tok, String ident, int pt1, boolean isExplicitEnd)2417   private boolean checkFlowEnd(int tok, String ident, int pt1,
2418                                boolean isExplicitEnd) {
2419     if (isExplicitEnd) {
2420         if (flowContext == null)
2421           return errorStr(ERROR_badContext, "end " + ident);
2422         flowContext.addLine = 0;
2423         flowContext.forceEndIf = false;
2424         switch (flowContext.token.tok) {
2425         case T.function:
2426         case T.parallel:
2427         case T.trycmd:
2428           break;
2429         default:
2430           setFlowEnd(T.end, "end");
2431           ltoken.set(0, tokenCommand);
2432       }
2433     } else {
2434       setFlowEnd(T.end, "end");
2435       addTokenToPrefix(tokenCommand);
2436     }
2437     if (flowContext == null || tok != flowContext.tok0)
2438       return errorStr(ERROR_badContext, "end " + ident);
2439     int pt = lltoken.size();
2440     flowContext.token.intValue = (tokCommand == T.catchcmd ? -pt : pt);
2441     switch (tok) {
2442     case T.ifcmd:
2443     case T.switchcmd:
2444       break;
2445     case T.catchcmd:
2446     case T.forcmd:
2447     case T.process:
2448     case T.whilecmd:
2449       if (!isExplicitEnd)
2450         vPush.removeItemAt(--pushCount);
2451       break;
2452     case T.parallel:
2453     case T.function:
2454     case T.trycmd:
2455       if (!isCheckOnly) {
2456         addTokenToPrefix(T.o(tok, thisFunction));
2457         ScriptFunction.setFunction(thisFunction, script, pt1, lltoken.size(),
2458             lineNumbers, lineIndices, lltoken);
2459       }
2460       thisFunction = (vFunctionStack.size() == 0 ? null
2461           : (ScriptFunction) vFunctionStack.removeItemAt(0));
2462       tokenCommand.intValue = 0;
2463       if (tok == T.trycmd)
2464         vPush.removeItemAt(--pushCount);
2465       break;
2466     default:
2467       return errorStr(ERROR_unrecognizedToken, "end " + ident);
2468     }
2469     flowContext = flowContext.parent;
2470     fixFlowAddLine(flowContext);
2471     return true;
2472   }
2473 
fixFlowAddLine(ScriptFlowContext flowContext)2474   private void fixFlowAddLine(ScriptFlowContext flowContext) {
2475     while (flowContext != null) {
2476       if (flowContext.addLine > 0 || flowContext.forceEndIf) {
2477         flowContext.addLine = lineCurrent - flowContext.ptLine;
2478         flowContext.forceEndIf = true;
2479       }
2480       flowContext = flowContext.parent;
2481     }
2482   }
2483 
getData(String key)2484   private boolean getData(String key) {
2485     addTokenToPrefix(T.o(T.string, key));
2486     ichToken += key.length() + 2;
2487     if (charAt(ichToken) == '\r') {
2488       lineCurrent++;
2489       ichToken++;
2490     }
2491     if (charAt(ichToken) == '\n') {
2492       lineCurrent++;
2493       ichToken++;
2494     }
2495     int i = script.indexOf(chFirst + key + chFirst, ichToken) - 4;
2496     if (i < 0 || !script.substring(i, i + 4).equalsIgnoreCase("END "))
2497       return false;
2498     String str = script.substring(ichToken, i);
2499     incrementLineCount(str);
2500     addTokenToPrefix(T.o(T.data, str));
2501     addTokenToPrefix(T.o(T.identifier, "end"));
2502     addTokenToPrefix(T.o(T.string, key));
2503     cchToken = i - ichToken + key.length() + 6;
2504     return true;
2505   }
2506 
incrementLineCount(String str)2507   private int incrementLineCount(String str) {
2508     char ch;
2509     int pt = str.indexOf('\r');
2510     int pt2 = str.indexOf('\n');
2511     if (pt < 0 && pt2 < 0)
2512       return 0;
2513     int n = lineCurrent;
2514     if (pt < 0 || pt2 < pt)
2515       pt = pt2;
2516     for (int i = str.length(); --i >= pt;) {
2517       if ((ch = str.charAt(i)) == '\n' || ch == '\r')
2518         lineCurrent++;
2519     }
2520     return lineCurrent - n;
2521   }
2522 
isSpaceOrTab(char ch)2523   private static boolean isSpaceOrTab(char ch) {
2524     return ch == ' ' || ch == '\t';
2525   }
2526 
2527   /**
2528    *
2529    * look for end-of-line character \r, \n, or ; that is not within a command
2530    * such as for (var i=0;i < 10; i++)
2531    *
2532    * @param ch
2533    * @return true if end of line
2534    */
eol(char ch)2535   private boolean eol(char ch) {
2536     return (ch == '\0' || ch == '\r' || ch == '\n' || ch == ';'
2537         && nSemiSkip <= 0);
2538   }
2539 
2540   /**
2541    *
2542    * look for '{' at the start of a command, allowing for syntaxes {xxx}.yyy =
2543    * ... or {xxx}[yy] = ...
2544    *
2545    * @return true only if found
2546    */
lookingAtSetBraceSyntax()2547   private boolean lookingAtSetBraceSyntax() {
2548     int ichT = ichToken;
2549     int nParen = 1;
2550     while (++ichT < cchScript && nParen > 0) {
2551       switch (script.charAt(ichT)) {
2552       case '{':
2553         nParen++;
2554         break;
2555       case '}':
2556         nParen--;
2557         break;
2558       }
2559     }
2560     if (charAt(ichT) == '[' && ++nParen == 1)
2561       while (++ichT < cchScript && nParen > 0) {
2562         switch (script.charAt(ichT)) {
2563         case '[':
2564           nParen++;
2565           break;
2566         case ']':
2567           if (charAt(ichT + 1) == '[')
2568             ichT++;
2569           else
2570             nParen--;
2571           break;
2572         }
2573       }
2574     if (charAt(ichT) == '.' && nParen == 0) {
2575       return true;
2576     }
2577 
2578     return false;
2579   }
2580 
2581   private char chFirst = '\0';
2582   private int afterMath;
2583 
2584   /**
2585    * look for a quoted string, possibly allowing single quotes.
2586    *
2587    * @param allowPrime
2588    *        cd, echo, gotocmd, help, hover, javascript, label, message, and
2589    *        pause all are implicitly strings. You CAN use "..." but you don't
2590    *        have to, and you cannot use '...'. This way the introduction of
2591    *        single quotes as an equivalent of double quotes cannot break
2592    *        existing scripts. -- BH 06/2009
2593    * @return true only if found
2594    *
2595    */
lookingAtString(boolean allowPrime)2596   private boolean lookingAtString(boolean allowPrime) {
2597     if (ichToken + 2 > cchScript)
2598       return false;
2599     chFirst = script.charAt(ichToken);
2600     if (chFirst != '"' && (!allowPrime || chFirst != '\''))
2601       return false;
2602     int ichT = ichToken;
2603     char ch;
2604     boolean previousCharBackslash = false;
2605     while (++ichT < cchScript) {
2606       ch = script.charAt(ichT);
2607       if (ch == chFirst && !previousCharBackslash)
2608         break;
2609       previousCharBackslash = (ch == '\\' ? !previousCharBackslash : false);
2610     }
2611     if (ichT == cchScript) {
2612       cchToken = -1;
2613       ichEnd = cchScript;
2614     } else {
2615       cchToken = ++ichT - ichToken;
2616     }
2617     return true;
2618   }
2619 
2620   /**
2621    * lookingAtString returned true, and we need to unescape any t, r, n, ", ',
2622    * x, u, or backslash after a backslash
2623    *
2624    * @param isFileName
2625    *        in certain cases, such as load "c:\temp\myfile.xyz" we only want to
2626    *        decode unicode, not other characters.
2627    *
2628    * @return quoted string
2629    *
2630    */
getUnescapedStringLiteral(boolean isFileName)2631   private String getUnescapedStringLiteral(boolean isFileName) {
2632     if (isFileName) {
2633       String s = script.substring(ichToken + 1, ichToken + cchToken - 1);
2634       if (s.indexOf("\\u") >= 0)
2635         s = Escape.unescapeUnicode(s);
2636       if (s.indexOf(";base64,") != 0)
2637         return s;
2638     }
2639     return unescapeString(script, ichToken + 1, cchToken - 2);
2640   }
2641 
unescapeString(String script, int ich, int nChar)2642   public static String unescapeString(String script, int ich, int nChar) {
2643     SB sb = SB.newN(nChar);
2644     int ichMax = ich + nChar;
2645     while (ich < ichMax) {
2646       char ch = script.charAt(ich++);
2647       if (ch == '\\' && ich < ichMax) {
2648         ch = script.charAt(ich++);
2649         switch (ch) {
2650         case 'n':
2651           ch = '\n';
2652           break;
2653         case 't':
2654           ch = '\t';
2655           break;
2656         case 'r':
2657           ch = '\r';
2658           //$FALL-THROUGH$
2659         case '"':
2660         case '\\':
2661         case '\'':
2662           break;
2663         case 'x':
2664         case 'u':
2665           int digitCount = ch == 'x' ? 2 : 4;
2666           if (ich < ichMax) {
2667             int unicode = 0;
2668             for (int k = digitCount; --k >= 0 && ich < ichMax;) {
2669               char chT = script.charAt(ich);
2670               int hexit = Escape.getHexitValue(chT);
2671               if (hexit < 0)
2672                 break;
2673               unicode <<= 4;
2674               unicode += hexit;
2675               ++ich;
2676             }
2677             ch = (char) unicode;
2678           }
2679         }
2680       }
2681       sb.appendC(ch);
2682     }
2683     return sb.toString();
2684   }
2685 
lookingAtLoadFormat(boolean allchar)2686   private boolean lookingAtLoadFormat(boolean allchar) {
2687     // just allow a simple word or =xxxx or $CCCC
2688     // old load formats are simple unneeded words like PDB or XYZ -- no numbers
2689     int ichT = ichToken;
2690     char ch;
2691     while ((PT.isLetterOrDigit(ch = charAt(ichT))
2692         && (allchar || PT.isLetter(ch)) || allchar
2693         && (!eol(ch) && !PT.isWhitespace(ch))))
2694       ++ichT;
2695     if (!allchar && ichT == ichToken || !isSpaceOrTab(ch))
2696       return false;
2697     cchToken = ichT - ichToken;
2698     return true;
2699   }
2700 
2701   /**
2702    * An "implied string" is a parameter that is not quoted but because of its
2703    * position in a command is implied to be a string. First we must exclude the
2704    *
2705    * @xxxx. Then we consume the entire math syntax @{......} or any set of
2706    *        characters not involving white space. echo, hover, message, and
2707    *        pause are odd-valued; no initial parsing of variables for them.
2708    *
2709    * @param allowSpace
2710    *        as in commands such as echo
2711    * @param allowEquals
2712    *        as in the load command, first parameter load =xxx but NOT any other
2713    *        command
2714    * @param allowSptParen
2715    *        specifically for script/load command, first parameter xxx.spt(3,4,4)
2716    *
2717    * @return true or false
2718    */
lookingAtImpliedString(boolean allowSpace, boolean allowEquals, boolean allowSptParen)2719   private boolean lookingAtImpliedString(boolean allowSpace,
2720                                          boolean allowEquals,
2721                                          boolean allowSptParen) {
2722     int ichT = ichToken;
2723     char ch = script.charAt(ichT);
2724     boolean isID = (lastToken.tok == T.id);
2725     boolean passVariableToString = (T.tokAttr(tokCommand,
2726         T.implicitStringCommand) && (tokCommand & 1) == 1);
2727     boolean isVariable = (ch == '@');
2728     boolean isMath = (isVariable && ichT + 3 < cchScript && script
2729         .charAt(ichT + 1) == '{');
2730     if (isMath && (isID || !passVariableToString))
2731       return false;
2732     //    if (isMath && passVariableToString) {
2733     //      // zip past math expression untested.
2734     //      ichT = Txt.ichMathTerminator(script, ichToken + 1, cchScript);
2735     //      return (!isID && ichT != cchScript && (cchToken = ichT + 1 - ichToken) > 0);
2736     //    }
2737     //    if (isMath && !passVariableToString)
2738     //      return false;
2739     // check implicit string for math expression here
2740     int ptSpace = -1;
2741     int ptLastChar = -1;
2742     // look ahead to \n, \r, terminal ;, or }
2743     boolean isOK = true;
2744     int parenpt = 0;
2745     while (isOK && !eol(ch = charAt(ichT))) {
2746       switch (ch) {
2747       case '(':
2748         if (!allowSptParen) {
2749           // script command
2750           if(tokCommand == T.macro || ichT >= 5
2751               && (script.substring(ichT - 4, ichT).equals(".spt")
2752                   || script.substring(ichT - 4, ichT).equals(".png") || script
2753                   .substring(ichT - 5, ichT).equals(".pngj"))) {
2754             isOK = false;
2755             continue;
2756           }
2757         }
2758         break;
2759       case '=':
2760         if (!allowEquals) {
2761           isOK = false;
2762           continue;
2763         }
2764         break;
2765       case '{':
2766         parenpt++;
2767         break;
2768       case '}':
2769         // fail if improperly nested
2770         parenpt--;
2771         if (parenpt < 0 && (braceCount > 0 || iBrace > 0)) {
2772           isOK = false;
2773           continue;
2774         }
2775         //$FALL-THROUGH$
2776       default:
2777         if (PT.isWhitespace(ch)) {
2778           if (ptSpace < 0)
2779             ptSpace = ichT;
2780         } else {
2781           ptLastChar = ichT;
2782         }
2783         break;
2784       }
2785       ++ichT;
2786     }
2787     // message/echo/label @x
2788     // message/echo/label @{.....}
2789     // message/echo/label @{....} testing  NOT ok
2790     // message/echo/label @x bananas OK -- "@x bananas"
2791     // {... message/echo label ok }
2792     if (allowSpace)
2793       ichT = ptLastChar + 1;
2794     else if (ptSpace > 0)
2795       ichT = ptSpace;
2796     if (isVariable
2797         && (!allowSpace || ptSpace < 0 && parenpt <= 0 && ichT - ichToken > 1)) {
2798       // if we have @xxx then this is not an implied string
2799       return false;
2800     }
2801     return (cchToken = ichT - ichToken) > 0;
2802   }
2803 
lookingAtExponential()2804   private float lookingAtExponential() {
2805     if (ichToken == cchScript)
2806       return Float.NaN; //end
2807     int ichT = ichToken;
2808     int pt0 = ichT;
2809     if (script.charAt(ichT) == '-')
2810       ++ichT;
2811     boolean isOK = false;
2812     char ch = 'X';
2813     while (PT.isDigit(ch = charAt(ichT))) {
2814       ++ichT;
2815       isOK = true;
2816     }
2817     if (ichT < cchScript && ch == '.')
2818       ++ichT;
2819     while (PT.isDigit(ch = charAt(ichT))) {
2820       ++ichT;
2821       isOK = true;
2822     }
2823     if (ichT == cchScript || !isOK)
2824       return Float.NaN; //integer
2825     isOK = (ch != 'E' && ch != 'e');
2826     if (isOK || ++ichT == cchScript)
2827       return Float.NaN;
2828     ch = script.charAt(ichT);
2829     // I THOUGHT we only should allow "E+" or "E-" here, not "2E1" because
2830     // "2E1" might be a PDB het group by that name. BUT it turns out that
2831     // any HET group starting with a number is unacceptable and must
2832     // be given as [nXm], in brackets.
2833 
2834     if (ch == '-' || ch == '+')
2835       ichT++;
2836     while (PT.isDigit(charAt(ichT))) {
2837       ichT++;
2838       isOK = true;
2839     }
2840     if (!isOK)
2841       return Float.NaN;
2842     cchToken = ichT - ichToken;
2843     return (float) PT.dVal(script.substring(pt0, ichT));
2844   }
2845 
lookingAtDecimal()2846   private boolean lookingAtDecimal() {
2847     if (ichToken == cchScript)
2848       return false;
2849     int ichT = ichToken;
2850     if (script.charAt(ichT) == '-')
2851       ++ichT;
2852     boolean digitSeen = false;
2853     char ch;
2854     while (PT.isDigit(ch = charAt(ichT++)))
2855       digitSeen = true;
2856     if (ch != '.')
2857       return false;
2858     // only here if  "dddd."
2859 
2860     // to support 1.ca, let's check the character after the dot
2861     // to determine if it is an alpha
2862     char ch1 = charAt(ichT);
2863     if (!isSpaceOrTab(ch1) && !eol(ch1)) {
2864       if (PT.isLetter(ch1) || ch1 == '?' || ch1 == '*' || ch1 == '_')
2865         return false;
2866       //well, guess what? we also have to look for 86.1Na, so...
2867       //watch out for moveto..... 56.;refresh...
2868       if (PT.isLetter(ch1 = charAt(ichT + 1)) || ch1 == '?')
2869         return false;
2870     }
2871     while (PT.isDigit(charAt(ichT))) {
2872       ++ichT;
2873       digitSeen = true;
2874     }
2875     cchToken = ichT - ichToken;
2876     return digitSeen;
2877   }
2878 
lookingAtSeqcode()2879   private boolean lookingAtSeqcode() {
2880     int ichT = ichToken;
2881     char ch;
2882     if (charAt(ichT + 1) == '^' && script.charAt(ichT) == '*') {
2883       ch = '^';
2884       ++ichT;
2885     } else {
2886       if (script.charAt(ichT) == '-')
2887         ++ichT;
2888       while (PT.isDigit(ch = charAt(ichT)))
2889         ++ichT;
2890     }
2891     if (ch != '^')
2892       return false;
2893     ichT++;
2894     if (ichT == cchScript)
2895       ch = ' ';
2896     else
2897       ch = script.charAt(ichT++);
2898     if (ch != ' ' && ch != '*' && ch != '?' && !PT.isLetter(ch))
2899       return false;
2900     cchToken = ichT - ichToken;
2901     return true;
2902   }
2903 
lookingAtInteger()2904   private int lookingAtInteger() {
2905     if (ichToken == cchScript)
2906       return Integer.MAX_VALUE;
2907     int ichT = ichToken;
2908     if (script.charAt(ichToken) == '-')
2909       ++ichT;
2910     int ichBeginDigits = ichT;
2911     while (PT.isDigit(charAt(ichT)))
2912       ++ichT;
2913     if (ichBeginDigits == ichT)
2914       return Integer.MAX_VALUE;
2915     cchToken = ichT - ichToken;
2916     try {
2917       int val = Integer.parseInt(ident = script.substring(ichToken, ichT));
2918       return val;
2919     } catch (NumberFormatException e) {
2920       // ignore
2921     }
2922     return Integer.MAX_VALUE;
2923   }
2924 
lookingAtBitset()2925   BS lookingAtBitset() {
2926     // ({n n:m n}) or ({null})
2927     // [{n:m}] is a BOND bitset
2928     // EXCEPT if the previous token was a function:
2929     // {carbon}.distance({3 3 3})
2930     // Yes, I wish I had used {{...}}, but this will work.
2931     // WITHIN ({....}) unfortunately has two contexts
2932 
2933     if (script.indexOf("({null})", ichToken) == ichToken) {
2934       cchToken = 8;
2935       return new BS();
2936     }
2937     int ichT;
2938     if (ichToken + 4 > cchScript || script.charAt(ichToken + 1) != '{'
2939         || (ichT = script.indexOf("}", ichToken)) < 0 || ichT + 1 == cchScript)
2940       return null;
2941     BS bs = BS.unescape(script.substring(ichToken, ichT + 2));
2942     if (bs != null)
2943       cchToken = ichT + 2 - ichToken;
2944     return bs;
2945   }
2946 
2947   /**
2948    *
2949    * Look for a valid $... sequence. This must be alphanumeric or _ or ~ only.
2950    * We skip any $"...". That will be handled later.
2951    *
2952    *
2953    * @return true only if valid $....
2954    */
lookingAtObjectID()2955   private boolean lookingAtObjectID() {
2956     boolean allowWildID = (nTokens == 1);
2957     int ichT = ichToken;
2958     if (charAt(ichT) != '$')
2959       return false;
2960     if (charAt(++ichT) == '"')
2961       return false;
2962     while (ichT < cchScript) {
2963       char ch;
2964       if (PT.isWhitespace(ch = script.charAt(ichT))) {
2965         if (ichT == ichToken + 1)
2966           return false;
2967         break;
2968       }
2969       if (!PT.isLetterOrDigit(ch)) {
2970         switch (ch) {
2971         default:
2972           return false;
2973         case '*':
2974           if (!allowWildID)
2975             return false;
2976           break;
2977         case '~':
2978         case '_':
2979           break;
2980         }
2981       }
2982       ichT++;
2983     }
2984     cchToken = ichT - (++ichToken);
2985     return true;
2986   }
2987 
lookingAtLookupToken(int ichT)2988   private boolean lookingAtLookupToken(int ichT) {
2989     if (ichT == cchScript)
2990       return false;
2991     int ichT0 = ichT;
2992     afterMath = (tokLastMath != 0 ? ichT : 0);
2993     tokLastMath = 0;
2994     char ch;
2995     switch (ch = script.charAt(ichT++)) {
2996     case '-':
2997     case '+':
2998     case '&':
2999     case '|':
3000     case '*':
3001       if (ichT < cchScript) {
3002         if (script.charAt(ichT) == ch) {
3003           ++ichT;
3004           if (ch == '-' || ch == '+')
3005             break;
3006           if (ch == '&' && charAt(ichT) == ch)
3007             ++ichT; // &&&
3008         } else if (script.charAt(ichT) == '=') {
3009           ++ichT;
3010         }
3011       }
3012       tokLastMath = 1;
3013       break;
3014     case '/':
3015       if (charAt(ichT) == '/')
3016         break;
3017       //$FALL-THROUGH$
3018     case '\\': // leftdivide
3019     case '!':
3020       if (charAt(ichT) == '=')
3021         ++ichT;
3022       tokLastMath = 1;
3023       break;
3024     case ')':
3025     case ']':
3026     case '}':
3027       break;
3028     case '.':
3029       if (charAt(ichT) == '.')
3030         ++ichT;
3031       tokLastMath = 1;
3032       break;
3033     case '@':
3034     case '{':
3035       tokLastMath = 2; // NOT considered a continuation if at beginning of a line
3036       break;
3037     case ':':
3038       tokLastMath = 1;
3039       break;
3040     case '(':
3041     case ',':
3042     case '$':
3043     case ';':
3044     case '[':
3045     case '%':
3046       tokLastMath = 1;
3047       break;
3048     case '<':
3049     case '=':
3050     case '>':
3051       if ((ch = charAt(ichT)) == '<' || ch == '=' || ch == '>')
3052         ++ichT;
3053       tokLastMath = 1;
3054       break;
3055     default:
3056       if (!PT.isLetter(ch) && !isDotDot)
3057         return false;
3058       //$FALL-THROUGH$
3059     case '~':
3060     case '_':
3061     case '\'':
3062     case '?': // include question marks in identifier for atom expressions
3063       if (ch == '?')
3064         tokLastMath = 1;
3065       // last is hack for insertion codes embedded in an atom expression :-(
3066       // select c3^a
3067       while (PT.isLetterOrDigit(ch = charAt(ichT)) || ch == '_' || ch == '*'
3068           && charAt(ichT - 1) == '?' || ch == '?' || ch == '~' || ch == '\''
3069           || ch == '\\' && charAt(ichT + 1) == '?' || ch == '^' && ichT > ichT0
3070           && PT.isDigit(charAt(ichT - 1)))
3071         ++ichT;
3072       break;
3073     }
3074     cchToken = ichT - ichT0;
3075     return true;
3076   }
3077 
3078   /**
3079    * Check for a set of characters that does not start with double quote or
3080    * at-sign and terminates with #, }, or an end of line. Only used for the SYNC
3081    * command's second character.
3082    *
3083    * @return true if ID is found.
3084    */
lookForSyncID()3085   private boolean lookForSyncID() {
3086     char ch;
3087     if ((ch = charAt(ichToken)) == '"' || ch == '@' || ch == '\0')
3088       return false;
3089     int ichT = ichToken;
3090     while (!isSpaceOrTab(ch = charAt(ichT)) && ch != '#' && ch != '}'
3091         && !eol(ch))
3092       ++ichT;
3093     cchToken = ichT - ichToken;
3094     return true;
3095   }
3096 
ERROR(int error)3097   private int ERROR(int error) {
3098     errorIntStr2(error, null, null);
3099     return ERROR;
3100   }
3101 
ERROR(int error, String value)3102   private int ERROR(int error, String value) {
3103     errorStr(error, value);
3104     return ERROR;
3105   }
3106 
handleError()3107   private boolean handleError() {
3108     errorType = errorMessage;
3109     errorLine = script.substring(ichCurrentCommand,
3110         ichEnd <= ichCurrentCommand ? ichToken + cchToken : ichEnd);
3111     String lineInfo = (ichToken < ichEnd ? errorLine.substring(0, ichToken
3112         - ichCurrentCommand)
3113         + " >>>> " + errorLine.substring(ichToken - ichCurrentCommand)
3114         : errorLine)
3115         + " <<<<";
3116     errorMessage = GT.$("script compiler ERROR: ")
3117         + errorMessage
3118         + ScriptError.getErrorLineMessage(null, filename, lineCurrent,
3119             iCommand, lineInfo);
3120     if (!isSilent) {
3121       ichToken = Math.max(ichEnd, ichToken);
3122       while (!lookingAtEndOfLine() && !lookingAtTerminator())
3123         ichToken++;
3124       errorLine = script.substring(ichCurrentCommand, ichToken);
3125       vwr.addCommand(errorLine + CommandHistory.ERROR_FLAG);
3126       Logger.error(errorMessage);
3127     }
3128     return false;
3129   }
3130 
3131 }
3132