1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.parser; 27 28 import static jdk.nashorn.internal.parser.TokenType.ADD; 29 import static jdk.nashorn.internal.parser.TokenType.COMMENT; 30 import static jdk.nashorn.internal.parser.TokenType.DIRECTIVE_COMMENT; 31 import static jdk.nashorn.internal.parser.TokenType.DECIMAL; 32 import static jdk.nashorn.internal.parser.TokenType.EOF; 33 import static jdk.nashorn.internal.parser.TokenType.EOL; 34 import static jdk.nashorn.internal.parser.TokenType.ERROR; 35 import static jdk.nashorn.internal.parser.TokenType.ESCSTRING; 36 import static jdk.nashorn.internal.parser.TokenType.EXECSTRING; 37 import static jdk.nashorn.internal.parser.TokenType.FLOATING; 38 import static jdk.nashorn.internal.parser.TokenType.HEXADECIMAL; 39 import static jdk.nashorn.internal.parser.TokenType.LBRACE; 40 import static jdk.nashorn.internal.parser.TokenType.LPAREN; 41 import static jdk.nashorn.internal.parser.TokenType.OCTAL; 42 import static jdk.nashorn.internal.parser.TokenType.RBRACE; 43 import static jdk.nashorn.internal.parser.TokenType.REGEX; 44 import static jdk.nashorn.internal.parser.TokenType.RPAREN; 45 import static jdk.nashorn.internal.parser.TokenType.STRING; 46 import static jdk.nashorn.internal.parser.TokenType.XML; 47 48 import jdk.nashorn.internal.runtime.ECMAErrors; 49 import jdk.nashorn.internal.runtime.ErrorManager; 50 import jdk.nashorn.internal.runtime.JSErrorType; 51 import jdk.nashorn.internal.runtime.ParserException; 52 import jdk.nashorn.internal.runtime.Source; 53 import jdk.nashorn.internal.runtime.options.Options; 54 55 /** 56 * Responsible for converting source content into a stream of tokens. 57 * 58 */ 59 @SuppressWarnings("fallthrough") 60 public class Lexer extends Scanner { 61 private static final long MIN_INT_L = Integer.MIN_VALUE; 62 private static final long MAX_INT_L = Integer.MAX_VALUE; 63 64 private static final boolean XML_LITERALS = Options.getBooleanProperty("nashorn.lexer.xmlliterals"); 65 66 /** Content source. */ 67 private final Source source; 68 69 /** Buffered stream for tokens. */ 70 private final TokenStream stream; 71 72 /** True if here and edit strings are supported. */ 73 private final boolean scripting; 74 75 /** True if a nested scan. (scan to completion, no EOF.) */ 76 private final boolean nested; 77 78 /** Pending new line number and position. */ 79 private int pendingLine; 80 81 /** Position of last EOL + 1. */ 82 private int linePosition; 83 84 /** Type of last token added. */ 85 private TokenType last; 86 87 private static final String SPACETAB = " \t"; // ASCII space and tab 88 private static final String LFCR = "\n\r"; // line feed and carriage return (ctrl-m) 89 90 private static final String JSON_WHITESPACE_EOL = LFCR; 91 private static final String JSON_WHITESPACE = SPACETAB + LFCR; 92 93 private static final String JAVASCRIPT_WHITESPACE_EOL = 94 LFCR + 95 "\u2028" + // line separator 96 "\u2029" // paragraph separator 97 ; 98 private static final String JAVASCRIPT_WHITESPACE = 99 SPACETAB + 100 JAVASCRIPT_WHITESPACE_EOL + 101 "\u000b" + // tabulation line 102 "\u000c" + // ff (ctrl-l) 103 "\u00a0" + // Latin-1 space 104 "\u1680" + // Ogham space mark 105 "\u180e" + // separator, Mongolian vowel 106 "\u2000" + // en quad 107 "\u2001" + // em quad 108 "\u2002" + // en space 109 "\u2003" + // em space 110 "\u2004" + // three-per-em space 111 "\u2005" + // four-per-em space 112 "\u2006" + // six-per-em space 113 "\u2007" + // figure space 114 "\u2008" + // punctuation space 115 "\u2009" + // thin space 116 "\u200a" + // hair space 117 "\u202f" + // narrow no-break space 118 "\u205f" + // medium mathematical space 119 "\u3000" + // ideographic space 120 "\ufeff" // byte order mark 121 ; 122 123 private static final String JAVASCRIPT_WHITESPACE_IN_REGEXP = 124 "\\u000a" + // line feed 125 "\\u000d" + // carriage return (ctrl-m) 126 "\\u2028" + // line separator 127 "\\u2029" + // paragraph separator 128 "\\u0009" + // tab 129 "\\u0020" + // ASCII space 130 "\\u000b" + // tabulation line 131 "\\u000c" + // ff (ctrl-l) 132 "\\u00a0" + // Latin-1 space 133 "\\u1680" + // Ogham space mark 134 "\\u180e" + // separator, Mongolian vowel 135 "\\u2000" + // en quad 136 "\\u2001" + // em quad 137 "\\u2002" + // en space 138 "\\u2003" + // em space 139 "\\u2004" + // three-per-em space 140 "\\u2005" + // four-per-em space 141 "\\u2006" + // six-per-em space 142 "\\u2007" + // figure space 143 "\\u2008" + // punctuation space 144 "\\u2009" + // thin space 145 "\\u200a" + // hair space 146 "\\u202f" + // narrow no-break space 147 "\\u205f" + // medium mathematical space 148 "\\u3000" + // ideographic space 149 "\\ufeff" // byte order mark 150 ; 151 152 static String unicodeEscape(final char ch) { 153 final StringBuilder sb = new StringBuilder(); 154 155 sb.append("\\u"); 156 157 final String hex = Integer.toHexString(ch); 158 for (int i = hex.length(); i < 4; i++) { 159 sb.append('0'); 160 } 161 sb.append(hex); 162 163 return sb.toString(); 164 } 165 166 /** 167 * Constructor 168 * 169 * @param source the source 170 * @param stream the token stream to lex 171 */ 172 public Lexer(final Source source, final TokenStream stream) { 173 this(source, stream, false); 174 } 175 176 /** 177 * Constructor 178 * 179 * @param source the source 180 * @param stream the token stream to lex 181 * @param scripting are we in scripting mode 182 */ 183 public Lexer(final Source source, final TokenStream stream, final boolean scripting) { 184 super(source.getContent(), 1, 0, source.getLength()); 185 186 this.source = source; 187 this.stream = stream; 188 this.scripting = scripting; 189 this.nested = false; 190 this.pendingLine = 1; 191 this.last = EOL; 192 } 193 194 private Lexer(final Lexer lexer, final State state) { 195 super(lexer, state); 196 197 source = lexer.source; 198 stream = lexer.stream; 199 scripting = lexer.scripting; 200 nested = true; 201 202 pendingLine = state.pendingLine; 203 linePosition = state.linePosition; 204 last = EOL; 205 } 206 207 static class State extends Scanner.State { 208 /** Pending new line number and position. */ 209 public final int pendingLine; 210 211 /** Position of last EOL + 1. */ 212 public final int linePosition; 213 214 /** Type of last token added. */ 215 public final TokenType last; 216 217 /* 218 * Constructor. 219 */ 220 221 State(final int position, final int limit, final int line, final int pendingLine, final int linePosition, final TokenType last) { 222 super(position, limit, line); 223 224 this.pendingLine = pendingLine; 225 this.linePosition = linePosition; 226 this.last = last; 227 } 228 } 229 230 /** 231 * Save the state of the scan. 232 * 233 * @return Captured state. 234 */ 235 @Override 236 State saveState() { 237 return new State(position, limit, line, pendingLine, linePosition, last); 238 } 239 240 /** 241 * Restore the state of the scan. 242 * 243 * @param state 244 * Captured state. 245 */ 246 void restoreState(final State state) { 247 super.restoreState(state); 248 249 pendingLine = state.pendingLine; 250 linePosition = state.linePosition; 251 last = state.last; 252 } 253 254 /** 255 * Add a new token to the stream. 256 * 257 * @param type 258 * Token type. 259 * @param start 260 * Start position. 261 * @param end 262 * End position. 263 */ 264 protected void add(final TokenType type, final int start, final int end) { 265 // Record last token. 266 last = type; 267 268 // Only emit the last EOL in a cluster. 269 if (type == EOL) { 270 pendingLine = end; 271 linePosition = start; 272 } else { 273 // Write any pending EOL to stream. 274 if (pendingLine != -1) { 275 stream.put(Token.toDesc(EOL, linePosition, pendingLine)); 276 pendingLine = -1; 277 } 278 279 // Write token to stream. 280 stream.put(Token.toDesc(type, start, end - start)); 281 } 282 } 283 284 /** 285 * Add a new token to the stream. 286 * 287 * @param type 288 * Token type. 289 * @param start 290 * Start position. 291 */ 292 protected void add(final TokenType type, final int start) { 293 add(type, start, position); 294 } 295 296 /** 297 * Return the String of valid whitespace characters for regular 298 * expressions in JavaScript 299 * @return regexp whitespace string 300 */ 301 public static String getWhitespaceRegExp() { 302 return JAVASCRIPT_WHITESPACE_IN_REGEXP; 303 } 304 305 /** 306 * Skip end of line. 307 * 308 * @param addEOL true if EOL token should be recorded. 309 */ 310 private void skipEOL(final boolean addEOL) { 311 312 if (ch0 == '\r') { // detect \r\n pattern 313 skip(1); 314 if (ch0 == '\n') { 315 skip(1); 316 } 317 } else { // all other space, ch0 is guaranteed to be EOL or \0 318 skip(1); 319 } 320 321 // bump up line count 322 line++; 323 324 if (addEOL) { 325 // Add an EOL token. 326 add(EOL, position, line); 327 } 328 } 329 330 /** 331 * Skip over rest of line including end of line. 332 * 333 * @param addEOL true if EOL token should be recorded. 334 */ 335 private void skipLine(final boolean addEOL) { 336 // Ignore characters. 337 while (!isEOL(ch0) && !atEOF()) { 338 skip(1); 339 } 340 // Skip over end of line. 341 skipEOL(addEOL); 342 } 343 344 /** 345 * Test whether a char is valid JavaScript whitespace 346 * @param ch a char 347 * @return true if valid JavaScript whitespace 348 */ 349 public static boolean isJSWhitespace(final char ch) { 350 return JAVASCRIPT_WHITESPACE.indexOf(ch) != -1; 351 } 352 353 /** 354 * Test whether a char is valid JavaScript end of line 355 * @param ch a char 356 * @return true if valid JavaScript end of line 357 */ 358 public static boolean isJSEOL(final char ch) { 359 return JAVASCRIPT_WHITESPACE_EOL.indexOf(ch) != -1; 360 } 361 362 /** 363 * Test whether a char is valid JSON whitespace 364 * @param ch a char 365 * @return true if valid JSON whitespace 366 */ 367 public static boolean isJsonWhitespace(final char ch) { 368 return JSON_WHITESPACE.indexOf(ch) != -1; 369 } 370 371 /** 372 * Test whether a char is valid JSON end of line 373 * @param ch a char 374 * @return true if valid JSON end of line 375 */ 376 public static boolean isJsonEOL(final char ch) { 377 return JSON_WHITESPACE_EOL.indexOf(ch) != -1; 378 } 379 380 /** 381 * Test if char is a string delimiter, e.g. '\' or '"'. Also scans exec 382 * strings ('`') in scripting mode. 383 * @param ch a char 384 * @return true if string delimiter 385 */ 386 protected boolean isStringDelimiter(final char ch) { 387 return ch == '\'' || ch == '"' || (scripting && ch == '`'); 388 } 389 390 /** 391 * Test whether a char is valid JavaScript whitespace 392 * @param ch a char 393 * @return true if valid JavaScript whitespace 394 */ 395 protected boolean isWhitespace(final char ch) { 396 return Lexer.isJSWhitespace(ch); 397 } 398 399 /** 400 * Test whether a char is valid JavaScript end of line 401 * @param ch a char 402 * @return true if valid JavaScript end of line 403 */ 404 protected boolean isEOL(final char ch) { 405 return Lexer.isJSEOL(ch); 406 } 407 408 /** 409 * Skip over whitespace and detect end of line, adding EOL tokens if 410 * encountered. 411 * 412 * @param addEOL true if EOL tokens should be recorded. 413 */ 414 private void skipWhitespace(final boolean addEOL) { 415 while (isWhitespace(ch0)) { 416 if (isEOL(ch0)) { 417 skipEOL(addEOL); 418 } else { 419 skip(1); 420 } 421 } 422 } 423 424 /** 425 * Skip over comments. 426 * 427 * @return True if a comment. 428 */ 429 protected boolean skipComments() { 430 // Save the current position. 431 final int start = position; 432 433 if (ch0 == '/') { 434 // Is it a // comment. 435 if (ch1 == '/') { 436 // Skip over //. 437 skip(2); 438 439 boolean directiveComment = false; 440 if ((ch0 == '#' || ch0 == '@') && (ch1 == ' ')) { 441 directiveComment = true; 442 } 443 444 // Scan for EOL. 445 while (!atEOF() && !isEOL(ch0)) { 446 skip(1); 447 } 448 // Did detect a comment. 449 add(directiveComment? DIRECTIVE_COMMENT : COMMENT, start); 450 return true; 451 } else if (ch1 == '*') { 452 // Skip over /*. 453 skip(2); 454 // Scan for */. 455 while (!atEOF() && !(ch0 == '*' && ch1 == '/')) { 456 // If end of line handle else skip character. 457 if (isEOL(ch0)) { 458 skipEOL(true); 459 } else { 460 skip(1); 461 } 462 } 463 464 if (atEOF()) { 465 // TODO - Report closing */ missing in parser. 466 add(ERROR, start); 467 } else { 468 // Skip */. 469 skip(2); 470 } 471 472 // Did detect a comment. 473 add(COMMENT, start); 474 return true; 475 } 476 } else if (ch0 == '#') { 477 assert scripting; 478 // shell style comment 479 // Skip over #. 480 skip(1); 481 // Scan for EOL. 482 while (!atEOF() && !isEOL(ch0)) { 483 skip(1); 484 } 485 // Did detect a comment. 486 add(COMMENT, start); 487 return true; 488 } 489 490 // Not a comment. 491 return false; 492 } 493 494 /** 495 * Convert a regex token to a token object. 496 * 497 * @param start Position in source content. 498 * @param length Length of regex token. 499 * @return Regex token object. 500 */ 501 public RegexToken valueOfPattern(final int start, final int length) { 502 // Save the current position. 503 final int savePosition = position; 504 // Reset to beginning of content. 505 reset(start); 506 // Buffer for recording characters. 507 final StringBuilder sb = new StringBuilder(length); 508 509 // Skip /. 510 skip(1); 511 boolean inBrackets = false; 512 // Scan for closing /, stopping at end of line. 513 while (!atEOF() && ch0 != '/' && !isEOL(ch0) || inBrackets) { 514 // Skip over escaped character. 515 if (ch0 == '\\') { 516 sb.append(ch0); 517 sb.append(ch1); 518 skip(2); 519 } else { 520 if (ch0 == '[') { 521 inBrackets = true; 522 } else if (ch0 == ']') { 523 inBrackets = false; 524 } 525 526 // Skip literal character. 527 sb.append(ch0); 528 skip(1); 529 } 530 } 531 532 // Get pattern as string. 533 final String regex = sb.toString(); 534 535 // Skip /. 536 skip(1); 537 538 // Options as string. 539 final String options = source.getString(position, scanIdentifier()); 540 541 reset(savePosition); 542 543 // Compile the pattern. 544 return new RegexToken(regex, options); 545 } 546 547 /** 548 * Return true if the given token can be the beginning of a literal. 549 * 550 * @param token a token 551 * @return true if token can start a literal. 552 */ 553 public boolean canStartLiteral(final TokenType token) { 554 return token.startsWith('/') || ((scripting || XML_LITERALS) && token.startsWith('<')); 555 } 556 557 /** 558 * interface to receive line information for multi-line literals. 559 */ 560 protected interface LineInfoReceiver { 561 /** 562 * Receives line information 563 * @param line last line number 564 * @param linePosition position of last line 565 */ 566 public void lineInfo(int line, int linePosition); 567 } 568 569 /** 570 * Check whether the given token represents the beginning of a literal. If so scan 571 * the literal and return <tt>true</tt>, otherwise return false. 572 * 573 * @param token the token. 574 * @param startTokenType the token type. 575 * @param lir LineInfoReceiver that receives line info for multi-line string literals. 576 * @return True if a literal beginning with startToken was found and scanned. 577 */ 578 protected boolean scanLiteral(final long token, final TokenType startTokenType, final LineInfoReceiver lir) { 579 // Check if it can be a literal. 580 if (!canStartLiteral(startTokenType)) { 581 return false; 582 } 583 // We break on ambiguous tokens so if we already moved on it can't be a literal. 584 if (stream.get(stream.last()) != token) { 585 return false; 586 } 587 // Rewind to token start position 588 reset(Token.descPosition(token)); 589 590 if (ch0 == '/') { 591 return scanRegEx(); 592 } else if (ch0 == '<') { 593 if (ch1 == '<') { 594 return scanHereString(lir); 595 } else if (Character.isJavaIdentifierStart(ch1)) { 596 return scanXMLLiteral(); 597 } 598 } 599 600 return false; 601 } 602 603 /** 604 * Scan over regex literal. 605 * 606 * @return True if a regex literal. 607 */ 608 private boolean scanRegEx() { 609 assert ch0 == '/'; 610 // Make sure it's not a comment. 611 if (ch1 != '/' && ch1 != '*') { 612 // Record beginning of literal. 613 final int start = position; 614 // Skip /. 615 skip(1); 616 boolean inBrackets = false; 617 618 // Scan for closing /, stopping at end of line. 619 while (!atEOF() && (ch0 != '/' || inBrackets) && !isEOL(ch0)) { 620 // Skip over escaped character. 621 if (ch0 == '\\') { 622 skip(1); 623 if (isEOL(ch0)) { 624 reset(start); 625 return false; 626 } 627 skip(1); 628 } else { 629 if (ch0 == '[') { 630 inBrackets = true; 631 } else if (ch0 == ']') { 632 inBrackets = false; 633 } 634 635 // Skip literal character. 636 skip(1); 637 } 638 } 639 640 // If regex literal. 641 if (ch0 == '/') { 642 // Skip /. 643 skip(1); 644 645 // Skip over options. 646 while (!atEOF() && Character.isJavaIdentifierPart(ch0) || ch0 == '\\' && ch1 == 'u') { 647 skip(1); 648 } 649 650 // Add regex token. 651 add(REGEX, start); 652 // Regex literal detected. 653 return true; 654 } 655 656 // False start try again. 657 reset(start); 658 } 659 660 // Regex literal not detected. 661 return false; 662 } 663 664 /** 665 * Convert a digit to a integer. Can't use Character.digit since we are 666 * restricted to ASCII by the spec. 667 * 668 * @param ch Character to convert. 669 * @param base Numeric base. 670 * 671 * @return The converted digit or -1 if invalid. 672 */ 673 protected static int convertDigit(final char ch, final int base) { 674 int digit; 675 676 if ('0' <= ch && ch <= '9') { 677 digit = ch - '0'; 678 } else if ('A' <= ch && ch <= 'Z') { 679 digit = ch - 'A' + 10; 680 } else if ('a' <= ch && ch <= 'z') { 681 digit = ch - 'a' + 10; 682 } else { 683 return -1; 684 } 685 686 return digit < base ? digit : -1; 687 } 688 689 690 /** 691 * Get the value of a hexadecimal numeric sequence. 692 * 693 * @param length Number of digits. 694 * @param type Type of token to report against. 695 * @return Value of sequence or < 0 if no digits. 696 */ 697 private int hexSequence(final int length, final TokenType type) { 698 int value = 0; 699 700 for (int i = 0; i < length; i++) { 701 final int digit = convertDigit(ch0, 16); 702 703 if (digit == -1) { 704 error(Lexer.message("invalid.hex"), type, position, limit); 705 return i == 0 ? -1 : value; 706 } 707 708 value = digit | value << 4; 709 skip(1); 710 } 711 712 return value; 713 } 714 715 /** 716 * Get the value of an octal numeric sequence. This parses up to 3 digits with a maximum value of 255. 717 * 718 * @return Value of sequence. 719 */ 720 private int octalSequence() { 721 int value = 0; 722 723 for (int i = 0; i < 3; i++) { 724 final int digit = convertDigit(ch0, 8); 725 726 if (digit == -1) { 727 break; 728 } 729 value = digit | value << 3; 730 skip(1); 731 732 if (i == 1 && value >= 32) { 733 break; 734 } 735 } 736 return value; 737 } 738 739 /** 740 * Convert a string to a JavaScript identifier. 741 * 742 * @param start Position in source content. 743 * @param length Length of token. 744 * @return Ident string or null if an error. 745 */ 746 private String valueOfIdent(final int start, final int length) throws RuntimeException { 747 // Save the current position. 748 final int savePosition = position; 749 // End of scan. 750 final int end = start + length; 751 // Reset to beginning of content. 752 reset(start); 753 // Buffer for recording characters. 754 final StringBuilder sb = new StringBuilder(length); 755 756 // Scan until end of line or end of file. 757 while (!atEOF() && position < end && !isEOL(ch0)) { 758 // If escape character. 759 if (ch0 == '\\' && ch1 == 'u') { 760 skip(2); 761 final int ch = hexSequence(4, TokenType.IDENT); 762 if (isWhitespace((char)ch)) { 763 return null; 764 } 765 if (ch < 0) { 766 sb.append('\\'); 767 sb.append('u'); 768 } else { 769 sb.append((char)ch); 770 } 771 } else { 772 // Add regular character. 773 sb.append(ch0); 774 skip(1); 775 } 776 } 777 778 // Restore position. 779 reset(savePosition); 780 781 return sb.toString(); 782 } 783 784 /** 785 * Scan over and identifier or keyword. Handles identifiers containing 786 * encoded Unicode chars. 787 * 788 * Example: 789 * 790 * var \u0042 = 44; 791 */ 792 private void scanIdentifierOrKeyword() { 793 // Record beginning of identifier. 794 final int start = position; 795 // Scan identifier. 796 final int length = scanIdentifier(); 797 // Check to see if it is a keyword. 798 final TokenType type = TokenLookup.lookupKeyword(content, start, length); 799 // Add keyword or identifier token. 800 add(type, start); 801 } 802 803 /** 804 * Convert a string to a JavaScript string object. 805 * 806 * @param start Position in source content. 807 * @param length Length of token. 808 * @return JavaScript string object. 809 */ 810 private String valueOfString(final int start, final int length, final boolean strict) throws RuntimeException { 811 // Save the current position. 812 final int savePosition = position; 813 // Calculate the end position. 814 final int end = start + length; 815 // Reset to beginning of string. 816 reset(start); 817 818 // Buffer for recording characters. 819 final StringBuilder sb = new StringBuilder(length); 820 821 // Scan until end of string. 822 while (position < end) { 823 // If escape character. 824 if (ch0 == '\\') { 825 skip(1); 826 827 final char next = ch0; 828 final int afterSlash = position; 829 830 skip(1); 831 832 // Special characters. 833 switch (next) { 834 case '0': 835 case '1': 836 case '2': 837 case '3': 838 case '4': 839 case '5': 840 case '6': 841 case '7': { 842 if (strict) { 843 // "\0" itself is allowed in strict mode. Only other 'real' 844 // octal escape sequences are not allowed (eg. "\02", "\31"). 845 // See section 7.8.4 String literals production EscapeSequence 846 if (next != '0' || (ch0 >= '0' && ch0 <= '9')) { 847 error(Lexer.message("strict.no.octal"), STRING, position, limit); 848 } 849 } 850 reset(afterSlash); 851 // Octal sequence. 852 final int ch = octalSequence(); 853 854 if (ch < 0) { 855 sb.append('\\'); 856 sb.append('x'); 857 } else { 858 sb.append((char)ch); 859 } 860 break; 861 } 862 case 'n': 863 sb.append('\n'); 864 break; 865 case 't': 866 sb.append('\t'); 867 break; 868 case 'b': 869 sb.append('\b'); 870 break; 871 case 'f': 872 sb.append('\f'); 873 break; 874 case 'r': 875 sb.append('\r'); 876 break; 877 case '\'': 878 sb.append('\''); 879 break; 880 case '\"': 881 sb.append('\"'); 882 break; 883 case '\\': 884 sb.append('\\'); 885 break; 886 case '\r': // CR | CRLF 887 if (ch0 == '\n') { 888 skip(1); 889 } 890 // fall through 891 case '\n': // LF 892 case '\u2028': // LS 893 case '\u2029': // PS 894 // continue on the next line, slash-return continues string 895 // literal 896 break; 897 case 'x': { 898 // Hex sequence. 899 final int ch = hexSequence(2, STRING); 900 901 if (ch < 0) { 902 sb.append('\\'); 903 sb.append('x'); 904 } else { 905 sb.append((char)ch); 906 } 907 } 908 break; 909 case 'u': { 910 // Unicode sequence. 911 final int ch = hexSequence(4, STRING); 912 913 if (ch < 0) { 914 sb.append('\\'); 915 sb.append('u'); 916 } else { 917 sb.append((char)ch); 918 } 919 } 920 break; 921 case 'v': 922 sb.append('\u000B'); 923 break; 924 // All other characters. 925 default: 926 sb.append(next); 927 break; 928 } 929 } else { 930 // Add regular character. 931 sb.append(ch0); 932 skip(1); 933 } 934 } 935 936 // Restore position. 937 reset(savePosition); 938 939 return sb.toString(); 940 } 941 942 /** 943 * Scan over a string literal. 944 * @param add true if we nare not just scanning but should actually modify the token stream 945 */ 946 protected void scanString(final boolean add) { 947 // Type of string. 948 TokenType type = STRING; 949 // Record starting quote. 950 final char quote = ch0; 951 // Skip over quote. 952 skip(1); 953 954 // Record beginning of string content. 955 final State stringState = saveState(); 956 957 // Scan until close quote or end of line. 958 while (!atEOF() && ch0 != quote && !isEOL(ch0)) { 959 // Skip over escaped character. 960 if (ch0 == '\\') { 961 type = ESCSTRING; 962 skip(1); 963 if (! isEscapeCharacter(ch0)) { 964 error(Lexer.message("invalid.escape.char"), STRING, position, limit); 965 } 966 if (isEOL(ch0)) { 967 // Multiline string literal 968 skipEOL(false); 969 continue; 970 } 971 } 972 // Skip literal character. 973 skip(1); 974 } 975 976 // If close quote. 977 if (ch0 == quote) { 978 // Skip close quote. 979 skip(1); 980 } else { 981 error(Lexer.message("missing.close.quote"), STRING, position, limit); 982 } 983 984 // If not just scanning. 985 if (add) { 986 // Record end of string. 987 stringState.setLimit(position - 1); 988 989 if (scripting && !stringState.isEmpty()) { 990 switch (quote) { 991 case '`': 992 // Mark the beginning of an exec string. 993 add(EXECSTRING, stringState.position, stringState.limit); 994 // Frame edit string with left brace. 995 add(LBRACE, stringState.position, stringState.position); 996 // Process edit string. 997 editString(type, stringState); 998 // Frame edit string with right brace. 999 add(RBRACE, stringState.limit, stringState.limit); 1000 break; 1001 case '"': 1002 // Only edit double quoted strings. 1003 editString(type, stringState); 1004 break; 1005 case '\'': 1006 // Add string token without editing. 1007 add(type, stringState.position, stringState.limit); 1008 break; 1009 default: 1010 break; 1011 } 1012 } else { 1013 /// Add string token without editing. 1014 add(type, stringState.position, stringState.limit); 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Is the given character a valid escape char after "\" ? 1021 * 1022 * @param ch character to be checked 1023 * @return if the given character is valid after "\" 1024 */ 1025 protected boolean isEscapeCharacter(final char ch) { 1026 return true; 1027 } 1028 1029 /** 1030 * Convert string to number. 1031 * 1032 * @param valueString String to convert. 1033 * @param radix Numeric base. 1034 * @return Converted number. 1035 */ 1036 private static Number valueOf(final String valueString, final int radix) throws NumberFormatException { 1037 try { 1038 final long value = Long.parseLong(valueString, radix); 1039 if(value >= MIN_INT_L && value <= MAX_INT_L) { 1040 return Integer.valueOf((int)value); 1041 } 1042 return Long.valueOf(value); 1043 } catch (final NumberFormatException e) { 1044 if (radix == 10) { 1045 return Double.valueOf(valueString); 1046 } 1047 1048 double value = 0.0; 1049 1050 for (int i = 0; i < valueString.length(); i++) { 1051 final char ch = valueString.charAt(i); 1052 // Preverified, should always be a valid digit. 1053 final int digit = convertDigit(ch, radix); 1054 value *= radix; 1055 value += digit; 1056 } 1057 1058 return value; 1059 } 1060 } 1061 1062 /** 1063 * Scan a number. 1064 */ 1065 protected void scanNumber() { 1066 // Record beginning of number. 1067 final int start = position; 1068 // Assume value is a decimal. 1069 TokenType type = DECIMAL; 1070 1071 // First digit of number. 1072 int digit = convertDigit(ch0, 10); 1073 1074 // If number begins with 0x. 1075 if (digit == 0 && (ch1 == 'x' || ch1 == 'X') && convertDigit(ch2, 16) != -1) { 1076 // Skip over 0xN. 1077 skip(3); 1078 // Skip over remaining digits. 1079 while (convertDigit(ch0, 16) != -1) { 1080 skip(1); 1081 } 1082 1083 type = HEXADECIMAL; 1084 } else { 1085 // Check for possible octal constant. 1086 boolean octal = digit == 0; 1087 // Skip first digit if not leading '.'. 1088 if (digit != -1) { 1089 skip(1); 1090 } 1091 1092 // Skip remaining digits. 1093 while ((digit = convertDigit(ch0, 10)) != -1) { 1094 // Check octal only digits. 1095 octal = octal && digit < 8; 1096 // Skip digit. 1097 skip(1); 1098 } 1099 1100 if (octal && position - start > 1) { 1101 type = OCTAL; 1102 } else if (ch0 == '.' || ch0 == 'E' || ch0 == 'e') { 1103 // Must be a double. 1104 if (ch0 == '.') { 1105 // Skip period. 1106 skip(1); 1107 // Skip mantissa. 1108 while (convertDigit(ch0, 10) != -1) { 1109 skip(1); 1110 } 1111 } 1112 1113 // Detect exponent. 1114 if (ch0 == 'E' || ch0 == 'e') { 1115 // Skip E. 1116 skip(1); 1117 // Detect and skip exponent sign. 1118 if (ch0 == '+' || ch0 == '-') { 1119 skip(1); 1120 } 1121 // Skip exponent. 1122 while (convertDigit(ch0, 10) != -1) { 1123 skip(1); 1124 } 1125 } 1126 1127 type = FLOATING; 1128 } 1129 } 1130 1131 if (Character.isJavaIdentifierStart(ch0)) { 1132 error(Lexer.message("missing.space.after.number"), type, position, 1); 1133 } 1134 1135 // Add number token. 1136 add(type, start); 1137 } 1138 1139 /** 1140 * Convert a regex token to a token object. 1141 * 1142 * @param start Position in source content. 1143 * @param length Length of regex token. 1144 * @return Regex token object. 1145 */ 1146 XMLToken valueOfXML(final int start, final int length) { 1147 return new XMLToken(source.getString(start, length)); 1148 } 1149 1150 /** 1151 * Scan over a XML token. 1152 * 1153 * @return TRUE if is an XML literal. 1154 */ 1155 private boolean scanXMLLiteral() { 1156 assert ch0 == '<' && Character.isJavaIdentifierStart(ch1); 1157 if (XML_LITERALS) { 1158 // Record beginning of xml expression. 1159 final int start = position; 1160 1161 int openCount = 0; 1162 1163 do { 1164 if (ch0 == '<') { 1165 if (ch1 == '/' && Character.isJavaIdentifierStart(ch2)) { 1166 skip(3); 1167 openCount--; 1168 } else if (Character.isJavaIdentifierStart(ch1)) { 1169 skip(2); 1170 openCount++; 1171 } else if (ch1 == '?') { 1172 skip(2); 1173 } else if (ch1 == '!' && ch2 == '-' && ch3 == '-') { 1174 skip(4); 1175 } else { 1176 reset(start); 1177 return false; 1178 } 1179 1180 while (!atEOF() && ch0 != '>') { 1181 if (ch0 == '/' && ch1 == '>') { 1182 openCount--; 1183 skip(1); 1184 break; 1185 } else if (ch0 == '\"' || ch0 == '\'') { 1186 scanString(false); 1187 } else { 1188 skip(1); 1189 } 1190 } 1191 1192 if (ch0 != '>') { 1193 reset(start); 1194 return false; 1195 } 1196 1197 skip(1); 1198 } else if (atEOF()) { 1199 reset(start); 1200 return false; 1201 } else { 1202 skip(1); 1203 } 1204 } while (openCount > 0); 1205 1206 add(XML, start); 1207 return true; 1208 } 1209 1210 return false; 1211 } 1212 1213 /** 1214 * Scan over identifier characters. 1215 * 1216 * @return Length of identifier or zero if none found. 1217 */ 1218 private int scanIdentifier() { 1219 final int start = position; 1220 1221 // Make sure first character is valid start character. 1222 if (ch0 == '\\' && ch1 == 'u') { 1223 skip(2); 1224 final int ch = hexSequence(4, TokenType.IDENT); 1225 1226 if (!Character.isJavaIdentifierStart(ch)) { 1227 error(Lexer.message("illegal.identifier.character"), TokenType.IDENT, start, position); 1228 } 1229 } else if (!Character.isJavaIdentifierStart(ch0)) { 1230 // Not an identifier. 1231 return 0; 1232 } 1233 1234 // Make sure remaining characters are valid part characters. 1235 while (!atEOF()) { 1236 if (ch0 == '\\' && ch1 == 'u') { 1237 skip(2); 1238 final int ch = hexSequence(4, TokenType.IDENT); 1239 1240 if (!Character.isJavaIdentifierPart(ch)) { 1241 error(Lexer.message("illegal.identifier.character"), TokenType.IDENT, start, position); 1242 } 1243 } else if (Character.isJavaIdentifierPart(ch0)) { 1244 skip(1); 1245 } else { 1246 break; 1247 } 1248 } 1249 1250 // Length of identifier sequence. 1251 return position - start; 1252 } 1253 1254 /** 1255 * Compare two identifiers (in content) for equality. 1256 * 1257 * @param aStart Start of first identifier. 1258 * @param aLength Length of first identifier. 1259 * @param bStart Start of second identifier. 1260 * @param bLength Length of second identifier. 1261 * @return True if equal. 1262 */ 1263 private boolean identifierEqual(final int aStart, final int aLength, final int bStart, final int bLength) { 1264 if (aLength == bLength) { 1265 for (int i = 0; i < aLength; i++) { 1266 if (content[aStart + i] != content[bStart + i]) { 1267 return false; 1268 } 1269 } 1270 1271 return true; 1272 } 1273 1274 return false; 1275 } 1276 1277 /** 1278 * Detect if a line starts with a marker identifier. 1279 * 1280 * @param identStart Start of identifier. 1281 * @param identLength Length of identifier. 1282 * @return True if detected. 1283 */ 1284 private boolean hasHereMarker(final int identStart, final int identLength) { 1285 // Skip any whitespace. 1286 skipWhitespace(false); 1287 1288 return identifierEqual(identStart, identLength, position, scanIdentifier()); 1289 } 1290 1291 /** 1292 * Lexer to service edit strings. 1293 */ 1294 private static class EditStringLexer extends Lexer { 1295 /** Type of string literals to emit. */ 1296 final TokenType stringType; 1297 1298 /* 1299 * Constructor. 1300 */ 1301 1302 EditStringLexer(final Lexer lexer, final TokenType stringType, final State stringState) { 1303 super(lexer, stringState); 1304 1305 this.stringType = stringType; 1306 } 1307 1308 /** 1309 * Lexify the contents of the string. 1310 */ 1311 @Override 1312 public void lexify() { 1313 // Record start of string position. 1314 int stringStart = position; 1315 // Indicate that the priming first string has not been emitted. 1316 boolean primed = false; 1317 1318 while (true) { 1319 // Detect end of content. 1320 if (atEOF()) { 1321 break; 1322 } 1323 1324 // Honour escapes (should be well formed.) 1325 if (ch0 == '\\' && stringType == ESCSTRING) { 1326 skip(2); 1327 1328 continue; 1329 } 1330 1331 // If start of expression. 1332 if (ch0 == '$' && ch1 == '{') { 1333 if (!primed || stringStart != position) { 1334 if (primed) { 1335 add(ADD, stringStart, stringStart + 1); 1336 } 1337 1338 add(stringType, stringStart, position); 1339 primed = true; 1340 } 1341 1342 // Skip ${ 1343 skip(2); 1344 1345 // Save expression state. 1346 final State expressionState = saveState(); 1347 1348 // Start with one open brace. 1349 int braceCount = 1; 1350 1351 // Scan for the rest of the string. 1352 while (!atEOF()) { 1353 // If closing brace. 1354 if (ch0 == '}') { 1355 // Break only only if matching brace. 1356 if (--braceCount == 0) { 1357 break; 1358 } 1359 } else if (ch0 == '{') { 1360 // Bump up the brace count. 1361 braceCount++; 1362 } 1363 1364 // Skip to next character. 1365 skip(1); 1366 } 1367 1368 // If braces don't match then report an error. 1369 if (braceCount != 0) { 1370 error(Lexer.message("edit.string.missing.brace"), LBRACE, expressionState.position - 1, 1); 1371 } 1372 1373 // Mark end of expression. 1374 expressionState.setLimit(position); 1375 // Skip closing brace. 1376 skip(1); 1377 1378 // Start next string. 1379 stringStart = position; 1380 1381 // Concatenate expression. 1382 add(ADD, expressionState.position, expressionState.position + 1); 1383 add(LPAREN, expressionState.position, expressionState.position + 1); 1384 1385 // Scan expression. 1386 final Lexer lexer = new Lexer(this, expressionState); 1387 lexer.lexify(); 1388 1389 // Close out expression parenthesis. 1390 add(RPAREN, position - 1, position); 1391 1392 continue; 1393 } 1394 1395 // Next character in string. 1396 skip(1); 1397 } 1398 1399 // If there is any unemitted string portion. 1400 if (stringStart != limit) { 1401 // Concatenate remaining string. 1402 if (primed) { 1403 add(ADD, stringStart, 1); 1404 } 1405 1406 add(stringType, stringStart, limit); 1407 } 1408 } 1409 1410 } 1411 1412 /** 1413 * Edit string for nested expressions. 1414 * 1415 * @param stringType Type of string literals to emit. 1416 * @param stringState State of lexer at start of string. 1417 */ 1418 private void editString(final TokenType stringType, final State stringState) { 1419 // Use special lexer to scan string. 1420 final EditStringLexer lexer = new EditStringLexer(this, stringType, stringState); 1421 lexer.lexify(); 1422 1423 // Need to keep lexer informed. 1424 last = stringType; 1425 } 1426 1427 /** 1428 * Scan over a here string. 1429 * 1430 * @return TRUE if is a here string. 1431 */ 1432 private boolean scanHereString(final LineInfoReceiver lir) { 1433 assert ch0 == '<' && ch1 == '<'; 1434 if (scripting) { 1435 // Record beginning of here string. 1436 final State saved = saveState(); 1437 1438 // << or <<< 1439 final boolean excludeLastEOL = ch2 != '<'; 1440 1441 if (excludeLastEOL) { 1442 skip(2); 1443 } else { 1444 skip(3); 1445 } 1446 1447 // Scan identifier. 1448 final int identStart = position; 1449 final int identLength = scanIdentifier(); 1450 1451 // Check for identifier. 1452 if (identLength == 0) { 1453 // Treat as shift. 1454 restoreState(saved); 1455 1456 return false; 1457 } 1458 1459 // Record rest of line. 1460 final State restState = saveState(); 1461 // keep line number updated 1462 int lastLine = line; 1463 1464 skipLine(false); 1465 lastLine++; 1466 int lastLinePosition = position; 1467 restState.setLimit(position); 1468 1469 // Record beginning of string. 1470 final State stringState = saveState(); 1471 int stringEnd = position; 1472 1473 // Hunt down marker. 1474 while (!atEOF()) { 1475 // Skip any whitespace. 1476 skipWhitespace(false); 1477 1478 if (hasHereMarker(identStart, identLength)) { 1479 break; 1480 } 1481 1482 skipLine(false); 1483 lastLine++; 1484 lastLinePosition = position; 1485 stringEnd = position; 1486 } 1487 1488 // notify last line information 1489 lir.lineInfo(lastLine, lastLinePosition); 1490 1491 // Record end of string. 1492 stringState.setLimit(stringEnd); 1493 1494 // If marker is missing. 1495 if (stringState.isEmpty() || atEOF()) { 1496 error(Lexer.message("here.missing.end.marker", source.getString(identStart, identLength)), last, position, position); 1497 restoreState(saved); 1498 1499 return false; 1500 } 1501 1502 // Remove last end of line if specified. 1503 if (excludeLastEOL) { 1504 // Handles \n. 1505 if (content[stringEnd - 1] == '\n') { 1506 stringEnd--; 1507 } 1508 1509 // Handles \r and \r\n. 1510 if (content[stringEnd - 1] == '\r') { 1511 stringEnd--; 1512 } 1513 1514 // Update end of string. 1515 stringState.setLimit(stringEnd); 1516 } 1517 1518 // Edit string if appropriate. 1519 if (scripting && !stringState.isEmpty()) { 1520 editString(STRING, stringState); 1521 } else { 1522 // Add here string. 1523 add(STRING, stringState.position, stringState.limit); 1524 } 1525 1526 // Scan rest of original line. 1527 final Lexer restLexer = new Lexer(this, restState); 1528 1529 restLexer.lexify(); 1530 1531 return true; 1532 } 1533 1534 return false; 1535 } 1536 1537 /** 1538 * Breaks source content down into lex units, adding tokens to the token 1539 * stream. The routine scans until the stream buffer is full. Can be called 1540 * repeatedly until EOF is detected. 1541 */ 1542 public void lexify() { 1543 while (!stream.isFull() || nested) { 1544 // Skip over whitespace. 1545 skipWhitespace(true); 1546 1547 // Detect end of file. 1548 if (atEOF()) { 1549 if (!nested) { 1550 // Add an EOF token at the end. 1551 add(EOF, position); 1552 } 1553 1554 break; 1555 } 1556 1557 // Check for comments. Note that we don't scan for regexp and other literals here as 1558 // we may not have enough context to distinguish them from similar looking operators. 1559 // Instead we break on ambiguous operators below and let the parser decide. 1560 if (ch0 == '/' && skipComments()) { 1561 continue; 1562 } 1563 1564 if (scripting && ch0 == '#' && skipComments()) { 1565 continue; 1566 } 1567 1568 // TokenType for lookup of delimiter or operator. 1569 TokenType type; 1570 1571 if (ch0 == '.' && convertDigit(ch1, 10) != -1) { 1572 // '.' followed by digit. 1573 // Scan and add a number. 1574 scanNumber(); 1575 } else if ((type = TokenLookup.lookupOperator(ch0, ch1, ch2, ch3)) != null) { 1576 // Get the number of characters in the token. 1577 final int typeLength = type.getLength(); 1578 // Skip that many characters. 1579 skip(typeLength); 1580 // Add operator token. 1581 add(type, position - typeLength); 1582 // Some operator tokens also mark the beginning of regexp, XML, or here string literals. 1583 // We break to let the parser decide what it is. 1584 if (canStartLiteral(type)) { 1585 break; 1586 } 1587 } else if (Character.isJavaIdentifierStart(ch0) || ch0 == '\\' && ch1 == 'u') { 1588 // Scan and add identifier or keyword. 1589 scanIdentifierOrKeyword(); 1590 } else if (isStringDelimiter(ch0)) { 1591 // Scan and add a string. 1592 scanString(true); 1593 } else if (Character.isDigit(ch0)) { 1594 // Scan and add a number. 1595 scanNumber(); 1596 } else { 1597 // Don't recognize this character. 1598 skip(1); 1599 add(ERROR, position - 1); 1600 } 1601 } 1602 } 1603 1604 /** 1605 * Return value of token given its token descriptor. 1606 * 1607 * @param token Token descriptor. 1608 * @return JavaScript value. 1609 */ 1610 Object getValueOf(final long token, final boolean strict) { 1611 final int start = Token.descPosition(token); 1612 final int len = Token.descLength(token); 1613 1614 switch (Token.descType(token)) { 1615 case DECIMAL: 1616 return Lexer.valueOf(source.getString(start, len), 10); // number 1617 case OCTAL: 1618 return Lexer.valueOf(source.getString(start, len), 8); // number 1619 case HEXADECIMAL: 1620 return Lexer.valueOf(source.getString(start + 2, len - 2), 16); // number 1621 case FLOATING: 1622 return Double.valueOf(source.getString(start, len)); // number 1623 case STRING: 1624 return source.getString(start, len); // String 1625 case ESCSTRING: 1626 return valueOfString(start, len, strict); // String 1627 case IDENT: 1628 return valueOfIdent(start, len); // String 1629 case REGEX: 1630 return valueOfPattern(start, len); // RegexToken::LexerToken 1631 case XML: 1632 return valueOfXML(start, len); // XMLToken::LexerToken 1633 case DIRECTIVE_COMMENT: 1634 return source.getString(start, len); 1635 default: 1636 break; 1637 } 1638 1639 return null; 1640 } 1641 1642 /** 1643 * Get the correctly localized error message for a given message id format arguments 1644 * @param msgId message id 1645 * @param args format arguments 1646 * @return message 1647 */ 1648 protected static String message(final String msgId, final String... args) { 1649 return ECMAErrors.getMessage("lexer.error." + msgId, args); 1650 } 1651 1652 /** 1653 * Generate a runtime exception 1654 * 1655 * @param message error message 1656 * @param type token type 1657 * @param start start position of lexed error 1658 * @param length length of lexed error 1659 * @throws ParserException unconditionally 1660 */ 1661 protected void error(final String message, final TokenType type, final int start, final int length) throws ParserException { 1662 final long token = Token.toDesc(type, start, length); 1663 final int pos = Token.descPosition(token); 1664 final int lineNum = source.getLine(pos); 1665 final int columnNum = source.getColumn(pos); 1666 final String formatted = ErrorManager.format(message, source, lineNum, columnNum, token); 1667 throw new ParserException(JSErrorType.SYNTAX_ERROR, formatted, source, lineNum, columnNum, token); 1668 } 1669 1670 /** 1671 * Helper class for Lexer tokens, e.g XML or RegExp tokens. 1672 * This is the abstract superclass 1673 */ 1674 public static abstract class LexerToken { 1675 private final String expression; 1676 1677 /** 1678 * Constructor 1679 * @param expression token expression 1680 */ 1681 protected LexerToken(final String expression) { 1682 this.expression = expression; 1683 } 1684 1685 /** 1686 * Get the expression 1687 * @return expression 1688 */ 1689 public String getExpression() { 1690 return expression; 1691 } 1692 } 1693 1694 /** 1695 * Temporary container for regular expressions. 1696 */ 1697 public static class RegexToken extends LexerToken { 1698 /** Options. */ 1699 private final String options; 1700 1701 /** 1702 * Constructor. 1703 * 1704 * @param expression regexp expression 1705 * @param options regexp options 1706 */ 1707 public RegexToken(final String expression, final String options) { 1708 super(expression); 1709 this.options = options; 1710 } 1711 1712 /** 1713 * Get regexp options 1714 * @return options 1715 */ 1716 public String getOptions() { 1717 return options; 1718 } 1719 1720 @Override 1721 public String toString() { 1722 return '/' + getExpression() + '/' + options; 1723 } 1724 } 1725 1726 /** 1727 * Temporary container for XML expression. 1728 */ 1729 public static class XMLToken extends LexerToken { 1730 1731 /** 1732 * Constructor. 1733 * 1734 * @param expression XML expression 1735 */ 1736 public XMLToken(final String expression) { 1737 super(expression); 1738 } 1739 } 1740 }