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