Skip to content

Commit

Permalink
Refine lambda expression
Browse files Browse the repository at this point in the history
  • Loading branch information
daniellansun committed Oct 18, 2016
1 parent b956b11 commit c380e42
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 50 deletions.
54 changes: 41 additions & 13 deletions src/main/antlr4/org/apache/groovy/parser/antlr4/GroovyParser.g4
Expand Up @@ -340,6 +340,7 @@ variableDeclaratorId
variableInitializer
: arrayInitializer
| statementExpression
| standardLambda
;

arrayInitializer
Expand Down Expand Up @@ -444,20 +445,31 @@ gstringPath

// LAMBDA EXPRESSION
lambda
options { baseContext = standardLambda; }
: lambdaParameters nls ARROW nls lambdaBody
;

standardLambda
: standardLambdaParameters nls ARROW nls lambdaBody
;

lambdaParameters
: LPAREN formalParameterList? RPAREN
options { baseContext = standardLambdaParameters; }
: formalParameters

// { a -> a * 2 } can be parsed as a lambda expression in a block, but we expect a closure.
// So it is better to put parameters in the parentheses and the following single parameter without parentheses is limited
// | variableDeclaratorId
// | variableDeclaratorId
;

standardLambdaParameters
: formalParameters
| variableDeclaratorId
;

lambdaBody
: block
| statementExpression
| expression
;


Expand Down Expand Up @@ -701,7 +713,7 @@ castParExpression
;

parExpression
: LPAREN statementExpression RPAREN
: LPAREN (statementExpression | standardLambda) RPAREN

// !!!Error Alternatives!!!
//| LPAREN statementExpression RPAREN RPAREN+ { notifyErrorListeners("Too many ')'"); } // the "Too many" case has been handled by the lexer
Expand Down Expand Up @@ -818,7 +830,7 @@ expression
| MOD_ASSIGN
| POWER_ASSIGN
) nls
right=statementExpression #assignmentExprAlt
(statementExpression | standardLambda) #assignmentExprAlt
;

commandExpression
Expand Down Expand Up @@ -1031,23 +1043,39 @@ typeArgumentsOrDiamond

arguments
: LPAREN
( argumentList?
| argumentList COMMA
( enhancedArgumentList?
| enhancedArgumentList COMMA
)
RPAREN
;

argumentList
: ( expressionListElement
| mapEntry
)
options { baseContext = enhancedArgumentList; }
: argumentListElement
( COMMA nls
( expressionListElement
| mapEntry
)
argumentListElement
)*
;

enhancedArgumentList
: enhancedArgumentListElement
( COMMA nls
enhancedArgumentListElement
)*
;

argumentListElement
options { baseContext = enhancedArgumentListElement; }
: expressionListElement
| mapEntry
;

enhancedArgumentListElement
: expressionListElement
| standardLambda
| mapEntry
;

stringLiteral
: StringLiteral
;
Expand Down
@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.ast.expr;

import org.codehaus.groovy.ast.AstToTextHelper;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.stmt.Statement;

/**
* Represents a lambda expression such as e -> e * 2
* or (x, y) -> x + y or (x, y) -> { x + y } or (int x, int y) -> { x + y }
*
* @author <a href="mailto:realbluesun@hotmail.com">Daniel.Sun</a>
* Created on 2016/10/18
*/
public class LambdaExpression extends ClosureExpression {
public LambdaExpression(Parameter[] parameters, Statement code) {
super(parameters, code);
}

@Override
public String getText() {
String paramText = AstToTextHelper.getParametersText(this.getParameters());
if (paramText.length() > 0) {
return "(" + paramText + ") -> { ... }";
} else {
return "() -> { ... }";
}
}
}
115 changes: 78 additions & 37 deletions src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
Expand Up @@ -1355,6 +1355,10 @@ public Expression visitVariableInitializer(VariableInitializerContext ctx) {
ctx);
}

if (asBoolean(ctx.standardLambda())) {
return this.configureAST(this.visitStandardLambda(ctx.standardLambda()), ctx);
}

throw createParsingFailedException("Unsupported variable initializer: " + ctx.getText(), ctx);
}

