Fri, 13 May 2016 18:38:15 +0200
8156714: Parsing issue with automatic semicolon insertion
Reviewed-by: jlaskey, sundar
jlaskey@3 | 1 | /* |
jlaskey@7 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jlaskey@3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jlaskey@3 | 4 | * |
jlaskey@3 | 5 | * This code is free software; you can redistribute it and/or modify it |
jlaskey@3 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jlaskey@3 | 7 | * published by the Free Software Foundation. Oracle designates this |
jlaskey@3 | 8 | * particular file as subject to the "Classpath" exception as provided |
jlaskey@3 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
jlaskey@3 | 10 | * |
jlaskey@3 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jlaskey@3 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jlaskey@3 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jlaskey@3 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
jlaskey@3 | 15 | * accompanied this code). |
jlaskey@3 | 16 | * |
jlaskey@3 | 17 | * You should have received a copy of the GNU General Public License version |
jlaskey@3 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
jlaskey@3 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jlaskey@3 | 20 | * |
jlaskey@3 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
jlaskey@3 | 22 | * or visit www.oracle.com if you need additional information or have any |
jlaskey@3 | 23 | * questions. |
jlaskey@3 | 24 | */ |
jlaskey@3 | 25 | |
jlaskey@3 | 26 | package jdk.nashorn.internal.parser; |
jlaskey@3 | 27 | |
jlaskey@460 | 28 | import static jdk.nashorn.internal.parser.TokenType.COMMENT; |
sundar@761 | 29 | import static jdk.nashorn.internal.parser.TokenType.DIRECTIVE_COMMENT; |
jlaskey@3 | 30 | import static jdk.nashorn.internal.parser.TokenType.EOF; |
jlaskey@3 | 31 | import static jdk.nashorn.internal.parser.TokenType.EOL; |
jlaskey@3 | 32 | import static jdk.nashorn.internal.parser.TokenType.IDENT; |
attila@975 | 33 | import java.util.HashMap; |
attila@975 | 34 | import java.util.Map; |
jlaskey@3 | 35 | import jdk.nashorn.internal.ir.IdentNode; |
jlaskey@3 | 36 | import jdk.nashorn.internal.ir.LiteralNode; |
jlaskey@3 | 37 | import jdk.nashorn.internal.parser.Lexer.LexerToken; |
jlaskey@3 | 38 | import jdk.nashorn.internal.parser.Lexer.RegexToken; |
jlaskey@3 | 39 | import jdk.nashorn.internal.runtime.ECMAErrors; |
jlaskey@3 | 40 | import jdk.nashorn.internal.runtime.ErrorManager; |
jlaskey@3 | 41 | import jdk.nashorn.internal.runtime.JSErrorType; |
jlaskey@3 | 42 | import jdk.nashorn.internal.runtime.ParserException; |
attila@235 | 43 | import jdk.nashorn.internal.runtime.Source; |
hannesw@114 | 44 | import jdk.nashorn.internal.runtime.regexp.RegExpFactory; |
jlaskey@3 | 45 | |
jlaskey@3 | 46 | /** |
jlaskey@3 | 47 | * Base class for parsers. |
jlaskey@3 | 48 | */ |
jlaskey@3 | 49 | public abstract class AbstractParser { |
jlaskey@3 | 50 | /** Source to parse. */ |
jlaskey@3 | 51 | protected final Source source; |
jlaskey@3 | 52 | |
jlaskey@3 | 53 | /** Error manager to report errors. */ |
jlaskey@3 | 54 | protected final ErrorManager errors; |
jlaskey@3 | 55 | |
jlaskey@3 | 56 | /** Stream of lex tokens to parse. */ |
jlaskey@3 | 57 | protected TokenStream stream; |
jlaskey@3 | 58 | |
jlaskey@3 | 59 | /** Index of current token. */ |
jlaskey@3 | 60 | protected int k; |
jlaskey@3 | 61 | |
attila@963 | 62 | /** Previous token - accessible to sub classes */ |
attila@963 | 63 | protected long previousToken; |
attila@963 | 64 | |
jlaskey@3 | 65 | /** Descriptor of current token. */ |
jlaskey@3 | 66 | protected long token; |
jlaskey@3 | 67 | |
jlaskey@3 | 68 | /** Type of current token. */ |
jlaskey@3 | 69 | protected TokenType type; |
jlaskey@3 | 70 | |
jlaskey@3 | 71 | /** Type of last token. */ |
jlaskey@3 | 72 | protected TokenType last; |
jlaskey@3 | 73 | |
jlaskey@3 | 74 | /** Start position of current token. */ |
jlaskey@3 | 75 | protected int start; |
jlaskey@3 | 76 | |
jlaskey@3 | 77 | /** Finish position of previous token. */ |
jlaskey@3 | 78 | protected int finish; |
jlaskey@3 | 79 | |
jlaskey@3 | 80 | /** Current line number. */ |
jlaskey@3 | 81 | protected int line; |
jlaskey@3 | 82 | |
jlaskey@3 | 83 | /** Position of last EOL + 1. */ |
jlaskey@3 | 84 | protected int linePosition; |
jlaskey@3 | 85 | |
jlaskey@3 | 86 | /** Lexer used to scan source content. */ |
jlaskey@3 | 87 | protected Lexer lexer; |
jlaskey@3 | 88 | |
jlaskey@3 | 89 | /** Is this parser running under strict mode? */ |
jlaskey@3 | 90 | protected boolean isStrictMode; |
jlaskey@3 | 91 | |
attila@963 | 92 | /** What should line numbers be counted from? */ |
attila@963 | 93 | protected final int lineOffset; |
sundar@761 | 94 | |
attila@975 | 95 | private final Map<String, String> canonicalNames = new HashMap<>(); |
attila@975 | 96 | |
jlaskey@3 | 97 | /** |
jlaskey@3 | 98 | * Construct a parser. |
jlaskey@3 | 99 | * |
attila@963 | 100 | * @param source Source to parse. |
attila@963 | 101 | * @param errors Error reporting manager. |
attila@963 | 102 | * @param strict True if we are in strict mode |
attila@963 | 103 | * @param lineOffset Offset from which lines should be counted |
jlaskey@3 | 104 | */ |
attila@963 | 105 | protected AbstractParser(final Source source, final ErrorManager errors, final boolean strict, final int lineOffset) { |
jlaskey@3 | 106 | this.source = source; |
jlaskey@3 | 107 | this.errors = errors; |
jlaskey@3 | 108 | this.k = -1; |
jlaskey@3 | 109 | this.token = Token.toDesc(EOL, 0, 1); |
jlaskey@3 | 110 | this.type = EOL; |
jlaskey@3 | 111 | this.last = EOL; |
jlaskey@3 | 112 | this.isStrictMode = strict; |
attila@963 | 113 | this.lineOffset = lineOffset; |
jlaskey@3 | 114 | } |
jlaskey@3 | 115 | |
jlaskey@3 | 116 | /** |
jlaskey@3 | 117 | * Get the ith token. |
jlaskey@3 | 118 | * |
jlaskey@3 | 119 | * @param i Index of token. |
jlaskey@3 | 120 | * |
jlaskey@3 | 121 | * @return the token |
jlaskey@3 | 122 | */ |
jlaskey@3 | 123 | protected final long getToken(final int i) { |
jlaskey@3 | 124 | // Make sure there are enough tokens available. |
jlaskey@3 | 125 | while (i > stream.last()) { |
jlaskey@3 | 126 | // If we need to buffer more for lookahead. |
jlaskey@3 | 127 | if (stream.isFull()) { |
jlaskey@3 | 128 | stream.grow(); |
jlaskey@3 | 129 | } |
jlaskey@3 | 130 | |
jlaskey@3 | 131 | // Get more tokens. |
jlaskey@3 | 132 | lexer.lexify(); |
jlaskey@3 | 133 | } |
jlaskey@3 | 134 | |
jlaskey@3 | 135 | return stream.get(i); |
jlaskey@3 | 136 | } |
jlaskey@3 | 137 | |
jlaskey@3 | 138 | /** |
jlaskey@3 | 139 | * Return the tokenType of the ith token. |
jlaskey@3 | 140 | * |
jlaskey@3 | 141 | * @param i Index of token |
jlaskey@3 | 142 | * |
jlaskey@3 | 143 | * @return the token type |
jlaskey@3 | 144 | */ |
jlaskey@3 | 145 | protected final TokenType T(final int i) { |
jlaskey@3 | 146 | // Get token descriptor and extract tokenType. |
jlaskey@3 | 147 | return Token.descType(getToken(i)); |
jlaskey@3 | 148 | } |
jlaskey@3 | 149 | |
jlaskey@3 | 150 | /** |
jlaskey@460 | 151 | * Seek next token that is not an EOL or comment. |
jlaskey@3 | 152 | * |
jlaskey@3 | 153 | * @return tokenType of next token. |
jlaskey@3 | 154 | */ |
jlaskey@3 | 155 | protected final TokenType next() { |
jlaskey@3 | 156 | do { |
jlaskey@3 | 157 | nextOrEOL(); |
jlaskey@460 | 158 | } while (type == EOL || type == COMMENT); |
jlaskey@460 | 159 | |
jlaskey@460 | 160 | return type; |
jlaskey@460 | 161 | } |
jlaskey@460 | 162 | |
jlaskey@460 | 163 | /** |
jlaskey@460 | 164 | * Seek next token or EOL (skipping comments.) |
jlaskey@460 | 165 | * |
jlaskey@460 | 166 | * @return tokenType of next token. |
jlaskey@460 | 167 | */ |
jlaskey@460 | 168 | protected final TokenType nextOrEOL() { |
jlaskey@460 | 169 | do { |
jlaskey@460 | 170 | nextToken(); |
sundar@761 | 171 | if (type == DIRECTIVE_COMMENT) { |
sundar@761 | 172 | checkDirectiveComment(); |
sundar@761 | 173 | } |
sundar@761 | 174 | } while (type == COMMENT || type == DIRECTIVE_COMMENT); |
jlaskey@3 | 175 | |
jlaskey@3 | 176 | return type; |
jlaskey@3 | 177 | } |
jlaskey@3 | 178 | |
sundar@761 | 179 | // sourceURL= after directive comment |
sundar@761 | 180 | private static final String SOURCE_URL_PREFIX = "sourceURL="; |
sundar@761 | 181 | |
sundar@761 | 182 | // currently only @sourceURL=foo supported |
sundar@761 | 183 | private void checkDirectiveComment() { |
sundar@761 | 184 | // if already set, ignore this one |
attila@963 | 185 | if (source.getExplicitURL() != null) { |
sundar@761 | 186 | return; |
sundar@761 | 187 | } |
sundar@761 | 188 | |
sundar@761 | 189 | final String comment = (String) lexer.getValueOf(token, isStrictMode); |
sundar@761 | 190 | final int len = comment.length(); |
sundar@761 | 191 | // 4 characters for directive comment marker //@\s or //#\s |
sundar@761 | 192 | if (len > 4 && comment.substring(4).startsWith(SOURCE_URL_PREFIX)) { |
attila@963 | 193 | source.setExplicitURL(comment.substring(4 + SOURCE_URL_PREFIX.length())); |
sundar@761 | 194 | } |
sundar@761 | 195 | } |
sundar@761 | 196 | |
jlaskey@3 | 197 | /** |
jlaskey@3 | 198 | * Seek next token. |
jlaskey@3 | 199 | * |
jlaskey@3 | 200 | * @return tokenType of next token. |
jlaskey@3 | 201 | */ |
sundar@761 | 202 | private TokenType nextToken() { |
hannesw@1832 | 203 | // Capture last token type, but ignore comments (which are irrelevant for the purpose of newline detection). |
hannesw@1832 | 204 | if (type != COMMENT) { |
hannesw@1832 | 205 | last = type; |
hannesw@1832 | 206 | } |
jlaskey@3 | 207 | if (type != EOF) { |
jlaskey@3 | 208 | |
jlaskey@3 | 209 | // Set up next token. |
jlaskey@3 | 210 | k++; |
jlaskey@3 | 211 | final long lastToken = token; |
attila@963 | 212 | previousToken = token; |
jlaskey@3 | 213 | token = getToken(k); |
jlaskey@3 | 214 | type = Token.descType(token); |
jlaskey@3 | 215 | |
jlaskey@3 | 216 | // do this before the start is changed below |
jlaskey@3 | 217 | if (last != EOL) { |
jlaskey@3 | 218 | finish = start + Token.descLength(lastToken); |
jlaskey@3 | 219 | } |
jlaskey@3 | 220 | |
jlaskey@3 | 221 | if (type == EOL) { |
attila@963 | 222 | line = Token.descLength(token); |
jlaskey@3 | 223 | linePosition = Token.descPosition(token); |
jlaskey@3 | 224 | } else { |
jlaskey@3 | 225 | start = Token.descPosition(token); |
jlaskey@3 | 226 | } |
jlaskey@3 | 227 | |
jlaskey@3 | 228 | } |
jlaskey@3 | 229 | |
jlaskey@3 | 230 | return type; |
jlaskey@3 | 231 | } |
jlaskey@3 | 232 | |
jlaskey@3 | 233 | /** |
jlaskey@3 | 234 | * Get the message string for a message ID and arguments |
jlaskey@3 | 235 | * |
jlaskey@3 | 236 | * @param msgId The Message ID |
jlaskey@3 | 237 | * @param args The arguments |
jlaskey@3 | 238 | * |
jlaskey@3 | 239 | * @return The message string |
jlaskey@3 | 240 | */ |
jlaskey@3 | 241 | protected static String message(final String msgId, final String... args) { |
jlaskey@3 | 242 | return ECMAErrors.getMessage("parser.error." + msgId, args); |
jlaskey@3 | 243 | } |
jlaskey@3 | 244 | |
jlaskey@3 | 245 | /** |
jlaskey@3 | 246 | * Report an error. |
jlaskey@3 | 247 | * |
jlaskey@3 | 248 | * @param message Error message. |
jlaskey@3 | 249 | * @param errorToken Offending token. |
lagergren@211 | 250 | * @return ParserException upon failure. Caller should throw and not ignore |
jlaskey@3 | 251 | */ |
lagergren@211 | 252 | protected final ParserException error(final String message, final long errorToken) { |
lagergren@211 | 253 | return error(JSErrorType.SYNTAX_ERROR, message, errorToken); |
jlaskey@3 | 254 | } |
jlaskey@3 | 255 | |
jlaskey@3 | 256 | /** |
jlaskey@3 | 257 | * Report an error. |
jlaskey@3 | 258 | * |
jlaskey@3 | 259 | * @param errorType The error type |
jlaskey@3 | 260 | * @param message Error message. |
jlaskey@3 | 261 | * @param errorToken Offending token. |
lagergren@211 | 262 | * @return ParserException upon failure. Caller should throw and not ignore |
jlaskey@3 | 263 | */ |
lagergren@211 | 264 | protected final ParserException error(final JSErrorType errorType, final String message, final long errorToken) { |
jlaskey@3 | 265 | final int position = Token.descPosition(errorToken); |
jlaskey@3 | 266 | final int lineNum = source.getLine(position); |
jlaskey@3 | 267 | final int columnNum = source.getColumn(position); |
jlaskey@3 | 268 | final String formatted = ErrorManager.format(message, source, lineNum, columnNum, errorToken); |
lagergren@211 | 269 | return new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken); |
jlaskey@3 | 270 | } |
jlaskey@3 | 271 | |
jlaskey@3 | 272 | /** |
jlaskey@3 | 273 | * Report an error. |
jlaskey@3 | 274 | * |
jlaskey@3 | 275 | * @param message Error message. |
lagergren@211 | 276 | * @return ParserException upon failure. Caller should throw and not ignore |
jlaskey@3 | 277 | */ |
lagergren@211 | 278 | protected final ParserException error(final String message) { |
lagergren@211 | 279 | return error(JSErrorType.SYNTAX_ERROR, message); |
jlaskey@3 | 280 | } |
jlaskey@3 | 281 | |
jlaskey@3 | 282 | /** |
jlaskey@3 | 283 | * Report an error. |
jlaskey@3 | 284 | * |
jlaskey@3 | 285 | * @param errorType The error type |
jlaskey@3 | 286 | * @param message Error message. |
lagergren@211 | 287 | * @return ParserException upon failure. Caller should throw and not ignore |
jlaskey@3 | 288 | */ |
lagergren@211 | 289 | protected final ParserException error(final JSErrorType errorType, final String message) { |
jlaskey@3 | 290 | // TODO - column needs to account for tabs. |
jlaskey@3 | 291 | final int position = Token.descPosition(token); |
jlaskey@3 | 292 | final int column = position - linePosition; |
jlaskey@3 | 293 | final String formatted = ErrorManager.format(message, source, line, column, token); |
lagergren@211 | 294 | return new ParserException(errorType, formatted, source, line, column, token); |
jlaskey@3 | 295 | } |
jlaskey@3 | 296 | |
jlaskey@3 | 297 | /** |
attila@235 | 298 | * Report a warning to the error manager. |
attila@235 | 299 | * |
attila@235 | 300 | * @param errorType The error type of the warning |
attila@235 | 301 | * @param message Warning message. |
lagergren@247 | 302 | * @param errorToken error token |
attila@235 | 303 | */ |
attila@235 | 304 | protected final void warning(final JSErrorType errorType, final String message, final long errorToken) { |
attila@235 | 305 | errors.warning(error(errorType, message, errorToken)); |
attila@235 | 306 | } |
attila@235 | 307 | |
attila@235 | 308 | /** |
jlaskey@3 | 309 | * Generate 'expected' message. |
jlaskey@3 | 310 | * |
jlaskey@3 | 311 | * @param expected Expected tokenType. |
jlaskey@3 | 312 | * |
jlaskey@3 | 313 | * @return the message string |
jlaskey@3 | 314 | */ |
jlaskey@3 | 315 | protected final String expectMessage(final TokenType expected) { |
sundar@97 | 316 | final String tokenString = Token.toString(source, token); |
jlaskey@3 | 317 | String msg; |
jlaskey@3 | 318 | |
jlaskey@3 | 319 | if (expected == null) { |
jlaskey@3 | 320 | msg = AbstractParser.message("expected.stmt", tokenString); |
jlaskey@3 | 321 | } else { |
jlaskey@3 | 322 | final String expectedName = expected.getNameOrType(); |
jlaskey@3 | 323 | msg = AbstractParser.message("expected", expectedName, tokenString); |
jlaskey@3 | 324 | } |
jlaskey@3 | 325 | |
jlaskey@3 | 326 | return msg; |
jlaskey@3 | 327 | } |
jlaskey@3 | 328 | |
jlaskey@3 | 329 | /** |
attila@998 | 330 | * Check current token and advance to the next token. |
jlaskey@3 | 331 | * |
jlaskey@3 | 332 | * @param expected Expected tokenType. |
jlaskey@3 | 333 | * |
jlaskey@3 | 334 | * @throws ParserException on unexpected token type |
jlaskey@3 | 335 | */ |
jlaskey@3 | 336 | protected final void expect(final TokenType expected) throws ParserException { |
attila@998 | 337 | expectDontAdvance(expected); |
attila@998 | 338 | next(); |
attila@998 | 339 | } |
attila@998 | 340 | |
attila@998 | 341 | /** |
attila@998 | 342 | * Check current token, but don't advance to the next token. |
attila@998 | 343 | * |
attila@998 | 344 | * @param expected Expected tokenType. |
attila@998 | 345 | * |
attila@998 | 346 | * @throws ParserException on unexpected token type |
attila@998 | 347 | */ |
attila@998 | 348 | protected final void expectDontAdvance(final TokenType expected) throws ParserException { |
jlaskey@3 | 349 | if (type != expected) { |
lagergren@211 | 350 | throw error(expectMessage(expected)); |
jlaskey@3 | 351 | } |
jlaskey@3 | 352 | } |
jlaskey@3 | 353 | |
jlaskey@3 | 354 | /** |
jlaskey@3 | 355 | * Check next token, get its value and advance. |
jlaskey@3 | 356 | * |
jlaskey@3 | 357 | * @param expected Expected tokenType. |
jlaskey@3 | 358 | * @return The JavaScript value of the token |
jlaskey@3 | 359 | * @throws ParserException on unexpected token type |
jlaskey@3 | 360 | */ |
jlaskey@3 | 361 | protected final Object expectValue(final TokenType expected) throws ParserException { |
jlaskey@3 | 362 | if (type != expected) { |
lagergren@211 | 363 | throw error(expectMessage(expected)); |
jlaskey@3 | 364 | } |
jlaskey@3 | 365 | |
jlaskey@3 | 366 | final Object value = getValue(); |
jlaskey@3 | 367 | |
jlaskey@3 | 368 | next(); |
jlaskey@3 | 369 | |
jlaskey@3 | 370 | return value; |
jlaskey@3 | 371 | } |
jlaskey@3 | 372 | |
jlaskey@3 | 373 | /** |
jlaskey@3 | 374 | * Get the value of the current token. |
jlaskey@3 | 375 | * |
jlaskey@3 | 376 | * @return JavaScript value of the token. |
jlaskey@3 | 377 | */ |
jlaskey@3 | 378 | protected final Object getValue() { |
jlaskey@3 | 379 | return getValue(token); |
jlaskey@3 | 380 | } |
jlaskey@3 | 381 | |
jlaskey@3 | 382 | /** |
jlaskey@3 | 383 | * Get the value of a specific token |
jlaskey@3 | 384 | * |
jlaskey@3 | 385 | * @param valueToken the token |
jlaskey@3 | 386 | * |
jlaskey@3 | 387 | * @return JavaScript value of the token |
jlaskey@3 | 388 | */ |
jlaskey@3 | 389 | protected final Object getValue(final long valueToken) { |
jlaskey@3 | 390 | try { |
jlaskey@3 | 391 | return lexer.getValueOf(valueToken, isStrictMode); |
jlaskey@3 | 392 | } catch (final ParserException e) { |
jlaskey@3 | 393 | errors.error(e); |
jlaskey@3 | 394 | } |
jlaskey@3 | 395 | |
jlaskey@3 | 396 | return null; |
jlaskey@3 | 397 | } |
jlaskey@3 | 398 | |
jlaskey@3 | 399 | /** |
jlaskey@3 | 400 | * Certain future reserved words can be used as identifiers in |
jlaskey@3 | 401 | * non-strict mode. Check if the current token is one such. |
jlaskey@3 | 402 | * |
jlaskey@3 | 403 | * @return true if non strict mode identifier |
jlaskey@3 | 404 | */ |
jlaskey@3 | 405 | protected final boolean isNonStrictModeIdent() { |
jlaskey@3 | 406 | return !isStrictMode && type.getKind() == TokenKind.FUTURESTRICT; |
jlaskey@3 | 407 | } |
jlaskey@3 | 408 | |
jlaskey@3 | 409 | /** |
jlaskey@3 | 410 | * Get ident. |
jlaskey@3 | 411 | * |
jlaskey@3 | 412 | * @return Ident node. |
jlaskey@3 | 413 | */ |
jlaskey@3 | 414 | protected final IdentNode getIdent() { |
jlaskey@3 | 415 | // Capture IDENT token. |
jlaskey@3 | 416 | long identToken = token; |
jlaskey@3 | 417 | |
jlaskey@3 | 418 | if (isNonStrictModeIdent()) { |
jlaskey@3 | 419 | // Fake out identifier. |
jlaskey@3 | 420 | identToken = Token.recast(token, IDENT); |
jlaskey@3 | 421 | // Get IDENT. |
jlaskey@3 | 422 | final String ident = (String)getValue(identToken); |
jlaskey@3 | 423 | |
jlaskey@3 | 424 | next(); |
jlaskey@3 | 425 | |
jlaskey@3 | 426 | // Create IDENT node. |
attila@975 | 427 | return createIdentNode(identToken, finish, ident).setIsFutureStrictName(); |
jlaskey@3 | 428 | } |
jlaskey@3 | 429 | |
jlaskey@3 | 430 | // Get IDENT. |
jlaskey@3 | 431 | final String ident = (String)expectValue(IDENT); |
jlaskey@3 | 432 | if (ident == null) { |
jlaskey@3 | 433 | return null; |
jlaskey@3 | 434 | } |
jlaskey@3 | 435 | // Create IDENT node. |
attila@975 | 436 | return createIdentNode(identToken, finish, ident); |
attila@975 | 437 | } |
attila@975 | 438 | |
attila@975 | 439 | /** |
attila@975 | 440 | * Creates a new {@link IdentNode} as if invoked with a {@link IdentNode#IdentNode(long, int, String) |
attila@975 | 441 | * constructor} but making sure that the {@code name} is deduplicated within this parse job. |
attila@975 | 442 | * @param identToken the token for the new {@code IdentNode} |
attila@975 | 443 | * @param identFinish the finish for the new {@code IdentNode} |
attila@975 | 444 | * @param name the name for the new {@code IdentNode}. It will be de-duplicated. |
attila@975 | 445 | * @return a newly constructed {@code IdentNode} with the specified token, finish, and name; the name will |
attila@975 | 446 | * be deduplicated. |
attila@975 | 447 | */ |
attila@975 | 448 | protected IdentNode createIdentNode(final long identToken, final int identFinish, final String name) { |
attila@975 | 449 | final String existingName = canonicalNames.putIfAbsent(name, name); |
attila@975 | 450 | final String canonicalName = existingName != null ? existingName : name; |
attila@975 | 451 | return new IdentNode(identToken, identFinish, canonicalName); |
jlaskey@3 | 452 | } |
jlaskey@3 | 453 | |
jlaskey@3 | 454 | /** |
jlaskey@3 | 455 | * Check if current token is in identifier name |
jlaskey@3 | 456 | * |
jlaskey@3 | 457 | * @return true if current token is an identifier name |
jlaskey@3 | 458 | */ |
jlaskey@3 | 459 | protected final boolean isIdentifierName() { |
jlaskey@3 | 460 | final TokenKind kind = type.getKind(); |
jlaskey@3 | 461 | if (kind == TokenKind.KEYWORD || kind == TokenKind.FUTURE || kind == TokenKind.FUTURESTRICT) { |
jlaskey@3 | 462 | return true; |
jlaskey@3 | 463 | } |
sundar@1413 | 464 | |
sundar@1413 | 465 | // only literals allowed are null, false and true |
sundar@1413 | 466 | if (kind == TokenKind.LITERAL) { |
sundar@1413 | 467 | switch (type) { |
sundar@1413 | 468 | case FALSE: |
sundar@1413 | 469 | case NULL: |
sundar@1413 | 470 | case TRUE: |
sundar@1413 | 471 | return true; |
sundar@1413 | 472 | default: |
sundar@1413 | 473 | return false; |
sundar@1413 | 474 | } |
sundar@1413 | 475 | } |
sundar@1413 | 476 | |
jlaskey@3 | 477 | // Fake out identifier. |
jlaskey@3 | 478 | final long identToken = Token.recast(token, IDENT); |
jlaskey@3 | 479 | // Get IDENT. |
jlaskey@3 | 480 | final String ident = (String)getValue(identToken); |
jlaskey@3 | 481 | return !ident.isEmpty() && Character.isJavaIdentifierStart(ident.charAt(0)); |
jlaskey@3 | 482 | } |
jlaskey@3 | 483 | |
jlaskey@3 | 484 | /** |
jlaskey@3 | 485 | * Create an IdentNode from the current token |
jlaskey@3 | 486 | * |
jlaskey@3 | 487 | * @return an IdentNode representing the current token |
jlaskey@3 | 488 | */ |
jlaskey@3 | 489 | protected final IdentNode getIdentifierName() { |
jlaskey@3 | 490 | if (type == IDENT) { |
jlaskey@3 | 491 | return getIdent(); |
jlaskey@3 | 492 | } else if (isIdentifierName()) { |
jlaskey@3 | 493 | // Fake out identifier. |
jlaskey@3 | 494 | final long identToken = Token.recast(token, IDENT); |
jlaskey@3 | 495 | // Get IDENT. |
jlaskey@3 | 496 | final String ident = (String)getValue(identToken); |
jlaskey@3 | 497 | next(); |
jlaskey@3 | 498 | // Create IDENT node. |
attila@975 | 499 | return createIdentNode(identToken, finish, ident); |
jlaskey@3 | 500 | } else { |
jlaskey@3 | 501 | expect(IDENT); |
jlaskey@3 | 502 | return null; |
jlaskey@3 | 503 | } |
jlaskey@3 | 504 | } |
jlaskey@3 | 505 | |
jlaskey@3 | 506 | /** |
jlaskey@3 | 507 | * Create a LiteralNode from the current token |
jlaskey@3 | 508 | * |
jlaskey@3 | 509 | * @return LiteralNode representing the current token |
jlaskey@3 | 510 | * @throws ParserException if any literals fails to parse |
jlaskey@3 | 511 | */ |
jlaskey@3 | 512 | protected final LiteralNode<?> getLiteral() throws ParserException { |
jlaskey@3 | 513 | // Capture LITERAL token. |
jlaskey@3 | 514 | final long literalToken = token; |
jlaskey@3 | 515 | |
jlaskey@3 | 516 | // Create literal node. |
jlaskey@3 | 517 | final Object value = getValue(); |
jlaskey@9 | 518 | // Advance to have a correct finish |
jlaskey@9 | 519 | next(); |
jlaskey@3 | 520 | |
jlaskey@3 | 521 | LiteralNode<?> node = null; |
jlaskey@3 | 522 | |
jlaskey@3 | 523 | if (value == null) { |
lagergren@252 | 524 | node = LiteralNode.newInstance(literalToken, finish); |
jlaskey@3 | 525 | } else if (value instanceof Number) { |
lagergren@252 | 526 | node = LiteralNode.newInstance(literalToken, finish, (Number)value); |
jlaskey@3 | 527 | } else if (value instanceof String) { |
lagergren@252 | 528 | node = LiteralNode.newInstance(literalToken, finish, (String)value); |
jlaskey@3 | 529 | } else if (value instanceof LexerToken) { |
jlaskey@3 | 530 | if (value instanceof RegexToken) { |
jlaskey@3 | 531 | final RegexToken regex = (RegexToken)value; |
jlaskey@3 | 532 | try { |
hannesw@114 | 533 | RegExpFactory.validate(regex.getExpression(), regex.getOptions()); |
jlaskey@3 | 534 | } catch (final ParserException e) { |
lagergren@211 | 535 | throw error(e.getMessage()); |
jlaskey@3 | 536 | } |
jlaskey@3 | 537 | } |
lagergren@252 | 538 | node = LiteralNode.newInstance(literalToken, finish, (LexerToken)value); |
jlaskey@3 | 539 | } else { |
jlaskey@3 | 540 | assert false : "unknown type for LiteralNode: " + value.getClass(); |
jlaskey@3 | 541 | } |
jlaskey@3 | 542 | |
jlaskey@3 | 543 | return node; |
jlaskey@3 | 544 | } |
jlaskey@3 | 545 | } |