Skip to content

Commit

Permalink
GROOVY-8551: java-style multi-dimensional array initializers
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Oct 8, 2024
1 parent a14109d commit 02ac82f
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 144 deletions.
22 changes: 14 additions & 8 deletions src/antlr/GroovyParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,6 @@ variableInitializer
: enhancedStatementExpression
;

variableInitializers
: variableInitializer (nls COMMA nls variableInitializer)* nls COMMA?
;

emptyDims
: (annotationsOpt LBRACK RBRACK)+
;
Expand Down Expand Up @@ -1140,16 +1136,26 @@ options { baseContext = mapEntryLabel; }
creator[int t]
: createdName
( nls arguments anonymousInnerClassDeclaration[0]?
| dim+ (nls arrayInitializer)?
| dim0+ nls arrayInitializer
| dim1+ dim0*
)
;

dim
: annotationsOpt LBRACK expression? RBRACK
dim0
: annotationsOpt LBRACK RBRACK
;

dim1
: annotationsOpt LBRACK expression RBRACK
;

arrayInitializer
: LBRACE nls (variableInitializers nls)? RBRACE
: LBRACE nls (
(arrayInitializer | variableInitializer) nls
(COMMA nls
(arrayInitializer | variableInitializer) nls
)*
)? COMMA? nls RBRACE
;

