# HG changeset patch # User mcimadamore # Date 1322495802 0 # Node ID 9dbe313bfb742dc51caf7cb79a804ebfb3140e00 # Parent 45d0ec1e746347e20dec5f53be90f564afd8d18b 7115050: Add parser support for lambda expressions Summary: Add support for parsing lambda expressions to JavacParser Reviewed-by: jjg diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/code/Source.java --- a/langtools/src/share/classes/com/sun/tools/javac/code/Source.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/code/Source.java Mon Nov 28 15:56:42 2011 +0000 @@ -194,6 +194,9 @@ public boolean allowObjectToPrimitiveCast() { return compareTo(JDK1_7) >= 0; } + public boolean allowLambda() { + return compareTo(JDK1_8) >= 0; + } public static SourceVersion toSourceVersion(Source source) { switch(source) { case JDK1_2: diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java --- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Mon Nov 28 15:56:42 2011 +0000 @@ -1975,6 +1975,11 @@ result = check(tree, owntype, VAL, pkind, pt); } + @Override + public void visitLambda(JCLambda that) { + throw new UnsupportedOperationException("Lambda expression not supported yet"); + } + public void visitParens(JCParens tree) { Type owntype = attribTree(tree.expr, env, pkind, pt); result = check(tree, owntype, pkind, pkind, pt); diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java --- a/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/parser/JavacParser.java Mon Nov 28 15:56:42 2011 +0000 @@ -110,6 +110,8 @@ this.allowDiamond = source.allowDiamond(); this.allowMulticatch = source.allowMulticatch(); this.allowStringFolding = fac.options.getBoolean("allowStringFolding", true); + this.allowLambda = source.allowLambda() && + fac.options.isSet("allowLambda"); this.keepDocComments = keepDocComments; docComments = keepDocComments ? new HashMap() : null; this.keepLineMap = keepLineMap; @@ -166,6 +168,10 @@ */ boolean allowStringFolding; + /** Switch: should we recognize lambda expressions? + */ + boolean allowLambda; + /** Switch: should we keep docComments? */ boolean keepDocComments; @@ -203,6 +209,30 @@ token = S.token(); } + protected boolean peekToken(TokenKind tk) { + return S.token(1).kind == tk; + } + + protected boolean peekToken(TokenKind tk1, TokenKind tk2) { + return S.token(1).kind == tk1 && + S.token(2).kind == tk2; + } + + protected boolean peekToken(TokenKind tk1, TokenKind tk2, TokenKind tk3) { + return S.token(1).kind == tk1 && + S.token(2).kind == tk2 && + S.token(3).kind == tk3; + } + + protected boolean peekToken(TokenKind... kinds) { + for (int lookahead = 0 ; lookahead < kinds.length ; lookahead++) { + if (S.token(lookahead + 1).kind != kinds[lookahead]) { + return false; + } + } + return true; + } + /* ---------- error recovery -------------- */ private JCErroneous errorTree; @@ -849,6 +879,8 @@ * | [TypeArguments] THIS [Arguments] * | [TypeArguments] SUPER SuperSuffix * | NEW [TypeArguments] Creator + * | "(" Arguments ")" "->" ( Expression | Block ) + * | Ident "->" ( Expression | Block ) * | Ident { "." Ident } * [ "[" ( "]" BracketsOpt "." CLASS | Expression "]" ) * | Arguments @@ -897,48 +929,75 @@ break; case LPAREN: if (typeArgs == null && (mode & EXPR) != 0) { - nextToken(); - mode = EXPR | TYPE | NOPARAMS; - t = term3(); - if ((mode & TYPE) != 0 && token.kind == LT) { - // Could be a cast to a parameterized type - JCTree.Tag op = JCTree.Tag.LT; - int pos1 = token.pos; + if (peekToken(FINAL) || + peekToken(RPAREN) || + peekToken(IDENTIFIER, COMMA) || + peekToken(IDENTIFIER, RPAREN, ARROW)) { + //implicit n-ary lambda + t = lambdaExpressionOrStatement(true, peekToken(FINAL), pos); + break; + } else { nextToken(); - mode &= (EXPR | TYPE); - mode |= TYPEARG; - JCExpression t1 = term3(); - if ((mode & TYPE) != 0 && - (token.kind == COMMA || token.kind == GT)) { - mode = TYPE; - ListBuffer args = new ListBuffer(); - args.append(t1); - while (token.kind == COMMA) { + mode = EXPR | TYPE | NOPARAMS; + t = term3(); + if ((mode & TYPE) != 0 && token.kind == LT) { + // Could be a cast to a parameterized type + JCTree.Tag op = JCTree.Tag.LT; + int pos1 = token.pos; + nextToken(); + mode &= (EXPR | TYPE); + mode |= TYPEARG; + JCExpression t1 = term3(); + if ((mode & TYPE) != 0 && + (token.kind == COMMA || token.kind == GT)) { + mode = TYPE; + ListBuffer args = new ListBuffer(); + args.append(t1); + while (token.kind == COMMA) { + nextToken(); + args.append(typeArgument()); + } + accept(GT); + t = toP(F.at(pos1).TypeApply(t, args.toList())); + checkGenerics(); + mode = EXPR | TYPE; //could be a lambda or a method ref or a cast to a type + t = term3Rest(t, typeArgs); + if (token.kind == IDENTIFIER || token.kind == ELLIPSIS) { + //explicit lambda (w/ generic type) + mode = EXPR; + JCModifiers mods = F.at(token.pos).Modifiers(Flags.PARAMETER); + if (token.kind == ELLIPSIS) { + mods.flags = Flags.VARARGS; + t = to(F.at(token.pos).TypeArray(t)); + nextToken(); + } + t = lambdaExpressionOrStatement(variableDeclaratorId(mods, t), pos); + break; + } + } else { + Assert.check((mode & EXPR) != 0); + mode = EXPR; + JCExpression e = term2Rest(t1, TreeInfo.shiftPrec); + t = F.at(pos1).Binary(op, t, e); + t = termRest(term1Rest(term2Rest(t, TreeInfo.orPrec))); + } + } else if ((mode & TYPE) != 0 && + (token.kind == IDENTIFIER || token.kind == ELLIPSIS)) { + //explicit lambda (w/ non-generic type) + mode = EXPR; + JCModifiers mods = F.at(token.pos).Modifiers(Flags.PARAMETER); + if (token.kind == ELLIPSIS) { + mods.flags = Flags.VARARGS; + t = to(F.at(token.pos).TypeArray(t)); nextToken(); - args.append(typeArgument()); } - accept(GT); - t = toP(F.at(pos1).TypeApply(t, args.toList())); - checkGenerics(); - while (token.kind == DOT) { - nextToken(); - mode = TYPE; - t = toP(F.at(token.pos).Select(t, ident())); - t = typeArgumentsOpt(t); - } - t = bracketsOpt(toP(t)); - } else if ((mode & EXPR) != 0) { - mode = EXPR; - JCExpression e = term2Rest(t1, TreeInfo.shiftPrec); - t = F.at(pos1).Binary(op, t, e); + t = lambdaExpressionOrStatement(variableDeclaratorId(mods, t), pos); + break; + } else { t = termRest(term1Rest(term2Rest(t, TreeInfo.orPrec))); - } else { - accept(GT); } } - else { - t = termRest(term1Rest(term2Rest(t, TreeInfo.orPrec))); - } + accept(RPAREN); lastmode = mode; mode = EXPR; @@ -953,14 +1012,16 @@ case INTLITERAL: case LONGLITERAL: case FLOATLITERAL: case DOUBLELITERAL: case CHARLITERAL: case STRINGLITERAL: case TRUE: case FALSE: case NULL: - case NEW: case IDENTIFIER: case ASSERT: case ENUM: + case NEW: case IDENTIFIER: case ASSERT: case ENUM: case BYTE: case SHORT: case CHAR: case INT: case LONG: case FLOAT: case DOUBLE: case BOOLEAN: case VOID: JCExpression t1 = term3(); return F.at(pos).TypeCast(t, t1); } } - } else return illegal(); + } else { + return illegal(); + } t = toP(F.at(pos).Parens(t)); break; case THIS: @@ -1003,75 +1064,79 @@ break; case IDENTIFIER: case ASSERT: case ENUM: if (typeArgs != null) return illegal(); - t = toP(F.at(token.pos).Ident(ident())); - loop: while (true) { - pos = token.pos; - switch (token.kind) { - case LBRACKET: - nextToken(); - if (token.kind == RBRACKET) { + if ((mode & EXPR) != 0 && peekToken(ARROW)) { + t = lambdaExpressionOrStatement(false, false, pos); + } else { + t = toP(F.at(token.pos).Ident(ident())); + loop: while (true) { + pos = token.pos; + switch (token.kind) { + case LBRACKET: nextToken(); - t = bracketsOpt(t); - t = toP(F.at(pos).TypeArray(t)); - t = bracketsSuffix(t); - } else { + if (token.kind == RBRACKET) { + nextToken(); + t = bracketsOpt(t); + t = toP(F.at(pos).TypeArray(t)); + t = bracketsSuffix(t); + } else { + if ((mode & EXPR) != 0) { + mode = EXPR; + JCExpression t1 = term(); + t = to(F.at(pos).Indexed(t, t1)); + } + accept(RBRACKET); + } + break loop; + case LPAREN: if ((mode & EXPR) != 0) { mode = EXPR; - JCExpression t1 = term(); - t = to(F.at(pos).Indexed(t, t1)); + t = arguments(typeArgs, t); + typeArgs = null; } - accept(RBRACKET); - } - break loop; - case LPAREN: - if ((mode & EXPR) != 0) { - mode = EXPR; - t = arguments(typeArgs, t); - typeArgs = null; + break loop; + case DOT: + nextToken(); + int oldmode = mode; + mode &= ~NOPARAMS; + typeArgs = typeArgumentsOpt(EXPR); + mode = oldmode; + if ((mode & EXPR) != 0) { + switch (token.kind) { + case CLASS: + if (typeArgs != null) return illegal(); + mode = EXPR; + t = to(F.at(pos).Select(t, names._class)); + nextToken(); + break loop; + case THIS: + if (typeArgs != null) return illegal(); + mode = EXPR; + t = to(F.at(pos).Select(t, names._this)); + nextToken(); + break loop; + case SUPER: + mode = EXPR; + t = to(F.at(pos).Select(t, names._super)); + t = superSuffix(typeArgs, t); + typeArgs = null; + break loop; + case NEW: + if (typeArgs != null) return illegal(); + mode = EXPR; + int pos1 = token.pos; + nextToken(); + if (token.kind == LT) typeArgs = typeArguments(false); + t = innerCreator(pos1, typeArgs, t); + typeArgs = null; + break loop; + } + } + // typeArgs saved for next loop iteration. + t = toP(F.at(pos).Select(t, ident())); + break; + default: + break loop; } - break loop; - case DOT: - nextToken(); - int oldmode = mode; - mode &= ~NOPARAMS; - typeArgs = typeArgumentsOpt(EXPR); - mode = oldmode; - if ((mode & EXPR) != 0) { - switch (token.kind) { - case CLASS: - if (typeArgs != null) return illegal(); - mode = EXPR; - t = to(F.at(pos).Select(t, names._class)); - nextToken(); - break loop; - case THIS: - if (typeArgs != null) return illegal(); - mode = EXPR; - t = to(F.at(pos).Select(t, names._this)); - nextToken(); - break loop; - case SUPER: - mode = EXPR; - t = to(F.at(pos).Select(t, names._super)); - t = superSuffix(typeArgs, t); - typeArgs = null; - break loop; - case NEW: - if (typeArgs != null) return illegal(); - mode = EXPR; - int pos1 = token.pos; - nextToken(); - if (token.kind == LT) typeArgs = typeArguments(false); - t = innerCreator(pos1, typeArgs, t); - typeArgs = null; - break loop; - } - } - // typeArgs saved for next loop iteration. - t = toP(F.at(pos).Select(t, ident())); - break; - default: - break loop; } } if (typeArgs != null) illegal(); @@ -1105,6 +1170,10 @@ default: return illegal(); } + return term3Rest(t, typeArgs); + } + + JCExpression term3Rest(JCExpression t, List typeArgs) { if (typeArgs != null) illegal(); while (true) { int pos1 = token.pos; @@ -1162,6 +1231,50 @@ return toP(t); } + JCExpression lambdaExpressionOrStatement(JCVariableDecl firstParam, int pos) { + ListBuffer params = new ListBuffer(); + params.append(firstParam); + JCVariableDecl lastParam = firstParam; + while ((lastParam.mods.flags & Flags.VARARGS) == 0 && token.kind == COMMA) { + nextToken(); + params.append(lastParam = formalParameter()); + } + accept(RPAREN); + return lambdaExpressionOrStatementRest(params.toList(), pos); + } + + JCExpression lambdaExpressionOrStatement(boolean hasParens, boolean explicitParams, int pos) { + List params = explicitParams ? + formalParameters() : + implicitParameters(hasParens); + + return lambdaExpressionOrStatementRest(params, pos); + } + + JCExpression lambdaExpressionOrStatementRest(List args, int pos) { + if (token.kind != ARROW) { + //better error recovery + return F.at(pos).Erroneous(args); + } + + checkLambda(); + accept(ARROW); + + return token.kind == LBRACE ? + lambdaStatement(args, pos, pos) : + lambdaExpression(args, pos); + } + + JCExpression lambdaStatement(List args, int pos, int pos2) { + JCBlock block = block(pos2, 0); + return toP(F.at(pos).Lambda(args, block)); + } + + JCExpression lambdaExpression(List args, int pos) { + JCTree expr = parseExpression(); + return toP(F.at(pos).Lambda(args, expr)); + } + /** SuperSuffix = Arguments | "." [TypeArguments] Ident [Arguments] */ JCExpression superSuffix(List typeArgs, JCExpression t) { @@ -2779,6 +2892,24 @@ return params.toList(); } + List implicitParameters(boolean hasParens) { + if (hasParens) { + accept(LPAREN); + } + ListBuffer params = new ListBuffer(); + if (token.kind != RPAREN && token.kind != ARROW) { + params.append(implicitParameter()); + while (token.kind == COMMA) { + nextToken(); + params.append(implicitParameter()); + } + } + if (hasParens) { + accept(RPAREN); + } + return params.toList(); + } + JCModifiers optFinal(long flags) { JCModifiers mods = modifiersOpt(); checkNoMods(mods.flags & ~(Flags.FINAL | Flags.DEPRECATED)); @@ -2801,6 +2932,11 @@ return variableDeclaratorId(mods, type); } + protected JCVariableDecl implicitParameter() { + JCModifiers mods = F.at(token.pos).Modifiers(Flags.PARAMETER); + return variableDeclaratorId(mods, null); + } + /* ---------- auxiliary methods -------------- */ void error(int pos, String key, Object ... args) { @@ -3024,6 +3160,12 @@ allowTWR = true; } } + void checkLambda() { + if (!allowLambda) { + log.error(token.pos, "lambda.not.supported.in.source", source.name); + allowLambda = true; + } + } /* * a functional source tree and end position mappings diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/parser/Lexer.java --- a/langtools/src/share/classes/com/sun/tools/javac/parser/Lexer.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/parser/Lexer.java Mon Nov 28 15:56:42 2011 +0000 @@ -50,6 +50,11 @@ Token token(); /** + * Return token with given lookahead. + */ + Token token(int lookahead); + + /** * Return the last character position of the previous token. */ Token prevToken(); diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/parser/Scanner.java --- a/langtools/src/share/classes/com/sun/tools/javac/parser/Scanner.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/parser/Scanner.java Mon Nov 28 15:56:42 2011 +0000 @@ -26,8 +26,9 @@ package com.sun.tools.javac.parser; import java.nio.*; +import java.util.List; +import java.util.ArrayList; -import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.Position.LineMap; import com.sun.tools.javac.parser.JavaTokenizer.*; @@ -53,6 +54,10 @@ */ private Token prevToken; + /** Buffer of saved tokens (used during lookahead) + */ + private List savedTokens = new ArrayList(); + private JavaTokenizer tokenizer; /** * Create a scanner from the input array. This method might @@ -80,16 +85,35 @@ } public Token token() { - return token; + return token(0); } + public Token token(int lookahead) { + if (lookahead == 0) { + return token; + } else { + ensureLookahead(lookahead); + return savedTokens.get(lookahead - 1); + } + } + //where + private void ensureLookahead(int lookahead) { + for (int i = savedTokens.size() ; i < lookahead ; i ++) { + savedTokens.add(tokenizer.readToken()); + } + } + public Token prevToken() { return prevToken; } public void nextToken() { prevToken = token; - token = tokenizer.readToken(); + if (!savedTokens.isEmpty()) { + token = savedTokens.remove(0); + } else { + token = tokenizer.readToken(); + } } public Token split() { diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java --- a/langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/parser/Tokens.java Mon Nov 28 15:56:42 2011 +0000 @@ -176,6 +176,7 @@ TRUE("true", Tag.NAMED), FALSE("false", Tag.NAMED), NULL("null", Tag.NAMED), + ARROW("->"), LPAREN("("), RPAREN(")"), LBRACE("{"), diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties --- a/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties Mon Nov 28 15:56:42 2011 +0000 @@ -1945,6 +1945,11 @@ strings in switch are not supported in -source {0}\n\ (use -source 7 or higher to enable strings in switch) +# 0: string +compiler.err.lambda.not.supported.in.source=\ + lambda expressions are not supported in -source {0}\n\ + (use -source 8 or higher to enable lambda expressions) + ######################################## # Diagnostics for verbose resolution # used by Resolve (debug only) diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/test/tools/javac/diags/examples/CatchWithoutTry.java --- a/langtools/test/tools/javac/diags/examples/CatchWithoutTry.java Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/test/tools/javac/diags/examples/CatchWithoutTry.java Mon Nov 28 15:56:42 2011 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,6 @@ // key: compiler.err.catch.without.try // key: compiler.err.expected -// key: compiler.err.not.stmt class CatchWithoutTry { void m() { diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/test/tools/javac/diags/examples/LambdaNotSupported.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/diags/examples/LambdaNotSupported.java Mon Nov 28 15:56:42 2011 +0000 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.lambda.not.supported.in.source +// options: -source 7 -Xlint:-options + +class LambdaNotSupported { + S s = ()->{}; +} diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/test/tools/javac/diags/examples/NotAStatement.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/diags/examples/NotAStatement.java Mon Nov 28 15:56:42 2011 +0000 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.not.stmt + +class NotAStatement { + void m() { + x + 1; + } +} diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/test/tools/javac/generics/rare/6665356/T6665356.out --- a/langtools/test/tools/javac/generics/rare/6665356/T6665356.out Thu Nov 24 13:38:40 2011 +0000 +++ b/langtools/test/tools/javac/generics/rare/6665356/T6665356.out Mon Nov 28 15:56:42 2011 +0000 @@ -1,5 +1,5 @@ T6665356.java:17:37: compiler.err.improperly.formed.type.param.missing T6665356.java:18:40: compiler.err.improperly.formed.type.inner.raw.param -T6665356.java:26:23: compiler.err.improperly.formed.type.param.missing +T6665356.java:26:22: compiler.err.improperly.formed.type.param.missing T6665356.java:27:25: compiler.err.improperly.formed.type.inner.raw.param 4 errors diff -r 45d0ec1e7463 -r 9dbe313bfb74 langtools/test/tools/javac/lambda/LambdaParserTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/lambda/LambdaParserTest.java Mon Nov 28 15:56:42 2011 +0000 @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 7115050 + * @summary Add parser support for lambda expressions + */ + +import com.sun.source.util.JavacTask; +import java.net.URI; +import java.util.Arrays; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +public class LambdaParserTest { + + static int checkCount = 0; + + enum LambdaKind { + NILARY_EXPR("()->x"), + NILARY_STMT("()->{ return x; }"), + ONEARY_SHORT_EXPR("x->x"), + ONEARY_SHORT_STMT("x->{ return x; }"), + ONEARY_EXPR("(#M1 #T1 x)->x"), + ONEARY_STMT("(#M1 #T1 x)->{ return x; }"), + TWOARY_EXPR("(#M1 #T1 x, #M2 #T2 y)->x"), + TWOARY_STMT("(#M1 #T1 x, #M2 #T2 y)->{ return x; }"); + + String lambdaTemplate; + + LambdaKind(String lambdaTemplate) { + this.lambdaTemplate = lambdaTemplate; + } + + String getLambdaString(LambdaParameterKind pk1, LambdaParameterKind pk2, + ModifierKind mk1, ModifierKind mk2) { + return lambdaTemplate.replaceAll("#M1", mk1.modifier) + .replaceAll("#M2", mk2.modifier) + .replaceAll("#T1", pk1.parameterType) + .replaceAll("#T2", pk2.parameterType); + } + + int arity() { + switch (this) { + case NILARY_EXPR: + case NILARY_STMT: return 0; + case ONEARY_SHORT_EXPR: + case ONEARY_SHORT_STMT: + case ONEARY_EXPR: + case ONEARY_STMT: return 1; + case TWOARY_EXPR: + case TWOARY_STMT: return 2; + default: throw new AssertionError("Invalid lambda kind " + this); + } + } + + boolean isShort() { + return this == ONEARY_SHORT_EXPR || + this == ONEARY_SHORT_STMT; + } + } + + enum LambdaParameterKind { + IMPLICIT(""), + EXPLIICT_SIMPLE("A"), + EXPLICIT_VARARGS("A..."), + EXPLICIT_GENERIC1("A"), + EXPLICIT_GENERIC3("A"); + + String parameterType; + + LambdaParameterKind(String parameterType) { + this.parameterType = parameterType; + } + + boolean explicit() { + return this != IMPLICIT; + } + } + + enum ModifierKind { + NONE(""), + FINAL("final"), + PUBLIC("public"); + + String modifier; + + ModifierKind(String modifier) { + this.modifier = modifier; + } + + boolean compatibleWith(LambdaParameterKind pk) { + switch (this) { + case PUBLIC: return false; + case FINAL: return pk != LambdaParameterKind.IMPLICIT; + case NONE: return true; + default: throw new AssertionError("Invalid modifier kind " + this); + } + } + } + + enum ExprKind { + NONE("#L#S"), + SINGLE_PAREN1("(#L#S)"), + SINGLE_PAREN2("(#L)#S"), + DOUBLE_PAREN1("((#L#S))"), + DOUBLE_PAREN2("((#L)#S)"), + DOUBLE_PAREN3("((#L))#S"); + + String expressionTemplate; + + ExprKind(String expressionTemplate) { + this.expressionTemplate = expressionTemplate; + } + + String expressionString(LambdaParameterKind pk1, LambdaParameterKind pk2, + ModifierKind mk1, ModifierKind mk2, LambdaKind lk, SubExprKind sk) { + return expressionTemplate.replaceAll("#L", lk.getLambdaString(pk1, pk2, mk1, mk2)) + .replaceAll("#S", sk.subExpression); + } + } + + enum SubExprKind { + NONE(""), + SELECT_FIELD(".f"), + SELECT_METHOD(".f()"), + SELECT_NEW(".new Foo()"), + POSTINC("++"), + POSTDEC("--"); + + String subExpression; + + SubExprKind(String subExpression) { + this.subExpression = subExpression; + } + } + + public static void main(String... args) throws Exception { + + //create default shared JavaCompiler - reused across multiple compilations + JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null); + + for (LambdaKind lk : LambdaKind.values()) { + for (LambdaParameterKind pk1 : LambdaParameterKind.values()) { + if (lk.arity() < 1 && pk1 != LambdaParameterKind.IMPLICIT) continue; + for (LambdaParameterKind pk2 : LambdaParameterKind.values()) { + if (lk.arity() < 2 && pk2 != LambdaParameterKind.IMPLICIT) continue; + for (ModifierKind mk1 : ModifierKind.values()) { + if (mk1 != ModifierKind.NONE && lk.isShort()) continue; + if (lk.arity() < 1 && mk1 != ModifierKind.NONE) continue; + for (ModifierKind mk2 : ModifierKind.values()) { + if (lk.arity() < 2 && mk2 != ModifierKind.NONE) continue; + for (SubExprKind sk : SubExprKind.values()) { + for (ExprKind ek : ExprKind.values()) { + new LambdaParserTest(pk1, pk2, mk1, mk2, lk, sk, ek) + .run(comp, fm); + } + } + } + } + } + } + } + System.out.println("Total check executed: " + checkCount); + } + + LambdaParameterKind pk1; + LambdaParameterKind pk2; + ModifierKind mk1; + ModifierKind mk2; + LambdaKind lk; + SubExprKind sk; + ExprKind ek; + JavaSource source; + DiagnosticChecker diagChecker; + + LambdaParserTest(LambdaParameterKind pk1, LambdaParameterKind pk2, ModifierKind mk1, + ModifierKind mk2, LambdaKind lk, SubExprKind sk, ExprKind ek) { + this.pk1 = pk1; + this.pk2 = pk2; + this.mk1 = mk1; + this.mk2 = mk2; + this.lk = lk; + this.sk = sk; + this.ek = ek; + this.source = new JavaSource(); + this.diagChecker = new DiagnosticChecker(); + } + + class JavaSource extends SimpleJavaFileObject { + + String template = "class Test {\n" + + " SAM s = #E;\n" + + "}"; + + String source; + + public JavaSource() { + super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); + source = template.replaceAll("#E", ek.expressionString(pk1, pk2, mk1, mk2, lk, sk)); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + } + + void run(JavaCompiler tool, StandardJavaFileManager fm) throws Exception { + JavacTask ct = (JavacTask)tool.getTask(null, fm, diagChecker, + Arrays.asList("-XDallowLambda"), null, Arrays.asList(source)); + try { + ct.parse(); + } catch (Throwable ex) { + throw new AssertionError("Error thron when parsing the following source:\n" + source.getCharContent(true)); + } + check(); + } + + void check() { + checkCount++; + + boolean errorExpected = (lk.arity() > 0 && !mk1.compatibleWith(pk1)) || + (lk.arity() > 1 && !mk2.compatibleWith(pk2)); + + if (lk.arity() == 2 && + (pk1.explicit() != pk2.explicit() || + pk1 == LambdaParameterKind.EXPLICIT_VARARGS)) { + errorExpected = true; + } + + if (errorExpected != diagChecker.errorFound) { + throw new Error("invalid diagnostics for source:\n" + + source.getCharContent(true) + + "\nFound error: " + diagChecker.errorFound + + "\nExpected error: " + errorExpected); + } + } + + static class DiagnosticChecker implements javax.tools.DiagnosticListener { + + boolean errorFound; + + public void report(Diagnostic diagnostic) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + errorFound = true; + } + } + } +}