Expand Down Expand Up @@ -1394,7 +1398,7 @@ public ExpressionStatement visitCommandExprAlt(CommandExprAltContext ctx) {
@Override
public Expression visitCommandExpression(CommandExpressionContext ctx) {
Expression baseExpr = this.visitPathExpression(ctx.pathExpression());
Expression arguments = this.visitArgumentList(ctx.argumentList());
Expression arguments = this.visitEnhancedArgumentList(ctx.enhancedArgumentList());

MethodCallExpression methodCallExpression;
if (baseExpr instanceof PropertyExpression) { // e.g. obj.a 1, 2
Expand Down Expand Up @@ -1460,7 +1464,7 @@ public Expression visitCommandArgument(CommandArgumentContext ctx) {

Expression primaryExpr = (Expression) this.visit(ctx.primary());

if (asBoolean(ctx.argumentList())) { // e.g. x y a b
if (asBoolean(ctx.enhancedArgumentList())) { // e.g. x y a b
if (baseExpr instanceof PropertyExpression) { // the branch should never reach, because a.b.c will be parsed as a path expression, not a method call
throw createParsingFailedException("Unsupported command argument: " + ctx.getText(), ctx);
}
Expand All @@ -1470,7 +1474,7 @@ public Expression visitCommandArgument(CommandArgumentContext ctx) {
new MethodCallExpression(
baseExpr,
this.createConstantExpression(primaryExpr),
this.visitArgumentList(ctx.argumentList())
this.visitEnhancedArgumentList(ctx.enhancedArgumentList())
);
methodCallExpression.setImplicitThis(false);

Expand Down Expand Up @@ -1510,7 +1514,15 @@ public ClassNode visitCastParExpression(CastParExpressionContext ctx) {

@Override
public Expression visitParExpression(ParExpressionContext ctx) {
Expression expression = ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression();
Expression expression;

if (asBoolean(ctx.statementExpression())) {
expression = ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression();
} else if (asBoolean(ctx.standardLambda())) {
expression = this.visitStandardLambda(ctx.standardLambda());
} else {
throw createParsingFailedException("Unsupported parentheses expression: " + ctx.getText(), ctx);
}

expression.putNodeMetaData(IS_INSIDE_PARENTHESES, true);

Expand Down Expand Up @@ -1843,29 +1855,40 @@ public ClassNode[] visitTypeList(TypeListContext ctx) {

@Override
public Expression visitArguments(ArgumentsContext ctx) {
if (!asBoolean(ctx) || !asBoolean(ctx.argumentList())) {
if (!asBoolean(ctx) || !asBoolean(ctx.enhancedArgumentList())) {
return new ArgumentListExpression();
}

return this.configureAST(this.visitArgumentList(ctx.argumentList()), ctx);
return this.configureAST(this.visitEnhancedArgumentList(ctx.enhancedArgumentList()), ctx);
}

@Override
public Expression visitArgumentList(ArgumentListContext ctx) {
public Expression visitEnhancedArgumentList(EnhancedArgumentListContext ctx) {
if (!asBoolean(ctx)) {
return null;
}

List<Expression> expressionList = this.createExpressionList(ctx.expressionListElement());
List<MapEntryExpression> mapEntryExpressionList = this.createMapEntryList(ctx.mapEntry());
List<Expression> expressionList = new LinkedList<>();
List<MapEntryExpression> mapEntryExpressionList = new LinkedList<>();

ctx.enhancedArgumentListElement().stream()
.map(this::visitEnhancedArgumentListElement)
.forEach(e -> {

if (e instanceof MapEntryExpression) {
mapEntryExpressionList.add((MapEntryExpression) e);
} else {
expressionList.add(e);
}
});

if (!asBoolean(ctx.mapEntry())) { // e.g. arguments like 1, 2
if (!asBoolean(mapEntryExpressionList)) { // e.g. arguments like 1, 2 OR someArg, e -> e
return this.configureAST(
new ArgumentListExpression(expressionList),
ctx);
}

if (!asBoolean(ctx.expressionListElement())) { // e.g. arguments like x: 1, y: 2
if (!asBoolean(expressionList)) { // e.g. arguments like x: 1, y: 2
return this.configureAST(
new TupleExpression(
this.configureAST(
Expand All @@ -1874,7 +1897,7 @@ public Expression visitArgumentList(ArgumentListContext ctx) {
ctx);
}

if (asBoolean(ctx.mapEntry()) && asBoolean(ctx.expressionListElement())) { // e.g. arguments like x: 1, 'a', y: 2, 'b', z: 3
if (asBoolean(mapEntryExpressionList) && asBoolean(expressionList)) { // e.g. arguments like x: 1, 'a', y: 2, 'b', z: 3
ArgumentListExpression argumentListExpression = new ArgumentListExpression(expressionList);
argumentListExpression.getExpressions().add(0, new MapExpression(mapEntryExpressionList)); // TODO: confirm BUG OR NOT? All map entries will be put at first, which is not friendly to Groovy developers

Expand All @@ -1884,6 +1907,24 @@ public Expression visitArgumentList(ArgumentListContext ctx) {
throw createParsingFailedException("Unsupported argument list: " + ctx.getText(), ctx);
}

@Override
public Expression visitEnhancedArgumentListElement(EnhancedArgumentListElementContext ctx) {
if (asBoolean(ctx.expressionListElement())) {
return this.configureAST(this.visitExpressionListElement(ctx.expressionListElement()), ctx);
}

if (asBoolean(ctx.standardLambda())) {
return this.configureAST(this.visitStandardLambda(ctx.standardLambda()), ctx);
}

if (asBoolean(ctx.mapEntry())) {
return this.configureAST(this.visitMapEntry(ctx.mapEntry()), ctx);
}

throw createParsingFailedException("Unsupported enhanced argument list element: " + ctx.getText(), ctx);
}


@Override
public ConstantExpression visitStringLiteral(StringLiteralContext ctx) {
String text = ctx.StringLiteral().getText();
Expand Down Expand Up @@ -2243,7 +2284,9 @@ && isTrue(leftExpr, IS_INSIDE_PARENTHESES)) { // it is a special multiple assign
new BinaryExpression(
this.configureAST(new TupleExpression(leftExpr), ctx.left),
this.createGroovyToken(ctx.op),
((ExpressionStatement) this.visit(ctx.right)).getExpression()),
asBoolean(ctx.statementExpression())
? ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression()
: this.visitStandardLambda(ctx.standardLambda())),
ctx);
}

Expand All @@ -2270,9 +2313,10 @@ && isTrue(leftExpr, IS_INSIDE_PARENTHESES)) { // it is a special multiple assign
new BinaryExpression(
leftExpr,
this.createGroovyToken(ctx.op),
((ExpressionStatement) this.visit(ctx.right)).getExpression()),
ctx
);
asBoolean(ctx.statementExpression())
? ((ExpressionStatement) this.visit(ctx.statementExpression())).getExpression()
: this.visitStandardLambda(ctx.standardLambda())),
ctx);
}

// } expression --------------------------------------------------------------------
Expand Down Expand Up @@ -2322,7 +2366,7 @@ public ClosureExpression visitClosurePrmrAlt(ClosurePrmrAltContext ctx) {

@Override
public ClosureExpression visitLambdaPrmrAlt(LambdaPrmrAltContext ctx) {
return this.configureAST(this.visitLambda(ctx.lambda()), ctx);
return this.configureAST(this.visitStandardLambda(ctx.standardLambda()), ctx);
}

@Override
Expand Down Expand Up @@ -2773,23 +2817,19 @@ public Expression visitGstringPath(GstringPathContext ctx) {
}
// } gstring --------------------------------------------------------------------

/**
* parse lambda expression as closure
*
* @param ctx LambdaContext instance
* @return ClosureExpression instance
*/
@Override
public ClosureExpression visitLambda(LambdaContext ctx) {
Parameter[] parameters = this.visitLambdaParameters(ctx.lambdaParameters());
Statement code = this.visitLambdaBody(ctx.lambdaBody());
public LambdaExpression visitStandardLambda(StandardLambdaContext ctx) {
return this.configureAST(this.createLambda(ctx.standardLambdaParameters(), ctx.lambdaBody()), ctx);
}

return this.configureAST(new ClosureExpression(parameters, code), ctx);
private LambdaExpression createLambda(StandardLambdaParametersContext standardLambdaParametersContext, LambdaBodyContext lambdaBodyContext) {
return new LambdaExpression(
this.visitStandardLambdaParameters(standardLambdaParametersContext),
this.visitLambdaBody(lambdaBodyContext));
}

@Override
public Parameter[] visitLambdaParameters(LambdaParametersContext ctx) {
/*
public Parameter[] visitStandardLambdaParameters(StandardLambdaParametersContext ctx) {
if (asBoolean(ctx.variableDeclaratorId())) {
return new Parameter[] {
this.configureAST(
Expand All @@ -2801,18 +2841,20 @@ public Parameter[] visitLambdaParameters(LambdaParametersContext ctx) {
)
};
}
*/

return asBoolean(ctx.formalParameterList())
? this.visitFormalParameterList(ctx.formalParameterList())
: null;
}
Parameter[] parameters = this.visitFormalParameters(ctx.formalParameters());

if (0 == parameters.length) {
return null;
}

return parameters;
}

@Override
public Statement visitLambdaBody(LambdaBodyContext ctx) {
if (asBoolean(ctx.statementExpression())) {
return this.configureAST((Statement) this.visit(ctx.statementExpression()), ctx);
if (asBoolean(ctx.expression())) {
return this.configureAST(new ExpressionStatement((Expression) this.visit(ctx.expression())), ctx);
}

if (asBoolean(ctx.block())) {
Expand All @@ -2822,7 +2864,6 @@ public Statement visitLambdaBody(LambdaBodyContext ctx) {
throw createParsingFailedException("Unsupported lambda body: " + ctx.getText(), ctx);
}


@Override
public ClosureExpression visitClosure(ClosureContext ctx) {
Parameter[] parameters = asBoolean(ctx.formalParameterList())
Expand Down

0 comments on commit c380e42

Please sign in to comment.