diff options
author | Jay Arthanareeswaran | 2019-04-29 07:20:44 +0000 |
---|---|---|
committer | Jay Arthanareeswaran | 2019-10-24 06:24:35 +0000 |
commit | f178ea0c32cc749d2aa2615414008a6a522da301 (patch) | |
tree | 83b380597a4460cf245dce1d90e64e957be1b700 | |
parent | 9ef03eee1438718368e8d3a7fe69a47ee6a51ef0 (diff) | |
download | eclipse.jdt.core-f178ea0c32cc749d2aa2615414008a6a522da301.tar.gz eclipse.jdt.core-f178ea0c32cc749d2aa2615414008a6a522da301.tar.xz eclipse.jdt.core-f178ea0c32cc749d2aa2615414008a6a522da301.zip |
Change-Id: Ia43f06569f4c8e9df3fd3294375f825638816af4
Signed-off-by: Jay Arthanareeswaran <jarthana@in.ibm.com>
6 files changed, 284 insertions, 64 deletions
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter13Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter13Test.java index 17592cbbeb..efec12db6e 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter13Test.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter13Test.java @@ -526,6 +526,16 @@ public class ASTConverter13Test extends ConverterTestSetup { assertTrue("String should not be empty", escapedValue.length() != 0); assertTrue("String should start with \"\"\"", escapedValue.startsWith("\"\"\"")); + + String literal = ((TextBlock) initializer).getLiteralValue(); + assertEquals("literal value not correct", + " <html>\n" + + " <body>\n" + + " <p>Hello, world</p>\n" + + " </body>\n" + + " </html>\n" + + " ", + literal); } finally { javaProject.setOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, old); @@ -607,6 +617,30 @@ public class ASTConverter13Test extends ConverterTestSetup { ITypeBinding binding = initializer.resolveTypeBinding(); assertNotNull("No binding", binding); assertEquals("Wrong qualified name", "java.lang.String", binding.getQualifiedName()); + + String escapedValue = ((TextBlock) initializer).getEscapedValue(); + + assertTrue("String should not be empty", escapedValue.length() != 0); + assertTrue("String should start with \"\"\"", escapedValue.startsWith("\"\"\"")); + assertEquals("escaped value not correct", + "\"\"\"\n" + + " <html>\n" + + " <body>\n" + + " <p>Hello, world</p>\n" + + " </body>\n" + + " </html>\n" + + " \"\"\"", + escapedValue); + + String literal = ((TextBlock) initializer).getLiteralValue(); + assertEquals("literal value not correct", + " <html>\n" + + " <body>\n" + + " <p>Hello, world</p>\n" + + " </body>\n" + + " </html>\n" + + " ", + literal); } finally { javaProject.setOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, old); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java index 82a5bcc103..54ea9a91d2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Scanner.java @@ -237,7 +237,7 @@ public class Scanner implements TerminalTokens { public static final int HIGH_SURROGATE_MAX_VALUE = 0xDBFF; public static final int LOW_SURROGATE_MAX_VALUE = 0xDFFF; - // raw string support - 11 + // text block support - 13 /* package */ int rawStart = -1; public Scanner() { @@ -1684,7 +1684,9 @@ protected int getNextToken0() throws InvalidInputException { lastQuotePos = this.currentPosition; // look for text block delimiter if (scanForTextBlockClose()) { - if (this.source[this.currentPosition + 2] == '"') { + // Account for just the snippet being passed around + // If already at the EOF, bail out. + if (this.currentPosition + 2 < this.source.length && this.source[this.currentPosition + 2] == '"') { terminators++; if (terminators > 2) throw new InvalidInputException(UNTERMINATED_TEXT_BLOCK); @@ -1753,6 +1755,9 @@ protected int getNextToken0() throws InvalidInputException { } } // consume next character + if (this.currentPosition >= this.eofPosition) { + break; + } this.unicodeAsBackSlash = false; if (((this.currentCharacter = this.source[this.currentPosition++]) == '\\') && (this.source[this.currentPosition] == 'u')) { @@ -2246,12 +2251,10 @@ public final void jumpOverMethodBody() { case '\n' : pushLineSeparator(); //$FALL-THROUGH$ - case '\\': - if (this.source[this.currentPosition++] == '"') { + default: + if (this.currentCharacter == '\\' && this.source[this.currentPosition++] == '"') { this.currentPosition++; } - //$FALL-THROUGH$ - default: this.currentCharacter = this.source[this.currentPosition++]; continue Inner; } diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TextBlock.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TextBlock.java index a74ed8307e..38ab6c9d79 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TextBlock.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TextBlock.java @@ -17,6 +17,7 @@ package org.eclipse.jdt.core.dom; import java.util.ArrayList; import java.util.List; +import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.InvalidInputException; import org.eclipse.jdt.internal.compiler.parser.Scanner; import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; @@ -240,45 +241,34 @@ public class TextBlock extends Expression { * @exception IllegalArgumentException if the literal value cannot be converted */ public String getLiteralValue() { - String s = getEscapedValue(); - int len = s.length(); - if (len < 2 || s.indexOf("\"\"\"") != 0 || !s.substring(len-3, len).equals("\"\"\"") ) { //$NON-NLS-1$ //$NON-NLS-2$ + char[] escaped = getEscapedValue().toCharArray(); + int len = escaped.length; + if (len < 7) { throw new IllegalArgumentException(); } - boolean newLineFound = false; - for (int i = 3; i < s.length(); i++) { - char c = s.charAt(i); - while (ScannerHelper.isWhitespace(c)) { + int start = -1; + loop: for (int i = 3; i < len; i++) { + char c = escaped[i]; + if (ScannerHelper.isWhitespace(c)) { switch (c) { case 10 : /* \ u000a: LINE FEED */ case 13 : /* \ u000d: CARRIAGE RETURN */ - newLineFound = true; - break; + start = i + 1; + break loop; default: break; } + } else { + break loop; } } - if (!newLineFound) { - throw new IllegalArgumentException(); - } - - Scanner scanner = this.ast.scanner; - char[] source = s.toCharArray(); - scanner.setSource(source); - scanner.resetTo(0, source.length); - try { - int tokenType = scanner.getNextToken(); - switch(tokenType) { - case TerminalTokens.TokenNameTextBlock: - return scanner.getCurrentStringLiteral(); - default: - throw new IllegalArgumentException(); - } - } catch(InvalidInputException e) { + if (start == -1) { throw new IllegalArgumentException(); } + return new String( + CharOperation.subarray(escaped, start, len - 3) + ); } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ToolFactory.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ToolFactory.java index 2b7317085b..9479cd8a34 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ToolFactory.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/ToolFactory.java @@ -547,12 +547,51 @@ public class ToolFactory { */ @SuppressWarnings("javadoc") // references deprecated TokenNameIdentifier public static IScanner createScanner(boolean tokenizeComments, boolean tokenizeWhiteSpace, boolean recordLineSeparator, String sourceLevel, String complianceLevel) { + return createScanner(tokenizeComments, tokenizeWhiteSpace, recordLineSeparator, sourceLevel, complianceLevel, true); + } + /** + * Create a scanner, indicating the level of detail requested for tokenizing. The scanner can then be + * used to tokenize some source in a Java aware way. + * Here is a typical scanning loop: + * + * <pre> + * <code> + * IScanner scanner = ToolFactory.createScanner(false, false, false, false); + * scanner.setSource("int i = 0;".toCharArray()); + * while (true) { + * int token = scanner.getNextToken(); + * if (token == ITerminalSymbols.TokenNameEOF) break; + * System.out.println(token + " : " + new String(scanner.getCurrentTokenSource())); + * } + * </code> + * </pre> + * + * @param tokenizeComments if set to <code>false</code>, comments will be silently consumed + * @param tokenizeWhiteSpace if set to <code>false</code>, white spaces will be silently consumed, + * @param recordLineSeparator if set to <code>true</code>, the scanner will record positions of encountered line + * separator ends. In case of multi-character line separators, the last character position is considered. These positions + * can then be extracted using {@link IScanner#getLineEnds()}. Only non-unicode escape sequences are + * considered as valid line separators. + * @param sourceLevel if set to <code>"1.3"</code> or <code>null</code>, occurrences of 'assert' will be reported as identifiers + * ({@link ITerminalSymbols#TokenNameIdentifier}), whereas if set to <code>"1.4"</code>, it + * would report assert keywords ({@link ITerminalSymbols#TokenNameassert}). Java 1.4 has introduced + * a new 'assert' keyword. + * @param complianceLevel This is used to support the Unicode 4.0 character sets. if set to 1.5 or above, + * the Unicode 4.0 is supported, otherwise Unicode 3.0 is supported. + * @param enablePreview specify whether the scanner should look for preview language features for the specified compliance level + * @return a scanner + * @see org.eclipse.jdt.core.compiler.IScanner + * + * @since 3.14 + */ + @SuppressWarnings("javadoc") // references deprecated TokenNameIdentifier + public static IScanner createScanner(boolean tokenizeComments, boolean tokenizeWhiteSpace, boolean recordLineSeparator, String sourceLevel, String complianceLevel, boolean enablePreview) { PublicScanner scanner = null; long sourceLevelValue = CompilerOptions.versionToJdkLevel(sourceLevel); if (sourceLevelValue == 0) sourceLevelValue = ClassFileConstants.JDK1_3; // fault-tolerance long complianceLevelValue = CompilerOptions.versionToJdkLevel(complianceLevel); if (complianceLevelValue == 0) complianceLevelValue = ClassFileConstants.JDK1_4; // fault-tolerance - scanner = new PublicScanner(tokenizeComments, tokenizeWhiteSpace, false/*nls*/,sourceLevelValue /*sourceLevel*/, complianceLevelValue, null/*taskTags*/, null/*taskPriorities*/, true/*taskCaseSensitive*/); + scanner = new PublicScanner(tokenizeComments, tokenizeWhiteSpace, false/*nls*/,sourceLevelValue /*sourceLevel*/, complianceLevelValue, null/*taskTags*/, null/*taskPriorities*/, true/*taskCaseSensitive*/, enablePreview); scanner.recordLineSeparator = recordLineSeparator; return scanner; } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/ITerminalSymbols.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/ITerminalSymbols.java index 0ab6738990..52fa9a24cd 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/ITerminalSymbols.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/ITerminalSymbols.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -99,6 +99,11 @@ public interface ITerminalSymbols { int TokenNameDoubleLiteral = 43; int TokenNameCharacterLiteral = 44; int TokenNameStringLiteral = 45; + /** + * @since 3.20 + * @noreference This class is not intended to be referenced by clients as it is a part of Java preview feature. + */ + int TokenNameTextBlock = 46; int TokenNamePLUS_PLUS = 1; int TokenNameMINUS_MINUS = 2; int TokenNameEQUAL_EQUAL = 35; @@ -185,5 +190,4 @@ public interface ITerminalSymbols { */ int TokenNameCOLON_COLON = 406; - } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/PublicScanner.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/PublicScanner.java index e7084cd927..13c78fd9ff 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/PublicScanner.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/PublicScanner.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -41,6 +41,7 @@ public class PublicScanner implements IScanner, ITerminalSymbols { */ public long sourceLevel; public long complianceLevel; + public boolean previewEnabled; // 1.4 feature public boolean useAssertAsAnIndentifier = false; @@ -114,6 +115,7 @@ public class PublicScanner implements IScanner, ITerminalSymbols { public static final String NULL_SOURCE_STRING = "Null_Source_String"; //$NON-NLS-1$ public static final String UNTERMINATED_STRING = "Unterminated_String"; //$NON-NLS-1$ + public static final String UNTERMINATED_TEXT_BLOCK = "Unterminated_Text_Block"; //$NON-NLS-1$ public static final String UNTERMINATED_COMMENT = "Unterminated_Comment"; //$NON-NLS-1$ public static final String INVALID_CHAR_IN_STRING = "Invalid_Char_In_String"; //$NON-NLS-1$ public static final String INVALID_DIGIT = "Invalid_Digit"; //$NON-NLS-1$ @@ -201,6 +203,9 @@ public class PublicScanner implements IScanner, ITerminalSymbols { public static final int HIGH_SURROGATE_MAX_VALUE = 0xDBFF; public static final int LOW_SURROGATE_MAX_VALUE = 0xDFFF; + // text block support - 13 + /* package */ int rawStart = -1; + public PublicScanner() { this(false /*comment*/, false /*whitespace*/, false /*nls*/, ClassFileConstants.JDK1_3 /*sourceLevel*/, null/*taskTag*/, null/*taskPriorities*/, true /*taskCaseSensitive*/); } @@ -214,12 +219,33 @@ public PublicScanner( char[][] taskTags, char[][] taskPriorities, boolean isTaskCaseSensitive) { + this(tokenizeComments, + tokenizeWhiteSpace, + checkNonExternalizedStringLiterals, + sourceLevel, + complianceLevel, + taskTags, + taskPriorities, + isTaskCaseSensitive, + true); +} +public PublicScanner( + boolean tokenizeComments, + boolean tokenizeWhiteSpace, + boolean checkNonExternalizedStringLiterals, + long sourceLevel, + long complianceLevel, + char[][] taskTags, + char[][] taskPriorities, + boolean isTaskCaseSensitive, + boolean previewEnabled) { this.eofPosition = Integer.MAX_VALUE; this.tokenizeComments = tokenizeComments; this.tokenizeWhiteSpace = tokenizeWhiteSpace; this.sourceLevel = sourceLevel; this.complianceLevel = complianceLevel; + this.previewEnabled = previewEnabled; this.checkNonExternalizedStringLiterals = checkNonExternalizedStringLiterals; if (taskTags != null) { int taskTagsLength = taskTags.length; @@ -512,6 +538,46 @@ public char[] getCurrentTokenSourceString() { } return result; } +protected final boolean scanForTextBlockBeginning() { + if (this.complianceLevel < ClassFileConstants.JDK13 || !this.previewEnabled) { + return false; + } + try { + // Don't change the position and current character unless we are certain + // to be dealing with a text block. For producing all errors like before + // in case of a valid """ but missing \r or \n, just return false and not + // throw any error. + int temp = this.currentPosition; + if ((this.source[temp++] == '\"' && this.source[temp++] == '\"')) { + char c = this.source[temp++]; + while (ScannerHelper.isWhitespace(c)) { + switch (c) { + case 10 : /* \ u000a: LINE FEED */ + case 13 : /* \ u000d: CARRIAGE RETURN */ + this.currentCharacter = c; + this.currentPosition = temp; + return true; + default: + break; + } + c = this.source[temp++]; + } + } + } catch(IndexOutOfBoundsException e) { + //let it return false; + } + return false; +} +protected final boolean scanForTextBlockClose() throws InvalidInputException { + try { + if (this.source[this.currentPosition] == '\"' && this.source[this.currentPosition + 1] == '\"') { + return true; + } + } catch(IndexOutOfBoundsException e) { + //let it return false; + } + return false; +} public final String getCurrentStringLiteral() { //return the token REAL source (aka unicodes are precomputed). //REMOVE the two " that are at the beginning and the end. @@ -1423,26 +1489,53 @@ public int getNextToken() throws InvalidInputException { } throw new InvalidInputException(INVALID_CHARACTER_CONSTANT); case '"' : + boolean isTextBlock = false; + int lastQuotePos = 0; try { // consume next character this.unicodeAsBackSlash = false; boolean isUnicode = false; - if (((this.currentCharacter = this.source[this.currentPosition++]) == '\\') - && (this.source[this.currentPosition] == 'u')) { - getNextUnicodeChar(); - isUnicode = true; - } else { - if (this.withoutUnicodePtr != 0) { - unicodeStore(); + isTextBlock = scanForTextBlockBeginning(); + if (!isTextBlock) { + if (((this.currentCharacter = this.source[this.currentPosition++]) == '\\') + && (this.source[this.currentPosition] == 'u')) { + getNextUnicodeChar(); + isUnicode = true; + } else { + if (this.withoutUnicodePtr != 0) { + unicodeStore(); + } } } - - while (this.currentCharacter != '"') { - if (this.currentPosition >= this.eofPosition) { - throw new InvalidInputException(UNTERMINATED_STRING); + this.rawStart = this.currentPosition - this.startPosition; + int terminators = 0; + while (this.currentPosition <= this.eofPosition) { + if (this.currentCharacter == '"') { + if (!isTextBlock) { + return ITerminalSymbols.TokenNameStringLiteral; + } + lastQuotePos = this.currentPosition; + // look for text block delimiter + if (scanForTextBlockClose()) { + // Account for just the snippet being passed around + // If already at the EOF, bail out. + if (this.currentPosition + 2 < this.source.length && this.source[this.currentPosition + 2] == '"') { + terminators++; + if (terminators > 2) + throw new InvalidInputException(UNTERMINATED_TEXT_BLOCK); + } else { + this.currentPosition += 2; + return ITerminalSymbols.TokenNameTextBlock; + } + } + if (this.withoutUnicodePtr != 0) { + unicodeStore(); + } + } else { + terminators = 0; } /**** \r and \n are not valid in string literals ****/ - if ((this.currentCharacter == '\n') || (this.currentCharacter == '\r')) { + if (!isTextBlock && (this.currentCharacter == '\n') || (this.currentCharacter == '\r')) { // relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed if (isUnicode) { int start = this.currentPosition; @@ -1496,22 +1589,39 @@ public int getNextToken() throws InvalidInputException { } } // consume next character + if (this.currentPosition >= this.eofPosition) { + break; + } this.unicodeAsBackSlash = false; if (((this.currentCharacter = this.source[this.currentPosition++]) == '\\') - && (this.source[this.currentPosition] == 'u')) { + && (this.source[this.currentPosition] == 'u')) { getNextUnicodeChar(); isUnicode = true; } else { isUnicode = false; + if (isTextBlock && this.currentCharacter == '"') + continue; if (this.withoutUnicodePtr != 0) { unicodeStore(); } } - + } + if (isTextBlock) { + if (lastQuotePos > 0) + this.currentPosition = lastQuotePos; + this.currentPosition = (lastQuotePos > 0) ? lastQuotePos : this.startPosition + this.rawStart; + throw new InvalidInputException(UNTERMINATED_TEXT_BLOCK); + } else { + throw new InvalidInputException(UNTERMINATED_STRING); } } catch (IndexOutOfBoundsException e) { - this.currentPosition--; - throw new InvalidInputException(UNTERMINATED_STRING); + if (isTextBlock) { + this.currentPosition = (lastQuotePos > 0) ? lastQuotePos : this.startPosition + this.rawStart; + throw new InvalidInputException(UNTERMINATED_TEXT_BLOCK); + } else { + this.currentPosition--; + throw new InvalidInputException(UNTERMINATED_STRING); + } } catch (InvalidInputException e) { if (e.getMessage().equals(INVALID_ESCAPE)) { // relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed @@ -1529,7 +1639,6 @@ public int getNextToken() throws InvalidInputException { } throw e; // rethrow } - return TokenNameStringLiteral; case '/' : if (!this.skipComments) { int test = getNextChar('/', '*'); @@ -1929,23 +2038,57 @@ public final void jumpOverMethodBody() { break NextToken; } case '"' : + boolean isTextBlock = false; + int firstClosingBrace = 0; try { try { // consume next character - this.unicodeAsBackSlash = false; - if (((this.currentCharacter = this.source[this.currentPosition++]) == '\\') - && (this.source[this.currentPosition] == 'u')) { - getNextUnicodeChar(); - } else { - if (this.withoutUnicodePtr != 0) { - unicodeStore(); + isTextBlock = scanForTextBlockBeginning(); + if (!isTextBlock) { + this.unicodeAsBackSlash = false; + if (((this.currentCharacter = this.source[this.currentPosition++]) == '\\') + && (this.source[this.currentPosition] == 'u')) { + getNextUnicodeChar(); + } else { + if (this.withoutUnicodePtr != 0) { + unicodeStore(); + } } } } catch (InvalidInputException ex) { // ignore } - while (this.currentCharacter != '"') { - if (this.currentPosition >= this.eofPosition) { - return; + Inner: while (this.currentPosition <= this.eofPosition) { + if (isTextBlock) { + switch (this.currentCharacter) { + case '"': + // look for text block delimiter + if (scanForTextBlockClose()) { + this.currentPosition += 2; + this.currentCharacter = this.source[this.currentPosition]; + isTextBlock = false; + break Inner; + } + break; + case '}': + if (firstClosingBrace == 0) + firstClosingBrace = this.currentPosition; + break; + case '\r' : + if (this.source[this.currentPosition] == '\n') + this.currentPosition++; + //$FALL-THROUGH$ + case '\n' : + pushLineSeparator(); + //$FALL-THROUGH$ + default: + if (this.currentCharacter == '\\' && this.source[this.currentPosition++] == '"') { + this.currentPosition++; + } + this.currentCharacter = this.source[this.currentPosition++]; + continue Inner; + } + } else if (this.currentCharacter == '"') { + break Inner; } if (this.currentCharacter == '\r'){ if (this.source[this.currentPosition] == '\n') this.currentPosition++; @@ -1989,7 +2132,13 @@ public final void jumpOverMethodBody() { } } } catch (IndexOutOfBoundsException e) { - return; + if(isTextBlock) { + // Pull it back to the first closing brace after the beginning + // of the unclosed text block and let recovery take over. + if (firstClosingBrace > 0) { + this.currentPosition = firstClosingBrace - 1; + } + } } break NextToken; case '/' : @@ -4076,6 +4225,7 @@ public static boolean isLiteral(int token) { case TerminalTokens.TokenNameFloatingPointLiteral: case TerminalTokens.TokenNameDoubleLiteral: case TerminalTokens.TokenNameStringLiteral: + case TerminalTokens.TokenNameTextBlock: case TerminalTokens.TokenNameCharacterLiteral: return true; default: |