/**
Expand Down
191 changes: 75 additions & 116 deletions src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static groovy.lang.Tuple.tuple;
import static org.apache.groovy.parser.antlr4.GroovyParser.*;
Expand Down Expand Up @@ -2191,31 +2190,6 @@ public Expression visitVariableInitializer(final VariableInitializerContext ctx)
ctx);
}

@Override
public List<Expression> visitVariableInitializers(final VariableInitializersContext ctx) {
if (!asBoolean(ctx)) {
return Collections.emptyList();
}

return ctx.variableInitializer().stream()
.map(this::visitVariableInitializer)
.collect(Collectors.toList());
}

@Override
public List<Expression> visitArrayInitializer(final ArrayInitializerContext ctx) {
if (!asBoolean(ctx)) {
return Collections.emptyList();
}

try {
visitingArrayInitializerCount += 1;
return this.visitVariableInitializers(ctx.variableInitializers());
} finally {
visitingArrayInitializerCount -= 1;
}
}

@Override
public Statement visitBlock(final BlockContext ctx) {
if (!asBoolean(ctx)) {
Expand Down Expand Up @@ -3293,101 +3267,76 @@ public Expression visitCreator(final CreatorContext ctx) {

ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(anonymousInnerClassNode, arguments);
constructorCallExpression.setUsingAnonymousInnerClass(true);

return configureAST(constructorCallExpression, ctx);
}

ConstructorCallExpression constructorCallExpression = new ConstructorCallExpression(classNode, arguments);
return configureAST(constructorCallExpression, ctx);
}

if (asBoolean(ctx.dim())) { // create array
ArrayExpression arrayExpression;

List<Tuple3<Expression, List<AnnotationNode>, TerminalNode>> dimList =
ctx.dim().stream()
.map(this::visitDim)
.collect(Collectors.toList());

TerminalNode invalidDimLBrack = null;
Boolean exprEmpty = null;
List<Tuple3<Expression, List<AnnotationNode>, TerminalNode>> emptyDimList = new LinkedList<>();
List<Tuple3<Expression, List<AnnotationNode>, TerminalNode>> dimWithExprList = new LinkedList<>();
Tuple3<Expression, List<AnnotationNode>, TerminalNode> latestDim = null;
for (Tuple3<Expression, List<AnnotationNode>, TerminalNode> dim : dimList) {
if (null == dim.getV1()) {
emptyDimList.add(dim);
exprEmpty = Boolean.TRUE;
} else {
if (Boolean.TRUE.equals(exprEmpty)) {
invalidDimLBrack = latestDim.getV3();
}

dimWithExprList.add(dim);
exprEmpty = Boolean.FALSE;
}

latestDim = dim;
if (asBoolean(ctx.dim1())) { // create array: new Type[n][]
final int nDim = ctx.dim1().size() + ctx.dim0().size();
List<Expression> sizeExpressions = new ArrayList<>(nDim);
List<List<AnnotationNode>> typeAnnotations = new ArrayList<>(nDim);
for (var dim : ctx.dim1()) {
sizeExpressions.add((Expression) this.visit(dim.expression()));
typeAnnotations.add(this.visitAnnotationsOpt(dim.annotationsOpt()));
}
for (var dim : ctx.dim0()) {
sizeExpressions.add(ConstantExpression.EMPTY_EXPRESSION);
typeAnnotations.add(this.visitAnnotationsOpt(dim.annotationsOpt()));
}

if (asBoolean(ctx.arrayInitializer())) {
if (!dimWithExprList.isEmpty()) {
throw createParsingFailedException("dimension should be empty", dimWithExprList.get(0).getV3());
}

ClassNode elementType = classNode;
for (int i = 0, n = emptyDimList.size() - 1; i < n; i += 1) {
elementType = this.createArrayType(elementType);
}

arrayExpression =
new ArrayExpression(
elementType,
this.visitArrayInitializer(ctx.arrayInitializer()));

} else {
if (null != invalidDimLBrack) {
throw createParsingFailedException("dimension cannot be empty", invalidDimLBrack);
}

if (dimWithExprList.isEmpty() && !emptyDimList.isEmpty()) {
throw createParsingFailedException("dimensions cannot be all empty", emptyDimList.get(0).getV3());
}
ArrayExpression arrayExpression = new ArrayExpression(classNode, null, sizeExpressions);
ClassNode arrayType = arrayExpression.getType();
int i = 0; // annotations apply to array then component(s)
do { arrayType.addTypeAnnotations(typeAnnotations.get(i++));
} while ((arrayType = arrayType.getComponentType()).isArray());
return configureAST(arrayExpression, ctx);
}

Expression[] empties;
if (asBoolean(emptyDimList)) {
empties = new Expression[emptyDimList.size()];
Arrays.fill(empties, ConstantExpression.EMPTY_EXPRESSION);
} else {
empties = Expression.EMPTY_ARRAY;
}
if (asBoolean(ctx.dim0())) { // create array: new Type[][]{ ... }
final int nDim = ctx.dim0().size();
List<List<AnnotationNode>> typeAnnotations = new ArrayList<>(nDim);
for (var dim : ctx.dim0()) typeAnnotations.add(this.visitAnnotationsOpt(dim.annotationsOpt()));

arrayExpression =
new ArrayExpression(
classNode,
null,
Stream.concat(
dimWithExprList.stream().map(Tuple3::getV1),
Arrays.stream(empties)
).collect(Collectors.toList()));
ClassNode elementType = classNode;
for (int i = nDim - 1; i > 0; i -= 1) {
elementType = this.createArrayType(elementType);
elementType.addTypeAnnotations(typeAnnotations.get(i));
}

arrayExpression.setType(
this.createArrayType(
classNode,
dimList.stream().map(Tuple3::getV2).collect(Collectors.toList())
)
);
var initializer = ctx.arrayInitializer();
initializer.putNodeMetaData("elementType", elementType);
List<Expression> initExpressions = this.visitArrayInitializer(initializer);

ArrayExpression arrayExpression = new ArrayExpression(elementType, initExpressions);
arrayExpression.getType().addTypeAnnotations(typeAnnotations.get(0));
return configureAST(arrayExpression, ctx);
}

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

@Override
public Tuple3<Expression, List<AnnotationNode>, TerminalNode> visitDim(final DimContext ctx) {
return tuple((Expression) this.visit(ctx.expression()), this.visitAnnotationsOpt(ctx.annotationsOpt()), ctx.LBRACK());
public ClassNode visitCreatedName(final CreatedNameContext ctx) {
ClassNode classNode = null;
if (asBoolean(ctx.qualifiedClassName())) {
classNode = this.visitQualifiedClassName(ctx.qualifiedClassName());
if (asBoolean(ctx.typeArgumentsOrDiamond())) {
classNode.setGenericsTypes(
this.visitTypeArgumentsOrDiamond(ctx.typeArgumentsOrDiamond()));
configureAST(classNode, ctx);
}
} else if (asBoolean(ctx.primitiveType())) {
classNode = configureAST(this.visitPrimitiveType(ctx.primitiveType()), ctx);
}
if (classNode == null) {
throw createParsingFailedException("Unsupported created name: " + ctx.getText(), ctx);
}
classNode.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); // GROOVY-11178

return classNode;
}

private static String nextAnonymousClassName(final ClassNode outerClass) {
Expand Down Expand Up @@ -3434,24 +3383,34 @@ public InnerClassNode visitAnonymousInnerClassDeclaration(final AnonymousInnerCl
}

@Override
public ClassNode visitCreatedName(final CreatedNameContext ctx) {
ClassNode classNode = null;
if (asBoolean(ctx.qualifiedClassName())) {
classNode = this.visitQualifiedClassName(ctx.qualifiedClassName());
if (asBoolean(ctx.typeArgumentsOrDiamond())) {
classNode.setGenericsTypes(
this.visitTypeArgumentsOrDiamond(ctx.typeArgumentsOrDiamond()));
configureAST(classNode, ctx);
}
} else if (asBoolean(ctx.primitiveType())) {
classNode = configureAST(this.visitPrimitiveType(ctx.primitiveType()), ctx);
}
if (classNode == null) {
throw createParsingFailedException("Unsupported created name: " + ctx.getText(), ctx);
public List<Expression> visitArrayInitializer(final ArrayInitializerContext ctx) {
if (!asBoolean(ctx)) {
return Collections.emptyList();
}
classNode.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt())); // GROOVY-11178

return classNode;
ClassNode elementType = ctx.getNodeMetaData("elementType");
try {
visitingArrayInitializerCount += 1;
var initExpressions = new ArrayList<Expression>();
for (int i = 0; i < ctx.getChildCount(); i += 1) {
var c = ctx.getChild(i);
if (c instanceof ArrayInitializerContext) {
var arrayInitializer = (ArrayInitializerContext) c;
ClassNode subType = elementType.getComponentType();
//if (subType == null) produce closure or throw exception
arrayInitializer.putNodeMetaData("elementType", subType);
var arrayExpression = configureAST(new ArrayExpression(subType,
this.visitArrayInitializer(arrayInitializer)), arrayInitializer);
arrayExpression.setType(elementType);
initExpressions.add(arrayExpression);
} else if (c instanceof VariableInitializerContext) {
initExpressions.add(this.visitVariableInitializer((VariableInitializerContext) c));
}
}
return initExpressions;
} finally {
visitingArrayInitializerCount -= 1;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,6 @@ private String formatInitExpressions() {
}

private String formatSizeExpressions() {
return sizeExpressions.stream().map(e -> "[" + e.getText() + "]").collect(Collectors.joining());
return sizeExpressions.stream().map(e -> "[" + (e == ConstantExpression.EMPTY_EXPRESSION ? "" : e.getText()) + "]").collect(Collectors.joining());
}
}
41 changes: 28 additions & 13 deletions src/spec/test/gdk/WorkingWithArraysTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,52 @@
*/
package gdk

