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.COMMENT; 29 import static jdk.nashorn.internal.parser.TokenType.EOF; 30 import static jdk.nashorn.internal.parser.TokenType.EOL; 31 import static jdk.nashorn.internal.parser.TokenType.IDENT; 32 33 import jdk.nashorn.internal.ir.IdentNode; 34 import jdk.nashorn.internal.ir.LiteralNode; 35 import jdk.nashorn.internal.parser.Lexer.LexerToken; 36 import jdk.nashorn.internal.parser.Lexer.RegexToken; 37 import jdk.nashorn.internal.runtime.ECMAErrors; 38 import jdk.nashorn.internal.runtime.ErrorManager; 39 import jdk.nashorn.internal.runtime.JSErrorType; 40 import jdk.nashorn.internal.runtime.ParserException; 41 import jdk.nashorn.internal.runtime.Source; 42 import jdk.nashorn.internal.runtime.regexp.RegExpFactory; 43 44 /** 45 * Base class for parsers. 46 */ 47 public abstract class AbstractParser { 48 /** Source to parse. */ 49 protected final Source source; 50 51 /** Error manager to report errors. */ 52 protected final ErrorManager errors; 53 54 /** Stream of lex tokens to parse. */ 55 protected TokenStream stream; 56 57 /** Index of current token. */ 58 protected int k; 59 60 /** Descriptor of current token. */ 61 protected long token; 62 63 /** Type of current token. */ 64 protected TokenType type; 65 66 /** Type of last token. */ 67 protected TokenType last; 68 69 /** Start position of current token. */ 70 protected int start; 71 72 /** Finish position of previous token. */ 73 protected int finish; 74 75 /** Current line number. */ 76 protected int line; 77 78 /** Position of last EOL + 1. */ 79 protected int linePosition; 80 81 /** Lexer used to scan source content. */ 82 protected Lexer lexer; 83 84 /** Is this parser running under strict mode? */ 85 protected boolean isStrictMode; 86 87 /** 88 * Construct a parser. 89 * 90 * @param source Source to parse. 91 * @param errors Error reporting manager. 92 * @param strict True if we are in strict mode 93 */ 94 protected AbstractParser(final Source source, final ErrorManager errors, final boolean strict) { 95 this.source = source; 96 this.errors = errors; 97 this.k = -1; 98 this.token = Token.toDesc(EOL, 0, 1); 99 this.type = EOL; 100 this.last = EOL; 101 this.isStrictMode = strict; 102 } 103 104 /** 105 * Get the ith token. 106 * 107 * @param i Index of token. 108 * 109 * @return the token 110 */ 111 protected final long getToken(final int i) { 112 // Make sure there are enough tokens available. 113 while (i > stream.last()) { 114 // If we need to buffer more for lookahead. 115 if (stream.isFull()) { 116 stream.grow(); 117 } 118 119 // Get more tokens. 120 lexer.lexify(); 121 } 122 123 return stream.get(i); 124 } 125 126 /** 127 * Return the tokenType of the ith token. 128 * 129 * @param i Index of token 130 * 131 * @return the token type 132 */ 133 protected final TokenType T(final int i) { 134 // Get token descriptor and extract tokenType. 135 return Token.descType(getToken(i)); 136 } 137 138 /** 139 * Seek next token that is not an EOL or comment. 140 * 141 * @return tokenType of next token. 142 */ 143 protected final TokenType next() { 144 do { 145 nextOrEOL(); 146 } while (type == EOL || type == COMMENT); 147 148 return type; 149 } 150 151 /** 152 * Seek next token or EOL (skipping comments.) 153 * 154 * @return tokenType of next token. 155 */ 156 protected final TokenType nextOrEOL() { 157 do { 158 nextToken(); 159 } while (type == COMMENT); 160 161 return type; 162 } 163 164 /** 165 * Seek next token. 166 * 167 * @return tokenType of next token. 168 */ 169 private final TokenType nextToken() { 170 // Capture last token tokenType. 171 last = type; 172 if (type != EOF) { 173 174 // Set up next token. 175 k++; 176 final long lastToken = token; 177 token = getToken(k); 178 type = Token.descType(token); 179 180 // do this before the start is changed below 181 if (last != EOL) { 182 finish = start + Token.descLength(lastToken); 183 } 184 185 if (type == EOL) { 186 line = Token.descLength(token); 187 linePosition = Token.descPosition(token); 188 } else { 189 start = Token.descPosition(token); 190 } 191 192 } 193 194 return type; 195 } 196 197 /** 198 * Get the message string for a message ID and arguments 199 * 200 * @param msgId The Message ID 201 * @param args The arguments 202 * 203 * @return The message string 204 */ 205 protected static String message(final String msgId, final String... args) { 206 return ECMAErrors.getMessage("parser.error." + msgId, args); 207 } 208 209 /** 210 * Report an error. 211 * 212 * @param message Error message. 213 * @param errorToken Offending token. 214 * @return ParserException upon failure. Caller should throw and not ignore 215 */ 216 protected final ParserException error(final String message, final long errorToken) { 217 return error(JSErrorType.SYNTAX_ERROR, message, errorToken); 218 } 219 220 /** 221 * Report an error. 222 * 223 * @param errorType The error type 224 * @param message Error message. 225 * @param errorToken Offending token. 226 * @return ParserException upon failure. Caller should throw and not ignore 227 */ 228 protected final ParserException error(final JSErrorType errorType, final String message, final long errorToken) { 229 final int position = Token.descPosition(errorToken); 230 final int lineNum = source.getLine(position); 231 final int columnNum = source.getColumn(position); 232 final String formatted = ErrorManager.format(message, source, lineNum, columnNum, errorToken); 233 return new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken); 234 } 235 236 /** 237 * Report an error. 238 * 239 * @param message Error message. 240 * @return ParserException upon failure. Caller should throw and not ignore 241 */ 242 protected final ParserException error(final String message) { 243 return error(JSErrorType.SYNTAX_ERROR, message); 244 } 245 246 /** 247 * Report an error. 248 * 249 * @param errorType The error type 250 * @param message Error message. 251 * @return ParserException upon failure. Caller should throw and not ignore 252 */ 253 protected final ParserException error(final JSErrorType errorType, final String message) { 254 // TODO - column needs to account for tabs. 255 final int position = Token.descPosition(token); 256 final int column = position - linePosition; 257 final String formatted = ErrorManager.format(message, source, line, column, token); 258 return new ParserException(errorType, formatted, source, line, column, token); 259 } 260 261 /** 262 * Report a warning to the error manager. 263 * 264 * @param errorType The error type of the warning 265 * @param message Warning message. 266 * @param errorToken error token 267 */ 268 protected final void warning(final JSErrorType errorType, final String message, final long errorToken) { 269 errors.warning(error(errorType, message, errorToken)); 270 } 271 272 /** 273 * Generate 'expected' message. 274 * 275 * @param expected Expected tokenType. 276 * 277 * @return the message string 278 */ 279 protected final String expectMessage(final TokenType expected) { 280 final String tokenString = Token.toString(source, token); 281 String msg; 282 283 if (expected == null) { 284 msg = AbstractParser.message("expected.stmt", tokenString); 285 } else { 286 final String expectedName = expected.getNameOrType(); 287 msg = AbstractParser.message("expected", expectedName, tokenString); 288 } 289 290 return msg; 291 } 292 293 /** 294 * Check next token and advance. 295 * 296 * @param expected Expected tokenType. 297 * 298 * @throws ParserException on unexpected token type 299 */ 300 protected final void expect(final TokenType expected) throws ParserException { 301 if (type != expected) { 302 throw error(expectMessage(expected)); 303 } 304 305 next(); 306 } 307 308 /** 309 * Check next token, get its value and advance. 310 * 311 * @param expected Expected tokenType. 312 * @return The JavaScript value of the token 313 * @throws ParserException on unexpected token type 314 */ 315 protected final Object expectValue(final TokenType expected) throws ParserException { 316 if (type != expected) { 317 throw error(expectMessage(expected)); 318 } 319 320 final Object value = getValue(); 321 322 next(); 323 324 return value; 325 } 326 327 /** 328 * Get the value of the current token. 329 * 330 * @return JavaScript value of the token. 331 */ 332 protected final Object getValue() { 333 return getValue(token); 334 } 335 336 /** 337 * Get the value of a specific token 338 * 339 * @param valueToken the token 340 * 341 * @return JavaScript value of the token 342 */ 343 protected final Object getValue(final long valueToken) { 344 try { 345 return lexer.getValueOf(valueToken, isStrictMode); 346 } catch (final ParserException e) { 347 errors.error(e); 348 } 349 350 return null; 351 } 352 353 /** 354 * Certain future reserved words can be used as identifiers in 355 * non-strict mode. Check if the current token is one such. 356 * 357 * @return true if non strict mode identifier 358 */ 359 protected final boolean isNonStrictModeIdent() { 360 return !isStrictMode && type.getKind() == TokenKind.FUTURESTRICT; 361 } 362 363 /** 364 * Get ident. 365 * 366 * @return Ident node. 367 */ 368 protected final IdentNode getIdent() { 369 // Capture IDENT token. 370 long identToken = token; 371 372 if (isNonStrictModeIdent()) { 373 // Fake out identifier. 374 identToken = Token.recast(token, IDENT); 375 // Get IDENT. 376 final String ident = (String)getValue(identToken); 377 378 next(); 379 380 // Create IDENT node. 381 return new IdentNode(identToken, finish, ident).setIsFutureStrictName(); 382 } 383 384 // Get IDENT. 385 final String ident = (String)expectValue(IDENT); 386 if (ident == null) { 387 return null; 388 } 389 // Create IDENT node. 390 return new IdentNode(identToken, finish, ident); 391 } 392 393 /** 394 * Check if current token is in identifier name 395 * 396 * @return true if current token is an identifier name 397 */ 398 protected final boolean isIdentifierName() { 399 final TokenKind kind = type.getKind(); 400 if (kind == TokenKind.KEYWORD || kind == TokenKind.FUTURE || kind == TokenKind.FUTURESTRICT) { 401 return true; 402 } 403 // Fake out identifier. 404 final long identToken = Token.recast(token, IDENT); 405 // Get IDENT. 406 final String ident = (String)getValue(identToken); 407 return !ident.isEmpty() && Character.isJavaIdentifierStart(ident.charAt(0)); 408 } 409 410 /** 411 * Create an IdentNode from the current token 412 * 413 * @return an IdentNode representing the current token 414 */ 415 protected final IdentNode getIdentifierName() { 416 if (type == IDENT) { 417 return getIdent(); 418 } else if (isIdentifierName()) { 419 // Fake out identifier. 420 final long identToken = Token.recast(token, IDENT); 421 // Get IDENT. 422 final String ident = (String)getValue(identToken); 423 next(); 424 // Create IDENT node. 425 return new IdentNode(identToken, finish, ident); 426 } else { 427 expect(IDENT); 428 return null; 429 } 430 } 431 432 /** 433 * Create a LiteralNode from the current token 434 * 435 * @return LiteralNode representing the current token 436 * @throws ParserException if any literals fails to parse 437 */ 438 protected final LiteralNode<?> getLiteral() throws ParserException { 439 // Capture LITERAL token. 440 final long literalToken = token; 441 442 // Create literal node. 443 final Object value = getValue(); 444 // Advance to have a correct finish 445 next(); 446 447 LiteralNode<?> node = null; 448 449 if (value == null) { 450 node = LiteralNode.newInstance(literalToken, finish); 451 } else if (value instanceof Number) { 452 node = LiteralNode.newInstance(literalToken, finish, (Number)value); 453 } else if (value instanceof String) { 454 node = LiteralNode.newInstance(literalToken, finish, (String)value); 455 } else if (value instanceof LexerToken) { 456 if (value instanceof RegexToken) { 457 final RegexToken regex = (RegexToken)value; 458 try { 459 RegExpFactory.validate(regex.getExpression(), regex.getOptions()); 460 } catch (final ParserException e) { 461 throw error(e.getMessage()); 462 } 463 } 464 node = LiteralNode.newInstance(literalToken, finish, (LexerToken)value); 465 } else { 466 assert false : "unknown type for LiteralNode: " + value.getClass(); 467 } 468 469 return node; 470 } 471 }