/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * John Lenz * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino; import com.google.common.base.Preconditions; import java.util.List; /** * An AST construction helper class * @author johnlenz@google.com (John Lenz) */ public class IR { private IR() {} public static Node empty() { return new Node(Token.EMPTY); } public static Node importNode(Node name, Node importSpecs, Node moduleIdentifier) { Preconditions.checkState(name.isName() || name.isEmpty(), name); Preconditions.checkState( importSpecs.isImportSpec() || importSpecs.isImportStar() || importSpecs.isEmpty(), importSpecs); Preconditions.checkState(moduleIdentifier.isString(), moduleIdentifier); return new Node(Token.IMPORT, name, importSpecs, moduleIdentifier); } public static Node importStar(String name) { return Node.newString(Token.IMPORT_STAR, name); } public static Node function(Node name, Node params, Node body) { Preconditions.checkState(name.isName()); Preconditions.checkState(params.isParamList()); Preconditions.checkState(body.isNormalBlock()); return new Node(Token.FUNCTION, name, params, body); } public static Node paramList() { return new Node(Token.PARAM_LIST); } public static Node paramList(Node param) { Preconditions.checkState(param.isName() || param.isRest()); return new Node(Token.PARAM_LIST, param); } public static Node paramList(Node ... params) { Node paramList = paramList(); for (Node param : params) { Preconditions.checkState(param.isName() || param.isRest()); paramList.addChildToBack(param); } return paramList; } public static Node paramList(List<Node> params) { Node paramList = paramList(); for (Node param : params) { Preconditions.checkState(param.isName() || param.isRest()); paramList.addChildToBack(param); } return paramList; } public static Node root(Node ... rootChildren) { Node root = new Node(Token.ROOT); for (Node child : rootChildren) { Preconditions.checkState(child.getToken() == Token.ROOT || child.getToken() == Token.SCRIPT); root.addChildToBack(child); } return root; } public static Node block() { Node block = new Node(Token.BLOCK); return block; } public static Node block(Node stmt) { Preconditions.checkState(mayBeStatement(stmt)); Node block = new Node(Token.BLOCK, stmt); return block; } public static Node block(Node ... stmts) { Node block = block(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatement(stmt)); block.addChildToBack(stmt); } return block; } public static Node block(List<Node> stmts) { Node paramList = block(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatement(stmt)); paramList.addChildToBack(stmt); } return paramList; } private static Node blockUnchecked(Node stmt) { return new Node(Token.BLOCK, stmt); } public static Node script() { // TODO(johnlenz): finish setting up the SCRIPT node Node block = new Node(Token.SCRIPT); return block; } public static Node script(Node ... stmts) { Node block = script(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatementNoReturn(stmt)); block.addChildToBack(stmt); } return block; } public static Node script(List<Node> stmts) { Node paramList = script(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatementNoReturn(stmt)); paramList.addChildToBack(stmt); } return paramList; } public static Node var(Node lhs, Node value) { return declaration(lhs, value, Token.VAR); } public static Node let(Node lhs, Node value) { return declaration(lhs, value, Token.LET); } public static Node constNode(Node lhs, Node value) { return declaration(lhs, value, Token.CONST); } public static Node var(Node lhs) { return declaration(lhs, Token.VAR); } public static Node declaration(Node lhs, Token type) { Preconditions.checkState( lhs.isName() || lhs.isDestructuringPattern() || lhs.isDestructuringLhs(), lhs); if (lhs.isDestructuringPattern()) { lhs = new Node(Token.DESTRUCTURING_LHS, lhs); } return new Node(type, lhs); } public static Node declaration(Node lhs, Node value, Token type) { if (lhs.isName()) { Preconditions.checkState(!lhs.hasChildren()); } else { Preconditions.checkState(lhs.isArrayPattern() || lhs.isObjectPattern()); lhs = new Node(Token.DESTRUCTURING_LHS, lhs); } Preconditions.checkState(mayBeExpression(value), "%s can't be an expression", value); lhs.addChildToBack(value); return new Node(type, lhs); } public static Node returnNode() { return new Node(Token.RETURN); } public static Node returnNode(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.RETURN, expr); } public static Node yield() { return new Node(Token.YIELD); } public static Node await(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.AWAIT, expr); } public static Node yield(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.YIELD, expr); } public static Node throwNode(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.THROW, expr); } public static Node exprResult(Node expr) { Preconditions.checkState(mayBeExpression(expr), expr); return new Node(Token.EXPR_RESULT, expr); } public static Node ifNode(Node cond, Node then) { Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(then.isNormalBlock()); return new Node(Token.IF, cond, then); } public static Node ifNode(Node cond, Node then, Node elseNode) { Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(then.isNormalBlock()); Preconditions.checkState(elseNode.isNormalBlock()); return new Node(Token.IF, cond, then, elseNode); } public static Node doNode(Node body, Node cond) { Preconditions.checkState(body.isNormalBlock()); Preconditions.checkState(mayBeExpression(cond)); return new Node(Token.DO, body, cond); } public static Node whileNode(Node cond, Node body) { Preconditions.checkState(body.isNormalBlock()); Preconditions.checkState(mayBeExpression(cond)); return new Node(Token.WHILE, cond, body); } public static Node forIn(Node target, Node cond, Node body) { Preconditions.checkState(target.isVar() || mayBeExpression(target)); Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(body.isNormalBlock()); return new Node(Token.FOR_IN, target, cond, body); } public static Node forNode(Node init, Node cond, Node incr, Node body) { Preconditions.checkState(init.isVar() || mayBeExpressionOrEmpty(init)); Preconditions.checkState(mayBeExpressionOrEmpty(cond)); Preconditions.checkState(mayBeExpressionOrEmpty(incr)); Preconditions.checkState(body.isNormalBlock()); return new Node(Token.FOR, init, cond, incr, body); } public static Node switchNode(Node cond, Node ... cases) { Preconditions.checkState(mayBeExpression(cond)); Node switchNode = new Node(Token.SWITCH, cond); for (Node caseNode : cases) { Preconditions.checkState(caseNode.isCase() || caseNode.isDefaultCase()); switchNode.addChildToBack(caseNode); } return switchNode; } public static Node caseNode(Node expr, Node body) { Preconditions.checkState(mayBeExpression(expr)); Preconditions.checkState(body.isNormalBlock()); body.setIsAddedBlock(true); return new Node(Token.CASE, expr, body); } public static Node defaultCase(Node body) { Preconditions.checkState(body.isNormalBlock()); body.setIsAddedBlock(true); return new Node(Token.DEFAULT_CASE, body); } public static Node label(Node name, Node stmt) { // TODO(johnlenz): additional validation here. Preconditions.checkState(name.isLabelName()); Preconditions.checkState(mayBeStatement(stmt)); Node block = new Node(Token.LABEL, name, stmt); return block; } public static Node labelName(String name) { Preconditions.checkState(!name.isEmpty()); return Node.newString(Token.LABEL_NAME, name); } public static Node tryFinally(Node tryBody, Node finallyBody) { Preconditions.checkState(tryBody.isNormalBlock()); Preconditions.checkState(finallyBody.isNormalBlock()); Node catchBody = block().useSourceInfoIfMissingFrom(tryBody); return new Node(Token.TRY, tryBody, catchBody, finallyBody); } public static Node tryCatch(Node tryBody, Node catchNode) { Preconditions.checkState(tryBody.isNormalBlock()); Preconditions.checkState(catchNode.isCatch()); Node catchBody = blockUnchecked(catchNode).useSourceInfoIfMissingFrom(catchNode); return new Node(Token.TRY, tryBody, catchBody); } public static Node tryCatchFinally( Node tryBody, Node catchNode, Node finallyBody) { Preconditions.checkState(finallyBody.isNormalBlock()); Node tryNode = tryCatch(tryBody, catchNode); tryNode.addChildToBack(finallyBody); return tryNode; } public static Node catchNode(Node expr, Node body) { Preconditions.checkState(expr.isName()); Preconditions.checkState(body.isNormalBlock()); return new Node(Token.CATCH, expr, body); } public static Node breakNode() { return new Node(Token.BREAK); } public static Node breakNode(Node name) { // TODO(johnlenz): additional validation here. Preconditions.checkState(name.isLabelName()); return new Node(Token.BREAK, name); } public static Node continueNode() { return new Node(Token.CONTINUE); } public static Node continueNode(Node name) { // TODO(johnlenz): additional validation here. Preconditions.checkState(name.isLabelName()); return new Node(Token.CONTINUE, name); } public static Node call(Node target, Node ... args) { Node call = new Node(Token.CALL, target); for (Node arg : args) { Preconditions.checkState(mayBeExpression(arg), arg); call.addChildToBack(arg); } return call; } public static Node newNode(Node target, Node ... args) { Node newcall = new Node(Token.NEW, target); for (Node arg : args) { Preconditions.checkState(mayBeExpression(arg)); newcall.addChildToBack(arg); } return newcall; } public static Node name(String name) { Preconditions.checkState(name.indexOf('.') == -1, "Invalid name '%s'. Did you mean to use NodeUtil.newQName?", name); return Node.newString(Token.NAME, name); } public static Node getprop(Node target, Node prop) { Preconditions.checkState(mayBeExpression(target)); Preconditions.checkState(prop.isString()); return new Node(Token.GETPROP, target, prop); } public static Node getprop(Node target, Node prop, Node ...moreProps) { Preconditions.checkState(mayBeExpression(target)); Preconditions.checkState(prop.isString()); Node result = new Node(Token.GETPROP, target, prop); for (Node moreProp : moreProps) { Preconditions.checkState(moreProp.isString()); result = new Node(Token.GETPROP, result, moreProp); } return result; } public static Node getprop(Node target, String prop, String ...moreProps) { Preconditions.checkState(mayBeExpression(target)); Node result = new Node(Token.GETPROP, target, IR.string(prop)); for (String moreProp : moreProps) { result = new Node(Token.GETPROP, result, IR.string(moreProp)); } return result; } public static Node getelem(Node target, Node elem) { Preconditions.checkState(mayBeExpression(target)); Preconditions.checkState(mayBeExpression(elem)); return new Node(Token.GETELEM, target, elem); } public static Node assign(Node target, Node expr) { Preconditions.checkState(target.isValidAssignmentTarget(), target); Preconditions.checkState(mayBeExpression(expr), expr); return new Node(Token.ASSIGN, target, expr); } public static Node hook(Node cond, Node trueval, Node falseval) { Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(mayBeExpression(trueval)); Preconditions.checkState(mayBeExpression(falseval)); return new Node(Token.HOOK, cond, trueval, falseval); } public static Node in(Node expr1, Node expr2) { return binaryOp(Token.IN, expr1, expr2); } public static Node comma(Node expr1, Node expr2) { return binaryOp(Token.COMMA, expr1, expr2); } public static Node and(Node expr1, Node expr2) { return binaryOp(Token.AND, expr1, expr2); } public static Node or(Node expr1, Node expr2) { return binaryOp(Token.OR, expr1, expr2); } public static Node not(Node expr1) { return unaryOp(Token.NOT, expr1); } /** * "<" */ public static Node lt(Node expr1, Node expr2) { return binaryOp(Token.LT, expr1, expr2); } /** * "==" */ public static Node eq(Node expr1, Node expr2) { return binaryOp(Token.EQ, expr1, expr2); } /** * "!=" */ public static Node ne(Node expr1, Node expr2) { return binaryOp(Token.NE, expr1, expr2); } /** * "===" */ public static Node sheq(Node expr1, Node expr2) { return binaryOp(Token.SHEQ, expr1, expr2); } /** * "!==" */ public static Node shne(Node expr1, Node expr2) { return binaryOp(Token.SHNE, expr1, expr2); } public static Node voidNode(Node expr1) { return unaryOp(Token.VOID, expr1); } public static Node neg(Node expr1) { return unaryOp(Token.NEG, expr1); } public static Node pos(Node expr1) { return unaryOp(Token.POS, expr1); } public static Node cast(Node expr1, JSDocInfo jsdoc) { Node op = unaryOp(Token.CAST, expr1); op.setJSDocInfo(jsdoc); return op; } public static Node inc(Node exp, boolean isPost) { Node op = unaryOp(Token.INC, exp); op.putBooleanProp(Node.INCRDECR_PROP, isPost); return op; } public static Node dec(Node exp, boolean isPost) { Node op = unaryOp(Token.DEC, exp); op.putBooleanProp(Node.INCRDECR_PROP, isPost); return op; } public static Node add(Node expr1, Node expr2) { return binaryOp(Token.ADD, expr1, expr2); } public static Node sub(Node expr1, Node expr2) { return binaryOp(Token.SUB, expr1, expr2); } // TODO(johnlenz): the rest of the ops // literals public static Node objectlit(Node ... propdefs) { Node objectlit = new Node(Token.OBJECTLIT); for (Node propdef : propdefs) { Preconditions.checkState( propdef.isStringKey() || propdef.isMemberFunctionDef() || propdef.isGetterDef() || propdef.isSetterDef()); if (!propdef.isStringKey()) { Preconditions.checkState(propdef.hasOneChild()); } objectlit.addChildToBack(propdef); } return objectlit; } public static Node computedProp(Node key, Node value) { Preconditions.checkState(mayBeExpression(key), key); Preconditions.checkState(mayBeExpression(value), value); return new Node(Token.COMPUTED_PROP, key, value); } // TODO(johnlenz): quoted props public static Node propdef(Node string, Node value) { Preconditions.checkState(string.isStringKey()); Preconditions.checkState(!string.hasChildren()); Preconditions.checkState(mayBeExpression(value)); string.addChildToFront(value); return string; } public static Node arraylit(Node ... exprs) { Node arraylit = new Node(Token.ARRAYLIT); for (Node expr : exprs) { Preconditions.checkState(mayBeExpressionOrEmpty(expr)); arraylit.addChildToBack(expr); } return arraylit; } public static Node regexp(Node expr) { Preconditions.checkState(expr.isString()); return new Node(Token.REGEXP, expr); } public static Node regexp(Node expr, Node flags) { Preconditions.checkState(expr.isString()); Preconditions.checkState(flags.isString()); return new Node(Token.REGEXP, expr, flags); } public static Node string(String s) { return Node.newString(s); } public static Node stringKey(String s) { return Node.newString(Token.STRING_KEY, s); } public static Node stringKey(String s, Node value) { Preconditions.checkState(mayBeExpression(value)); Node stringKey = stringKey(s); stringKey.addChildToFront(value); return stringKey; } public static Node rest(String name) { return new Node(Token.REST, name(name)); } public static Node spread(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.SPREAD, expr); } public static Node superNode() { return new Node(Token.SUPER); } public static Node memberFunctionDef(String name, Node function) { Preconditions.checkState(function.isFunction()); Node member = Node.newString(Token.MEMBER_FUNCTION_DEF, name); member.addChildToBack(function); return member; } public static Node number(double d) { return Node.newNumber(d); } public static Node thisNode() { return new Node(Token.THIS); } public static Node trueNode() { return new Node(Token.TRUE); } public static Node falseNode() { return new Node(Token.FALSE); } public static Node nullNode() { return new Node(Token.NULL); } // helper methods private static Node binaryOp(Token token, Node expr1, Node expr2) { Preconditions.checkState(mayBeExpression(expr1), expr1); Preconditions.checkState(mayBeExpression(expr2), expr2); return new Node(token, expr1, expr2); } private static Node unaryOp(Token token, Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(token, expr); } private static boolean mayBeExpressionOrEmpty(Node n) { return n.isEmpty() || mayBeExpression(n); } // NOTE: some nodes are neither statements nor expression nodes: // SCRIPT, LABEL_NAME, PARAM_LIST, CASE, DEFAULT_CASE, CATCH // GETTER_DEF, SETTER_DEF /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeStatementNoReturn(Node n) { switch (n.getToken()) { case EMPTY: case FUNCTION: // EMPTY and FUNCTION are used both in expression and statement // contexts return true; case BLOCK: case BREAK: case CLASS: case CONST: case CONTINUE: case DEBUGGER: case DO: case EXPR_RESULT: case FOR: case IF: case LABEL: case LET: case SWITCH: case THROW: case TRY: case VAR: case WHILE: case WITH: return true; default: return false; } } /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeStatement(Node n) { if (!mayBeStatementNoReturn(n)) { return n.isReturn(); } return true; } /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeExpression(Node n) { switch (n.getToken()) { case FUNCTION: case CLASS: // FUNCTION and CLASS are used both in expression and statement // contexts. return true; case ADD: case AND: case ARRAYLIT: case ASSIGN: case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_EXPONENT: case ASSIGN_DIV: case ASSIGN_MOD: case AWAIT: case BITAND: case BITOR: case BITNOT: case BITXOR: case CALL: case CAST: case COMMA: case DEC: case DELPROP: case DIV: case EQ: case EXPONENT: case FALSE: case GE: case GETPROP: case GETELEM: case GT: case HOOK: case IN: case INC: case INSTANCEOF: case LE: case LSH: case LT: case MOD: case MUL: case NAME: case NE: case NEG: case NEW: case NOT: case NUMBER: case NULL: case OBJECTLIT: case OR: case POS: case REGEXP: case RSH: case SHEQ: case SHNE: case SPREAD: case STRING: case SUB: case SUPER: case TEMPLATELIT: case TAGGED_TEMPLATELIT: case THIS: case TYPEOF: case TRUE: case URSH: case VOID: case YIELD: return true; default: return false; } } }