import groovy.test.GroovyTestCase
import org.junit.Test

class WorkingWithArraysTest extends GroovyTestCase {
final class WorkingWithArraysTest {

@Test
void testArrayLiterals() {
// tag::array_literals[]
Integer[] nums = [5, 6, 7, 8]
Number[] nums = [5, 6, 7, 8]
assert nums[1] == 6
assert nums.getAt(2) == 7 // alternative syntax
assert nums[-1] == 8 // negative indices
assert nums instanceof Integer[]
assert nums instanceof Number[]

int[] primes = [2, 3, 5, 7] // primitives
assert primes instanceof int[]

def evens = new int[]{2, 4, 6} // alt syntax 1
assert evens instanceof int[]

def odds = [1, 3, 5] as int[] // alt syntax 2
def odds = [1, 3, 5] as int[] // alt syntax 1
assert odds instanceof int[]

def evens = new int[]{2, 4, 6} // alt syntax 2
assert evens instanceof int[]

// empty array examples
Integer[] emptyNums = []
assert emptyNums instanceof Integer[] && emptyNums.size() == 0
Number[] emptyNums = []
assert emptyNums instanceof Number[] && emptyNums.size() == 0

def emptyStrings = new String[]{} // alternative syntax 1
var emptyObjects = new Object[0] // alternative syntax 1
assert emptyObjects instanceof Object[] && emptyObjects.size() == 0

def emptyStrings = new String[]{} // alternative syntax 2
assert emptyStrings instanceof String[] && emptyStrings.size() == 0

var emptyObjects = new Object[0] // alternative syntax 2
assert emptyObjects instanceof Object[] && emptyObjects.size() == 0
// multi-dimension examples
int[][] manyInts = [ [1,2], [3,4] ]
assert manyInts instanceof int[][] && manyInts.size() == 2
assert manyInts[0].size() == 2 && manyInts[1].size() == 2

def manyStrings = new String[][] { {'one','two'}, {'three','four'}, }
assert manyStrings instanceof String[][] && manyStrings.size() == 2
assert manyStrings[0].size() == 2 && manyStrings[1].size() == 2
assert manyStrings[0][0] == 'one'
assert manyStrings[-1][-1] == 'four'
// end::array_literals[]
}

@Test
void testArrayIteration() {
// tag::array_each[]
String[] vowels = ['a', 'e', 'i', 'o', 'u']
Expand All @@ -73,6 +87,7 @@ class WorkingWithArraysTest extends GroovyTestCase {
// end::array_each[]
}

@Test
void testListCollect() {
// tag::array_gdk[]
int[] nums = [1, 2, 3]
Expand Down
Loading

0 comments on commit 02ac82f

Please sign in to comment.