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 }