nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Lexer.java
changeset 33414 2e284c36d51f
parent 32444 4c7a40aab132
child 33890 2e8c1be40a52
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Lexer.java	Wed Jul 05 20:56:54 2017 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Lexer.java	Wed Oct 28 10:54:05 2015 +0100
@@ -46,6 +46,10 @@
 import static jdk.nashorn.internal.parser.TokenType.REGEX;
 import static jdk.nashorn.internal.parser.TokenType.RPAREN;
 import static jdk.nashorn.internal.parser.TokenType.STRING;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_HEAD;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_MIDDLE;
+import static jdk.nashorn.internal.parser.TokenType.TEMPLATE_TAIL;
 import static jdk.nashorn.internal.parser.TokenType.XML;
 
 import java.io.Serializable;
@@ -96,6 +100,8 @@
     private final boolean pauseOnFunctionBody;
     private boolean pauseOnNextLeftBrace;
 
+    private int templateExpressionOpenBraces;
+
     private static final String SPACETAB = " \t";  // ASCII space and tab
     private static final String LFCR     = "\n\r"; // line feed and carriage return (ctrl-m)
 
@@ -392,13 +398,19 @@
     }
 
     /**
-     * Test if char is a string delimiter, e.g. '\' or '"'.  Also scans exec
-     * strings ('`') in scripting mode.
+     * Test if char is a string delimiter, e.g. '\' or '"'.
      * @param ch a char
      * @return true if string delimiter
      */
     protected boolean isStringDelimiter(final char ch) {
-        return ch == '\'' || ch == '"' || (scripting && ch == '`');
+        return ch == '\'' || ch == '"';
+    }
+
+    /**
+     * Test if char is a template literal delimiter ('`').
+     */
+    private static boolean isTemplateDelimiter(char ch) {
+        return ch == '`';
     }
 
     /**
@@ -943,6 +955,10 @@
                     sb.append(next);
                     break;
                 }
+            } else if (ch0 == '\r') {
+                // Convert CR-LF or CR to LF line terminator.
+                sb.append('\n');
+                skip(ch1 == '\n' ? 2 : 1);
             } else {
                 // Add regular character.
                 sb.append(ch0);
@@ -958,7 +974,7 @@
 
     /**
      * Scan over a string literal.
-     * @param add true if we nare not just scanning but should actually modify the token stream
+     * @param add true if we are not just scanning but should actually modify the token stream
      */
     protected void scanString(final boolean add) {
         // Type of string.
@@ -1034,6 +1050,70 @@
     }
 
     /**
+     * Scan over a template string literal.
+     */
+    private void scanTemplate() {
+        assert ch0 == '`';
+        TokenType type = TEMPLATE;
+
+        // Skip over quote and record beginning of string content.
+        skip(1);
+        State stringState = saveState();
+
+        // Scan until close quote
+        while (!atEOF()) {
+            // Skip over escaped character.
+            if (ch0 == '`') {
+                skip(1);
+                // Record end of string.
+                stringState.setLimit(position - 1);
+                add(type == TEMPLATE ? type : TEMPLATE_TAIL, stringState.position, stringState.limit);
+                return;
+            } else if (ch0 == '$' && ch1 == '{') {
+                skip(2);
+                stringState.setLimit(position - 2);
+                add(type == TEMPLATE ? TEMPLATE_HEAD : type, stringState.position, stringState.limit);
+
+                // scan to RBRACE
+                Lexer expressionLexer = new Lexer(this, saveState());
+                expressionLexer.templateExpressionOpenBraces = 1;
+                expressionLexer.lexify();
+                restoreState(expressionLexer.saveState());
+
+                // scan next middle or tail of the template literal
+                assert ch0 == '}';
+                type = TEMPLATE_MIDDLE;
+
+                // Skip over rbrace and record beginning of string content.
+                skip(1);
+                stringState = saveState();
+
+                continue;
+            } else if (ch0 == '\\') {
+                skip(1);
+                // EscapeSequence
+                if (!isEscapeCharacter(ch0)) {
+                    error(Lexer.message("invalid.escape.char"), TEMPLATE, position, limit);
+                }
+                if (isEOL(ch0)) {
+                    // LineContinuation
+                    skipEOL(false);
+                    continue;
+                }
+            }  else if (isEOL(ch0)) {
+                // LineTerminatorSequence
+                skipEOL(false);
+                continue;
+            }
+
+            // Skip literal character.
+            skip(1);
+        }
+
+        error(Lexer.message("missing.close.quote"), TEMPLATE, position, limit);
+    }
+
+    /**
      * Is the given character a valid escape char after "\" ?
      *
      * @param ch character to be checked
@@ -1621,6 +1701,16 @@
                 // Scan and add a number.
                 scanNumber();
             } else if ((type = TokenLookup.lookupOperator(ch0, ch1, ch2, ch3)) != null) {
+                if (templateExpressionOpenBraces > 0) {
+                    if (type == LBRACE) {
+                        templateExpressionOpenBraces++;
+                    } else if (type == RBRACE) {
+                        if (--templateExpressionOpenBraces == 0) {
+                            break;
+                        }
+                    }
+                }
+
                 // Get the number of characters in the token.
                 final int typeLength = type.getLength();
                 // Skip that many characters.
@@ -1644,6 +1734,12 @@
             } else if (Character.isDigit(ch0)) {
                 // Scan and add a number.
                 scanNumber();
+            } else if (isTemplateDelimiter(ch0) && es6) {
+                // Scan and add template in ES6 mode.
+                scanTemplate();
+            } else if (isTemplateDelimiter(ch0) && scripting) {
+                // Scan and add an exec string ('`') in scripting mode.
+                scanString(true);
             } else {
                 // Don't recognize this character.
                 skip(1);
@@ -1699,6 +1795,11 @@
             return valueOfIdent(start, len); // String
         case REGEX:
             return valueOfPattern(start, len); // RegexToken::LexerToken
+        case TEMPLATE:
+        case TEMPLATE_HEAD:
+        case TEMPLATE_MIDDLE:
+        case TEMPLATE_TAIL:
+            return valueOfString(start, len, true); // String
         case XML:
             return valueOfXML(start, len); // XMLToken::LexerToken
         case DIRECTIVE_COMMENT:
@@ -1711,6 +1812,45 @@
     }
 
     /**
+     * Get the raw string value of a template literal string part.
+     *
+     * @param token template string token
+     * @return raw string
+     */
+    public String valueOfRawString(final long token) {
+        final int start  = Token.descPosition(token);
+        final int length = Token.descLength(token);
+
+        // Save the current position.
+        final int savePosition = position;
+        // Calculate the end position.
+        final int end = start + length;
+        // Reset to beginning of string.
+        reset(start);
+
+        // Buffer for recording characters.
+        final StringBuilder sb = new StringBuilder(length);
+
+        // Scan until end of string.
+        while (position < end) {
+            if (ch0 == '\r') {
+                // Convert CR-LF or CR to LF line terminator.
+                sb.append('\n');
+                skip(ch1 == '\n' ? 2 : 1);
+            } else {
+                // Add regular character.
+                sb.append(ch0);
+                skip(1);
+            }
+        }
+
+        // Restore position.
+        reset(savePosition);
+
+        return sb.toString();
+    }
+
+    /**
      * Get the correctly localized error message for a given message id format arguments
      * @param msgId message id
      * @param args  format arguments