private Node transformNewExpr(NewExpression node) { decompiler.addToken(Token.NEW); Node nx = createCallOrNew(Token.NEW, transform(node.getTarget())); nx.setLineno(node.getLineno()); List<AstNode> args = node.getArguments(); decompiler.addToken(Token.LP); for (int i = 0; i < args.size(); i++) { AstNode arg = args.get(i); nx.addChildToBack(transform(arg)); if (i < args.size() - 1) { decompiler.addToken(Token.COMMA); } } decompiler.addToken(Token.RP); if (node.getInitializer() != null) { nx.addChildToBack(transformObjectLiteral(node.getInitializer())); } return nx; }
private Node transformParenExpr(ParenthesizedExpression node) { AstNode expr = node.getExpression(); decompiler.addToken(Token.LP); int count = 1; while (expr instanceof ParenthesizedExpression) { decompiler.addToken(Token.LP); count++; expr = ((ParenthesizedExpression)expr).getExpression(); } Node result = transform(expr); for (int i = 0; i < count; i++) { decompiler.addToken(Token.RP); } result.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE); return result; }
private Node transformReturn(ReturnStatement node) { boolean expClosure = Boolean.TRUE.equals(node.getProp(Node.EXPRESSION_CLOSURE_PROP)); boolean isArrow = Boolean.TRUE.equals(node.getProp(Node.ARROW_FUNCTION_PROP)); if (expClosure) { if (!isArrow) { decompiler.addName(" "); } } else { decompiler.addToken(Token.RETURN); } AstNode rv = node.getReturnValue(); Node value = rv == null ? null : transform(rv); if (!expClosure) decompiler.addEOL(Token.SEMI); return rv == null ? new Node(Token.RETURN, node.getLineno()) : new Node(Token.RETURN, value, node.getLineno()); }
private void autoInsertSemicolon(AstNode pn) throws IOException { int ttFlagged = peekFlaggedToken(); int pos = pn.getPosition(); switch (ttFlagged & CLEAR_TI_MASK) { case Token.SEMI: // Consume ';' as a part of expression consumeToken(); // extend the node bounds to include the semicolon. pn.setLength(ts.tokenEnd - pos); break; case Token.ERROR: case Token.EOF: case Token.RC: // Autoinsert ; warnMissingSemi(pos, nodeEnd(pn)); break; default: if ((ttFlagged & TI_AFTER_EOL) == 0) { // Report error if no EOL or autoinsert ; otherwise reportError("msg.no.semi.stmt"); } else { warnMissingSemi(pos, nodeEnd(pn)); } break; } }
private IfStatement ifStatement() throws IOException { if (currentToken != Token.IF) codeBug(); consumeToken(); int pos = ts.tokenBeg, lineno = ts.lineno, elsePos = -1; ConditionData data = condition(); AstNode ifTrue = statement(), ifFalse = null; if (matchToken(Token.ELSE)) { elsePos = ts.tokenBeg - pos; ifFalse = statement(); } int end = getNodeEnd(ifFalse != null ? ifFalse : ifTrue); IfStatement pn = new IfStatement(pos, end - pos); pn.setCondition(data.condition); pn.setParens(data.lp - pos, data.rp - pos); pn.setThenPart(ifTrue); pn.setElsePart(ifFalse); pn.setElsePosition(elsePos); pn.setLineno(lineno); return pn; }
private WhileLoop whileLoop() throws IOException { if (currentToken != Token.WHILE) codeBug(); consumeToken(); int pos = ts.tokenBeg; WhileLoop pn = new WhileLoop(pos); pn.setLineno(ts.lineno); enterLoop(pn); try { ConditionData data = condition(); pn.setCondition(data.condition); pn.setParens(data.lp - pos, data.rp - pos); AstNode body = statement(); pn.setLength(getNodeEnd(body) - pos); pn.setBody(body); } finally { exitLoop(); } return pn; }
private ThrowStatement throwStatement() throws IOException { if (currentToken != Token.THROW) codeBug(); consumeToken(); int pos = ts.tokenBeg, lineno = ts.lineno; if (peekTokenOrEOL() == Token.EOL) { // ECMAScript does not allow new lines before throw expression, // see bug 256617 reportError("msg.bad.throw.eol"); } AstNode expr = expr(); ThrowStatement pn = new ThrowStatement(pos, getNodeEnd(expr), expr); pn.setLineno(lineno); return pn; }
/** * Xml attribute expression:<p> * {@code @attr}, {@code @ns::attr}, {@code @ns::*}, {@code @ns::*}, * {@code @*}, {@code @*::attr}, {@code @*::*}, {@code @ns::[expr]}, * {@code @*::[expr]}, {@code @[expr]} <p> * Called if we peeked an '@' token. */ private AstNode attributeAccess() throws IOException { int tt = nextToken(), atPos = ts.tokenBeg; switch (tt) { // handles: @name, @ns::name, @ns::*, @ns::[expr] case Token.NAME: return propertyName(atPos, ts.getString(), 0); // handles: @*, @*::name, @*::*, @*::[expr] case Token.MUL: saveNameTokenData(ts.tokenBeg, "*", ts.lineno); return propertyName(atPos, "*", 0); // handles @[expr] case Token.LB: return xmlElemRef(atPos, null, -1); default: reportError("msg.no.name.after.xmlAttr"); return makeErrorNode(); } }
/** * Parse the [expr] portion of an xml element reference, e.g. * @[expr], @*::[expr], or ns::[expr]. */ private XmlElemRef xmlElemRef(int atPos, Name namespace, int colonPos) throws IOException { int lb = ts.tokenBeg, rb = -1, pos = atPos != -1 ? atPos : lb; AstNode expr = expr(); int end = getNodeEnd(expr); if (mustMatchToken(Token.RB, "msg.no.bracket.index")) { rb = ts.tokenBeg; end = ts.tokenEnd; } XmlElemRef ref = new XmlElemRef(pos, end - pos); ref.setNamespace(namespace); ref.setColonPos(colonPos); ref.setAtPos(atPos); ref.setExpression(expr); ref.setBrackets(lb, rb); return ref; }
private AstNode name(int ttFlagged, int tt) throws IOException { String nameString = ts.getString(); int namePos = ts.tokenBeg, nameLineno = ts.lineno; if (0 != (ttFlagged & TI_CHECK_LABEL) && peekToken() == Token.COLON) { // Do not consume colon. It is used as an unwind indicator // to return to statementHelper. Label label = new Label(namePos, ts.tokenEnd - namePos); label.setName(nameString); label.setLineno(ts.lineno); return label; } // Not a label. Unfortunately peeking the next token to check for // a colon has biffed ts.tokenBeg, ts.tokenEnd. We store the name's // bounds in instance vars and createNameNode uses them. saveNameTokenData(namePos, nameString, nameLineno); if (compilerEnv.isXmlAvailable()) { return propertyName(-1, nameString, 0); } else { return createNameNode(true, Token.NAME); } }
public List<String> getPropertyNames(){ List<String> propNames = new ArrayList<String>(); ObjectLiteral ol = (ObjectLiteral)this.getNode(); for (ObjectProperty op : ol.getElements()){ AstNode left = op.getLeft(); if (left instanceof Name){ propNames.add(((Name)left).getIdentifier()); } else if (left instanceof StringLiteral){ String identifier = ConstraintGenUtil.removeQuotes(((StringLiteral)left).toSource()); propNames.add(identifier); } else { System.err.println(left.getClass().getName() + " " + left.toSource()); throw new Error("unsupported case in getPropertyNames()"); } } return propNames; }
/** * Find or create a term representing a FunctionNode (function definition). The * created FunctionTerm provides methods for accessing the terms corresponding to the * function's parameters and return type. The boolean flag isMethod indicates whether * the function is a method. */ public FunctionTerm findOrCreateFunctionTerm(FunctionNode fun, FunctionTerm.FunctionKind funType) { if (!functionTerms.containsKey(fun)){ FunctionTerm var = new FunctionTerm(fun, funType); var.setReturnVariable(this.findOrCreateFunctionReturnTerm(var, fun.getParamCount())); List<AstNode> params = fun.getParams(); List<NameDeclarationTerm> paramVars = new ArrayList<NameDeclarationTerm>(); for (int i=0; i < params.size(); i++){ AstNode param = params.get(i); if (param instanceof Name){ NameDeclarationTerm paramCV = this.findOrCreateNameDeclarationTerm((Name)param); paramVars.add(paramCV); } else { throw new Error("unimplemented case in findOrCreateFunctionVariable"); } } var.setParamVariables(paramVars); functionTerms.put(fun, var); } return functionTerms.get(fun); }
@Override public boolean visit(AstNode node) { if (node instanceof PropertyGet){ PropertyGet pg = (PropertyGet)node; AstNode target = pg.getTarget(); String propName = pg.getProperty().getIdentifier(); if (target instanceof KeywordLiteral && ConstraintGenUtil.isThis(target)){ if (node.getParent() instanceof Assignment){ Assignment a = (Assignment)node.getParent(); if (a.getLeft() == node){ writtenProperties.add(propName); } else { readProperties.add(propName); } } else { readProperties.add(propName); } readProperties.removeAll(writtenProperties); // if something is read and written, it should only be in the written set } } else if (node instanceof FunctionNode) { // don't recurse into nested function body return false; } return true; }
public static List<String> getPropertyNames(ObjectLiteral ol){ List<String> propNames = new ArrayList<String>(); for (ObjectProperty op : ol.getElements()){ AstNode left = op.getLeft(); if (left instanceof Name){ propNames.add(((Name)left).getIdentifier()); } else if (left instanceof StringLiteral){ String identifier = ConstraintGenUtil.removeQuotes(((StringLiteral)left).toSource()); propNames.add(identifier); } else { System.err.println(left.getClass().getName() + " " + left.toSource()); throw new Error("unsupported case in getPropertyNames()"); } } return propNames; }
/** * For a function definition (FunctionNode), create constraints equating its * parameters to param(.) variables, and equate the type of the function name * to the type of the entire function definition. */ private void createFunctionNodeConstraints(FunctionNode node, ITypeTerm funTerm){ // if the function has a name, equate the types of the function node and the function name Name funName = node.getFunctionName(); if (funName != null){ ITypeTerm nameTerm = findOrCreateNameDeclarationTerm(funName); addSubTypeConstraint(funTerm, nameTerm, node.getLineno(), null); } // for a function f with params v1, v2, ... generate param(|f|,i) = |v_i| for (int i=0; i < node.getParamCount(); i++){ AstNode param = node.getParams().get(i); if (param instanceof Name){ Name name = (Name)param; ITypeTerm nameVar = findOrCreateNameDeclarationTerm(name); ITypeTerm paramVar = findOrCreateFunctionParamTerm(funTerm, i, node.getParamCount(), node.getLineno()); addTypeEqualityConstraint(paramVar, nameVar, param.getLineno(), null); } else { error("createFunctionNodeConstraints: unexpected parameter type", node); } } }
/** * call a function through a closure */ private void processClosureCall(FunctionCall fc) { AstNode target = fc.getTarget(); ITypeTerm funVar = processExpression(target); // for call foo(E_1,...,E_n), equate the type of the call to ret(foo) FunctionCallTerm callTerm = findOrCreateFunctionCallTerm(fc); callTerm.setTarget(funVar); ITypeTerm retTerm = findOrCreateFunctionReturnTerm(funVar, fc.getArguments().size(), fc.getLineno(), fc); addTypeEqualityConstraint(callTerm, retTerm, fc.getLineno(), null); // for call foo(E_1,...,E_n), generate constraints |E_i| <: Param(foo,i) for (int i=0; i < fc.getArguments().size(); i++){ AstNode arg = fc.getArguments().get(i); ITypeTerm argExp = processExpression(arg); ITypeTerm paramExp = findOrCreateFunctionParamTerm(funVar, i, fc.getArguments().size(), fc.getLineno()); processCopy(arg, argExp, paramExp, fc.getLineno(), (solution) -> subtypeError("bad argument " + shortSrc(arg) + " passed to function", solution.typeOfTerm(argExp), solution.typeOfTerm(paramExp), locationOf(arg))); } }
/** * For an ElementGet a[x], generate constraint |a[x]| = Elem(a), and return Elem(a). * Also require the index to be an integer */ private ITypeTerm processElementGet(ElementGet eg) { AstNode target = eg.getTarget(); AstNode element = eg.getElement(); ITypeTerm egTerm = findOrCreateExpressionTerm(eg); ITypeTerm targetExp = processExpression(target); // for an expression e1[e2], generate constraint Elem(|e1|) = |e1[e2]| ITypeTerm elementVariable = findOrCreateIndexedTerm(targetExp, eg.getLineno()); addTypeEqualityConstraint(elementVariable, egTerm, eg.getLineno(), null); // for arrays we want to restrict the index expression to be an integer, and // for maps, we want to restrict it to be a string. Since we don't know at // constraint generation time whether an array or map is being indexed, we // generate a constraint that requires the index expression to be equal to // the "key type" of the base expression's type. The key type for an ArrayType // is integer, and the key type for a MapType is (for now) string. ITypeTerm indexTerm = processExpression(element); ITypeTerm keyTerm = findOrCreateKeyTerm(targetExp, eg.getLineno()); addTypeEqualityConstraint(indexTerm, keyTerm, eg.getLineno(), (solution) -> typeEqualityError("wrong key type used on " + describeNode(target, targetExp, solution), solution.typeOfTerm(indexTerm), solution.typeOfTerm(keyTerm), locationOf(eg))); return egTerm; }
/** * generate constraints for return statements */ private void processReturnStatement(ReturnStatement rs) throws Error { FunctionNode fun = ConstraintGenUtil.findEnclosingFunction(rs); FunctionTerm.FunctionKind funType = ConstraintGenUtil.isConstructor(fun) ? FunctionTerm.FunctionKind.Constructor : (ConstraintGenUtil.isMethod(fun) ? FunctionTerm.FunctionKind.Method : FunctionTerm.FunctionKind.Function); ITypeTerm funTerm = findOrCreateFunctionTerm(fun, funType); ITypeTerm returnTerm = findOrCreateFunctionReturnTerm(funTerm, fun.getParamCount(), rs.getLineno(), null); AstNode exp = rs.getReturnValue(); if (exp != null){ ITypeTerm expTerm = processExpression(exp); addSubTypeConstraint(expTerm, returnTerm, rs.getLineno(), (solution) -> subtypeError("bad return value " + shortSrc(exp), solution.typeOfTerm(expTerm), solution.typeOfTerm(returnTerm), locationOf(rs))); } else { ITypeTerm voidTerm = findOrCreateTypeTerm(new VoidType(), rs.getLineno()); addTypeEqualityConstraint(voidTerm, returnTerm, rs.getLineno(), (solution) -> genericTypeError("missing return value", locationOf(rs)) .withNote("expected " + describeTypeOf(returnTerm, solution))); } }
/** * generate constraints for assignments */ private void processAssignment(Assignment a) throws Error { AstNode left = a.getLeft(); AstNode right = a.getRight(); ITypeTerm expTerm = findOrCreateExpressionTerm(a); if (left instanceof Name){ processAssignToVariable(a, left, right, expTerm); } else if (left instanceof PropertyGet) { PropertyGet pg = (PropertyGet)left; if (pg.getProperty().getIdentifier().equals("prototype")){ processAssignToPrototype(a, left, right, expTerm); } else { processAssignToProperty(a, left, right, expTerm); } processExpression(pg.getTarget()); // TEST } else if (left instanceof ElementGet){ processIndexedAssignment(a, left, right, expTerm); } else { error("unsupported LHS type in Assignment: " + left.getClass().getName(), left); } }
/** * assignment to an array element or map element: v[e] = ... */ private void processIndexedAssignment(Assignment a, AstNode left, AstNode right, ITypeTerm expTerm) throws Error { ElementGet eg = (ElementGet)left; AstNode base = eg.getTarget(); AstNode element = eg.getElement(); // generate the appropriate constraints for the expression being indexed processExpression(base); // an assignment expression has the same type as its left-hand side ITypeTerm leftTerm = findOrCreateExpressionTerm(eg); ITypeTerm rightTerm = processExpression(right); addTypeEqualityConstraint(expTerm, leftTerm, a.getLineno(), null); // require index to be of the appropriate type ITypeTerm elementTerm = processExpression(element); ITypeTerm baseTerm = findOrCreateExpressionTerm(base); addTypeEqualityConstraint(elementTerm, findOrCreateKeyTerm(baseTerm, eg.getLineno()), a.getLineno(), (solution) -> genericTypeError("indexes must be strings or ints", locationOf(element))); processCopy(right, rightTerm, leftTerm, a.getLineno(), (solution) -> subtypeError("bad assignment of " + shortSrc(right) + " to " + shortSrc(left), solution.typeOfTerm(rightTerm), solution.typeOfTerm(leftTerm), locationOf(a))); }
/** * Create constraints for an object literal. */ private ITypeTerm processObjectLiteralForObject(ObjectLiteral n) { ITypeTerm expTerm = findOrCreateObjectLiteralTerm(n); ObjectLiteral o = (ObjectLiteral)n; for (ObjectProperty prop : o.getElements()){ AstNode left = prop.getLeft(); AstNode right = prop.getRight(); if (left instanceof Name){ String identifier = ((Name)left).getIdentifier(); // for object literal o = { name_1 : exp_1, ..., name_k : exp_k } generate // a constraint |exp_i| <: prop(|o|, name_i) ITypeTerm propTerm = findOrCreatePropertyAccessTerm(expTerm, identifier, null); ITypeTerm valTerm = processExpression(right); processCopy(right, valTerm, propTerm, n.getLineno(), null); } } return expTerm; }
/** * Create constraints for a map literal. */ private ITypeTerm processObjectLiteralForMap(ObjectLiteral o) { ITypeTerm expTerm = findOrCreateMapLiteralTerm(o); for (ObjectProperty prop : o.getElements()){ AstNode left = prop.getLeft(); AstNode right = prop.getRight(); if (left instanceof StringLiteral){ // for map literal o = { name_1 : exp_1, ..., name_k : exp_k } generate // a constraint |exp_i| <: MapElem(|o|) ITypeTerm mapAccessTerm = findOrCreateIndexedTerm(expTerm, o.getLineno()); ITypeTerm valTerm = processExpression(right); processCopy(right, valTerm, mapAccessTerm, o.getLineno(), (solution) -> genericTypeError("map does not have a homogenous value type", locationOf(prop)) .withNote("map value type is " + describeTypeOf(mapAccessTerm, solution)) .withNote("key " + left.toSource() + " has type " + describeTypeOf(valTerm, solution))); } } return expTerm; }
/** * create constraint variable for the array literal. create subtype constraints between expressions * in the literal and the array's element type */ private ITypeTerm processArrayLiteral(ArrayLiteral lit) { ITypeTerm arrayTerm = findOrCreateArrayLiteralTerm(lit); ITypeTerm elemTerm = findOrCreateIndexedTerm(arrayTerm, lit.getLineno()); List<AstNode> elements = lit.getElements(); for (AstNode litElem : elements){ ITypeTerm litElemTerm = processExpression(litElem); processCopy(litElem, litElemTerm, elemTerm, lit.getLineno(), (solution) -> subtypeError("array cannot contain " + shortSrc(litElem), solution.typeOfTerm(litElemTerm), solution.typeOfTerm(elemTerm), locationOf(litElem))); } createArrayConstraints(arrayTerm, lit); return arrayTerm; }
public static TypeErrorMessage binaryOperatorMisuse(InfixExpression node, Type t1, Type t2, Type outType) { TypeErrorMessage msg = genericTypeError("misuse of binary operator " + AstNode.operatorToString(node.getOperator()) + " in \"" + shortSrc(node) + '"', locationOf(node)); if (node.getOperator() == Token.IN) { if (!unconstrained(t1) && !Types.isEqual(t1, StringType.make())) { msg = msg.withNote("left operand has type " + describeType(t1) + " instead of " + StringType.make()); } if (!unconstrained(t2) && !Types.isMapType(t2)) { msg = msg.withNote("right operand has type " + describeType(t2) + " instead of " + new MapType(new DefaultType())); } } else { if (!unconstrained(t1)) { msg = msg.withNote("left operand has type " + describeType(t1)); } if (!unconstrained(t2)) { msg = msg.withNote("right operand has type " + describeType(t2)); } } if (!unconstrained(outType)) { msg = msg.withNote("result type is " + describeType(outType)); } return msg; }
/** * conservative check that returns false only for terms that obviously do not represent methods * * TODO move this code inside ITypeTerm?? * @param t * @return */ private boolean possiblyAMethodTerm(ITypeTerm t) { if (ConstraintGenUtil.isNullUndefinedLitOrVoidOp(t)) { return false; } if (t instanceof ExpressionTerm) { ExpressionTerm et = (ExpressionTerm) t; AstNode node = et.getNode(); if (node != null) { return !(node instanceof NumberLiteral || node instanceof StringLiteral); } } return !(t instanceof ArrayLiteralTerm || t instanceof MapLiteralTerm || t instanceof ObjectLiteralTerm || t instanceof TypeConstantTerm); }
public Map<AstNode, MROMRWVariable> getExternalMROMRW() { Map<AstNode, MROMRWVariable> result = HashMapFactory.make(); term2MROMRW .keySet() .stream() .filter((term) -> { return !(term instanceof TypeConstantTerm) && term.getNode() != null && term2MROMRW.get(term).nonEmpty(); }) .forEach( (t) -> { AstNode node = t.getNode(); if (result.containsKey(node)) { assert result.get(node).sameAs( term2MROMRW.get(t)) : "something weird happened"; } else { result.put(node, term2MROMRW.get(t)); } }); return result; }
public static boolean isTrivial (String formula) { // No formulas, just one constant or one variable. final ArrayList<AstNode> result = new ArrayList<AstNode>(); parseIntoTree(formula).visit(new NodeVisitor(){ @Override public boolean visit(AstNode node) { if(node.depth()>1){ result.add(node); return false; } return true; } }); if(result.size()>0){ switch (result.get(0).getType()) { case Token.NUMBER: return true; case Token.NAME: return true; case Token.STRING: return true; default: return false; } } return false; }
private void visitOtherNode(final AstNode node) { if (node instanceof PropertyGet && node.getParent() instanceof ExpressionStatement) { if (node.getJsDoc() == null) { LOG.error("Node {} has empty comment in file:{}", node.toSource(), fileName); return; } final JsElement element = parser.parse(fileName, node.getJsDoc()); final String typedef = node.toSource(); if (isMethod(typedef, element)) { addMethodOrField(typedef, element, false); } else if (element.getType() == null) { final JsFile jFile = parseClassOrInterfaceName(typedef, element.isInterface(), element); files.put(typedef, jFile); } else { LOG.error("Type '{}' ignored in file:{}", typedef, fileName); } } }
private void print(SwitchStatement node) throws IOException { writer.append("switch").ws().append('('); print(node.getExpression()); writer.append(')').ws().append('{').indent().softNewLine(); for (SwitchCase sc : node.getCases()) { if (sc.getExpression() == null) { writer.append("default:"); } else { writer.append("case "); print(sc.getExpression()); writer.append(':'); } writer.indent().softNewLine(); if (sc.getStatements() != null) { for (AstNode stmt : sc.getStatements()) { print(stmt); writer.softNewLine(); } } writer.outdent(); } writer.append('}'); }