diff --git a/java/fury-core/src/main/java/io/fury/codegen/ClosureVisitable.java b/java/fury-core/src/main/java/io/fury/codegen/ClosureVisitable.java new file mode 100644 index 0000000000..ee77064a60 --- /dev/null +++ b/java/fury-core/src/main/java/io/fury/codegen/ClosureVisitable.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 The Fury authors + * 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 io.fury.codegen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation used to mark closure may contain child expressions. + * + * @author chaokunyang + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ClosureVisitable {} diff --git a/java/fury-core/src/main/java/io/fury/codegen/Expression.java b/java/fury-core/src/main/java/io/fury/codegen/Expression.java index 64664b1591..8f21cc1563 100644 --- a/java/fury-core/src/main/java/io/fury/codegen/Expression.java +++ b/java/fury-core/src/main/java/io/fury/codegen/Expression.java @@ -19,16 +19,61 @@ package io.fury.codegen; import static io.fury.codegen.Code.ExprCode; +import static io.fury.codegen.Code.LiteralValue; +import static io.fury.codegen.Code.LiteralValue.FalseLiteral; +import static io.fury.codegen.Code.LiteralValue.TrueLiteral; +import static io.fury.codegen.CodeGenerator.alignIndent; +import static io.fury.codegen.CodeGenerator.appendNewlineIfNeeded; +import static io.fury.codegen.CodeGenerator.indent; +import static io.fury.type.TypeUtils.BOOLEAN_TYPE; +import static io.fury.type.TypeUtils.CLASS_TYPE; +import static io.fury.type.TypeUtils.ITERABLE_TYPE; +import static io.fury.type.TypeUtils.OBJECT_TYPE; +import static io.fury.type.TypeUtils.PRIMITIVE_BOOLEAN_TYPE; +import static io.fury.type.TypeUtils.PRIMITIVE_BYTE_TYPE; +import static io.fury.type.TypeUtils.PRIMITIVE_INT_TYPE; +import static io.fury.type.TypeUtils.PRIMITIVE_LONG_TYPE; +import static io.fury.type.TypeUtils.PRIMITIVE_SHORT_TYPE; +import static io.fury.type.TypeUtils.PRIMITIVE_VOID_TYPE; +import static io.fury.type.TypeUtils.STRING_TYPE; +import static io.fury.type.TypeUtils.boxedType; +import static io.fury.type.TypeUtils.defaultValue; +import static io.fury.type.TypeUtils.getArrayType; +import static io.fury.type.TypeUtils.getElementType; +import static io.fury.type.TypeUtils.getRawType; +import static io.fury.type.TypeUtils.getSizeOfPrimitiveType; +import static io.fury.type.TypeUtils.isPrimitive; +import static io.fury.type.TypeUtils.maxType; +import static io.fury.util.Utils.checkArgument; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Primitives; import com.google.common.reflect.TypeToken; +import io.fury.type.TypeUtils; +import io.fury.util.Functions; +import io.fury.util.Platform; +import io.fury.util.ReflectionUtils; +import io.fury.util.StringUtils; +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * An expression represents a piece of code evaluation logic which can be generated to valid java * code. Expression can be used to compose complex code logic. * - *

TODO refactor expression into Expression and Stmt with a common class Node. Expression will - * have a value, the stmt won't have value. If/While/For/doWhile/ForEach are statements instead of - * expression. + *

TODO(chaokunyang) refactor expression into Expression and Stmt with a common class Node. + * Expression will have a value, the stmt won't have value. If/While/For/doWhile/ForEach are + * statements instead of expression. + * + *

Note that all dependent expression field are marked as non-final, so when split expression + * tree into subtrees, we can replace dependent expression fields in subtree root nodes with {@link + * Reference}. * * @author chaokunyang */ @@ -73,4 +118,2325 @@ default boolean nullable() { // ########################################################### // ####################### Expressions ####################### // ########################################################### + + /** An expression that have a value as the result of the evaluation. */ + abstract class ValueExpression implements Expression { + // set to others to get a more context-dependent variable name. + public String valuePrefix = "value"; + + public String isNullPrefix() { + return "is" + StringUtils.capitalize(valuePrefix) + "Null"; + } + } + + /** + * A ListExpression is a list of expressions. Use last expression's type/nullable/value/IsNull to + * represent ListExpression's type/nullable/value/IsNull. + */ + class ListExpression implements Expression { + private final List expressions; + private Expression last; + + public ListExpression(Expression... expressions) { + this(new ArrayList<>(Arrays.asList(expressions))); + } + + public ListExpression(List expressions) { + this.expressions = expressions; + if (!this.expressions.isEmpty()) { + this.last = this.expressions.get(this.expressions.size() - 1); + } + } + + @Override + public TypeToken type() { + Preconditions.checkNotNull(last); + return last.type(); + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + boolean hasCode = false; + for (Expression expr : expressions) { + ExprCode code = expr.genCode(ctx); + if (StringUtils.isNotBlank(code.code())) { + appendNewlineIfNeeded(codeBuilder); + codeBuilder.append(code.code()); + hasCode = true; + } + } + ExprCode lastExprCode = last.genCode(ctx); + String code = codeBuilder.toString(); + if (!hasCode) { + code = null; + } + return new ExprCode(code, lastExprCode.isNull(), lastExprCode.value()); + } + + @Override + public boolean nullable() { + return last.nullable(); + } + + public List expressions() { + return expressions; + } + + public ListExpression add(Expression expr) { + Preconditions.checkNotNull(expr); + this.expressions.add(expr); + this.last = expr; + return this; + } + + public ListExpression add(Expression expr, Expression... exprs) { + add(expr); + return addAll(Arrays.asList(exprs)); + } + + public ListExpression addAll(List exprs) { + Preconditions.checkNotNull(exprs); + this.expressions.addAll(exprs); + if (!exprs.isEmpty()) { + this.last = exprs.get(exprs.size() - 1); + } + return this; + } + + @Override + public String toString() { + return expressions.stream().map(Object::toString).collect(Collectors.joining(",")); + } + } + + class Literal implements Expression { + public static final Literal True = new Literal(true, PRIMITIVE_BOOLEAN_TYPE); + public static final Literal False = new Literal(false, PRIMITIVE_BOOLEAN_TYPE); + + private final Object value; + private final TypeToken type; + + public Literal(String value) { + this.value = value; + this.type = STRING_TYPE; + } + + public Literal(Object value, TypeToken type) { + this.value = value; + this.type = type; + } + + public static Literal ofByte(short v) { + return new Literal(v, PRIMITIVE_BYTE_TYPE); + } + + public static Literal ofShort(short v) { + return new Literal(v, PRIMITIVE_SHORT_TYPE); + } + + public static Literal ofInt(int v) { + return new Literal(v, PRIMITIVE_INT_TYPE); + } + + public static Literal ofLong(long v) { + return new Literal(v, PRIMITIVE_LONG_TYPE); + } + + public static Literal ofClass(Class v) { + return new Literal(v, CLASS_TYPE); + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + Class javaType = getRawType(type); + if (isPrimitive(javaType)) { + javaType = boxedType(javaType); + } + if (value == null) { + LiteralValue defaultLiteral = new LiteralValue(javaType, defaultValue(javaType)); + return new ExprCode(null, TrueLiteral, defaultLiteral); + } else { + if (javaType == String.class) { + return new ExprCode(FalseLiteral, new LiteralValue(value)); + } else if (javaType == Boolean.class || javaType == Integer.class) { + return new ExprCode(null, FalseLiteral, new LiteralValue(javaType, value.toString())); + } else if (javaType == Float.class) { + Float f = (Float) value; + if (f.isNaN()) { + return new ExprCode(FalseLiteral, new LiteralValue(javaType, "Float.NaN")); + } else if (f.equals(Float.POSITIVE_INFINITY)) { + return new ExprCode( + FalseLiteral, new LiteralValue(javaType, "Float.POSITIVE_INFINITY")); + } else if (f.equals(Float.NEGATIVE_INFINITY)) { + return new ExprCode( + FalseLiteral, new LiteralValue(javaType, "Float.NEGATIVE_INFINITY")); + } else { + return new ExprCode(FalseLiteral, new LiteralValue(javaType, String.format("%fF", f))); + } + } else if (javaType == Double.class) { + Double d = (Double) value; + if (d.isNaN()) { + return new ExprCode(FalseLiteral, new LiteralValue(javaType, "Double.NaN")); + } else if (d.equals(Double.POSITIVE_INFINITY)) { + return new ExprCode( + FalseLiteral, new LiteralValue(javaType, "Double.POSITIVE_INFINITY")); + } else if (d.equals(Double.NEGATIVE_INFINITY)) { + return new ExprCode( + FalseLiteral, new LiteralValue(javaType, "Double.NEGATIVE_INFINITY")); + } else { + return new ExprCode(FalseLiteral, new LiteralValue(javaType, String.format("%fD", d))); + } + } else if (javaType == Byte.class) { + return new ExprCode( + FalseLiteral, Code.exprValue(javaType, String.format("(%s)%s", "byte", value))); + } else if (javaType == Short.class) { + return new ExprCode( + FalseLiteral, Code.exprValue(javaType, String.format("(%s)%s", "short", value))); + } else if (javaType == Long.class) { + return new ExprCode( + FalseLiteral, + new LiteralValue(javaType, String.format("%dL", ((Number) (value)).longValue()))); + } else if (isPrimitive(javaType)) { + return new ExprCode(FalseLiteral, new LiteralValue(javaType, String.valueOf(value))); + } else if (javaType == Class.class) { + String v; + Class valueClass = (Class) value; + if (valueClass.isArray()) { + v = String.format("%s.class", TypeUtils.getArrayType((Class) value)); + } else { + v = String.format("%s.class", ((Class) (value)).getCanonicalName()); + } + return new ExprCode(FalseLiteral, new LiteralValue(javaType, v)); + } else { + throw new UnsupportedOperationException("Unsupported type " + javaType); + } + } + } + + @Override + public String toString() { + if (value == null) { + return "null"; + } else { + return value.toString(); + } + } + } + + class Null implements Expression { + private TypeToken type; + private final boolean typedNull; + + public Null(TypeToken type) { + this(type, false); + } + + public Null(TypeToken type, boolean typedNull) { + this.type = type; + this.typedNull = typedNull; + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + Class javaType = getRawType(type); + LiteralValue defaultLiteral = + new LiteralValue(javaType, typedNull ? String.format("((%s)null)", type) : "null"); + return new ExprCode(null, TrueLiteral, defaultLiteral); + } + + @Override + public String toString() { + return String.format("(%s)(null)", getRawType(type).getSimpleName()); + } + } + + /** A Reference is a variable/field that can be accessed in the expression's CodegenContext. */ + class Reference implements Expression { + private final String name; + private final TypeToken type; + private final boolean nullable; + + public Reference(String name) { + this(name, OBJECT_TYPE); + } + + public Reference(String name, TypeToken type) { + this.name = name; + this.type = type; + this.nullable = false; + } + + public Reference(String name, TypeToken type, boolean nullable) { + this.name = name; + this.type = type; + this.nullable = nullable; + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + if (nullable) { + String isNull = ctx.newName("isNull"); + String code = + StringUtils.format( + "boolean ${isNull} = ${name} == null;", "isNull", isNull, "name", name); + return new ExprCode( + code, Code.isNullVariable(isNull), Code.variable(getRawType(type), name)); + } else { + return new ExprCode(FalseLiteral, Code.variable(getRawType(type), name)); + } + } + + @Override + public boolean nullable() { + return nullable; + } + + public String name() { + return name; + } + + @Override + public String toString() { + return name; + } + } + + class Empty implements Expression { + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + return new ExprCode(""); + } + } + + class Block implements Expression { + private final String code; + + public Block(String code) { + this.code = code; + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + return new ExprCode(code, null, null); + } + } + + class ForceEvaluate implements Expression { + private Expression expression; + + public ForceEvaluate(Expression expression) { + this.expression = expression; + } + + @Override + public TypeToken type() { + return expression.type(); + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + return expression.doGenCode(ctx); + } + + @Override + public boolean nullable() { + return expression.nullable(); + } + + @Override + public String toString() { + return expression.toString(); + } + } + + class FieldValue implements Expression { + private Expression targetObject; + private String fieldName; + private TypeToken type; + private boolean fieldNullable; + private final boolean inline; + + public FieldValue(Expression targetObject, String fieldName, TypeToken type) { + this(targetObject, fieldName, type, !type.isPrimitive(), false); + } + + public FieldValue( + Expression targetObject, + String fieldName, + TypeToken type, + boolean fieldNullable, + boolean inline) { + Preconditions.checkArgument(type != null); + this.targetObject = targetObject; + this.fieldName = fieldName; + this.type = type; + this.fieldNullable = fieldNullable; + this.inline = inline; + if (inline) { + Preconditions.checkArgument(!fieldNullable); + } + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = targetObject.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()).append("\n"); + } + + Class rawType = getRawType(type); + // although isNull is not always used, we place it outside and get freshNames simultaneously + // to have same suffix, thus get more readability. + String[] freshNames = + ctx.newNames(fieldName, "is" + StringUtils.capitalize(fieldName) + "Null"); + String value = freshNames[0]; + String isNull = freshNames[1]; + if (inline) { + String inlinedValue = + StringUtils.format( + "${target}.${fieldName}", "target", targetExprCode.value(), "fieldName", fieldName); + return new ExprCode( + codeBuilder.toString(), FalseLiteral, Code.variable(rawType, inlinedValue)); + } else if (fieldNullable) { + codeBuilder.append(String.format("boolean %s = false;\n", isNull)); + String code = + StringUtils.format( + "${type} ${value} = ${target}.${fieldName};\n", + "type", + ctx.type(type), + "value", + value, + "defaultValue", + defaultValue(rawType), + "target", + targetExprCode.value(), + "fieldName", + fieldName); + codeBuilder.append(code); + + String nullCode = + StringUtils.format( + "if (${value} == null) {\n" + " ${isNull} = true;\n" + "}", + "value", + value, + "isNull", + isNull); + codeBuilder.append(nullCode); + return new ExprCode( + codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value)); + } else { + String code = + StringUtils.format( + "${type} ${value} = ${target}.${fieldName};", + "type", + getRawType(type).getCanonicalName(), + "value", + value, + "target", + targetExprCode.value(), + "fieldName", + fieldName); + codeBuilder.append(code); + return new ExprCode( + codeBuilder.toString(), FalseLiteral, Code.variable(getRawType(type), value)); + } + } + + @Override + public boolean nullable() { + return fieldNullable; + } + + @Override + public String toString() { + return String.format("(%s).%s", targetObject, fieldName); + } + } + + class SetField implements Expression { + private Expression targetObject; + private final String fieldName; + private Expression fieldValue; + + public SetField(Expression targetObject, String fieldName, Expression fieldValue) { + this.targetObject = targetObject; + this.fieldName = fieldName; + this.fieldValue = fieldValue; + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = targetObject.genCode(ctx); + ExprCode fieldValueExprCode = fieldValue.genCode(ctx); + Stream.of(targetExprCode, fieldValueExprCode) + .forEach( + exprCode -> { + if (StringUtils.isNotBlank(exprCode.code())) { + codeBuilder.append(exprCode.code()).append('\n'); + } + }); + // don't check whether ${target} null, place it in if expression + String assign = + StringUtils.format( + "${target}.${fieldName} = ${fieldValue};", + "target", + targetExprCode.value(), + "fieldName", + fieldName, + "fieldValue", + fieldValueExprCode.value()); + codeBuilder.append(assign); + return new ExprCode(codeBuilder.toString(), null, null); + } + + public String toString() { + return String.format("SetField(%s, %s, %s)", targetObject, fieldName, fieldValue); + } + } + + /** + * An expression to set up a stub in expression tree, so later tree building can replace it with + * other expression. + */ + class ReplaceStub implements Expression { + private Expression targetObject; + + public ReplaceStub() {} + + public ReplaceStub(Expression targetObject) { + this.targetObject = targetObject; + } + + @Override + public TypeToken type() { + return targetObject != null ? targetObject.type() : PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + return targetObject != null ? targetObject.doGenCode(ctx) : new ExprCode("", null, null); + } + + @Override + public boolean nullable() { + return targetObject != null && targetObject.nullable(); + } + + public void setTargetObject(Expression targetObject) { + this.targetObject = targetObject; + } + } + + class Cast implements Expression { + private Expression targetObject; + private final String castedValueNamePrefix; + private final TypeToken type; + private final boolean inline; + private final boolean ignoreUpcast; + + public Cast(Expression targetObject, TypeToken type) { + this(targetObject, type, "castedValue", true, true); + } + + public Cast(Expression targetObject, TypeToken type, String castedValueNamePrefix) { + this(targetObject, type, castedValueNamePrefix, false, true); + } + + public Cast( + Expression targetObject, + TypeToken type, + String castedValueNamePrefix, + boolean inline, + boolean ignoreUpcast) { + this.targetObject = targetObject; + this.type = type; + this.castedValueNamePrefix = castedValueNamePrefix; + this.inline = inline; + this.ignoreUpcast = ignoreUpcast; + checkArgument(ReflectionUtils.isPublic(type), "Type %s is not public", type); + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + if (ignoreUpcast + && (targetObject.type().equals(type) + || getRawType(type).isAssignableFrom(getRawType((targetObject.type()))))) { + return targetObject.genCode(ctx); + } + StringBuilder codeBuilder = new StringBuilder(); + Class rawType = getRawType(type); + ExprCode targetExprCode = targetObject.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()); + } + // workaround: Cannot cast "java.lang.Object" to "long". then use java auto box/unbox. + String withCast = ctx.type(type); + if (type.isPrimitive() && !targetObject.type().isPrimitive()) { + withCast = ctx.type(boxedType(getRawType(type))); + } + if (inline) { + String value = + StringUtils.format( + "((${withCast})${target})", "withCast", withCast, "target", targetExprCode.value()); + return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, value)); + } else { + String castedValue = ctx.newName(castedValueNamePrefix); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append("\n"); + } + String cast = + StringUtils.format( + "${type} ${castedValue} = (${withCast})${target};", + "type", + ctx.type(type), + "withCast", + withCast, + "castedValue", + castedValue, + "target", + targetExprCode.value()); + codeBuilder.append(cast); + return new ExprCode( + codeBuilder.toString(), targetExprCode.isNull(), Code.variable(rawType, castedValue)); + } + } + + @Override + public boolean nullable() { + return targetObject.nullable(); + } + + @Override + public String toString() { + return String.format("(%s)%s", type, targetObject); + } + } + + class Invoke implements Expression { + public Expression targetObject; + public final String functionName; + public final TypeToken type; + public Expression[] arguments; + private String returnNamePrefix; + private final boolean returnNullable; + private boolean needTryCatch; + private boolean inlineCall; + + /** Invoke don't return value, this is a procedure call for side effect. */ + public Invoke(Expression targetObject, String functionName, Expression... arguments) { + this(targetObject, functionName, PRIMITIVE_VOID_TYPE, false, arguments); + } + + /** Invoke don't accept arguments. */ + public Invoke(Expression targetObject, String functionName, TypeToken type) { + this(targetObject, functionName, type, false); + } + + /** Invoke don't accept arguments. */ + public Invoke( + Expression targetObject, String functionName, String returnNamePrefix, TypeToken type) { + this(targetObject, functionName, returnNamePrefix, type, false); + } + + public Invoke( + Expression targetObject, String functionName, TypeToken type, Expression... arguments) { + this(targetObject, functionName, "", type, false, arguments); + } + + public Invoke( + Expression targetObject, + String functionName, + TypeToken type, + boolean returnNullable, + Expression... arguments) { + this(targetObject, functionName, "", type, returnNullable, arguments); + } + + public Invoke( + Expression targetObject, + String functionName, + String returnNamePrefix, + TypeToken type, + boolean returnNullable, + Expression... arguments) { + this( + targetObject, + functionName, + returnNamePrefix, + type, + returnNullable, + ReflectionUtils.hasException(getRawType(targetObject.type()), functionName), + arguments); + } + + public Invoke( + Expression targetObject, + String functionName, + String returnNamePrefix, + TypeToken type, + boolean returnNullable, + boolean needTryCatch, + Expression... arguments) { + this.targetObject = targetObject; + this.functionName = functionName; + this.returnNamePrefix = returnNamePrefix; + this.type = type; + this.returnNullable = returnNullable; + this.arguments = arguments; + this.needTryCatch = needTryCatch; + } + + public static Invoke inlineInvoke( + Expression targetObject, String functionName, TypeToken type, Expression... arguments) { + Invoke invoke = new Invoke(targetObject, functionName, type, false, arguments); + invoke.inlineCall = true; + return invoke; + } + + public static Invoke inlineInvoke( + Expression targetObject, + String functionName, + TypeToken type, + boolean needTryCatch, + Expression... arguments) { + Invoke invoke = + new Invoke(targetObject, functionName, "", type, false, needTryCatch, arguments); + invoke.inlineCall = true; + return invoke; + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = targetObject.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()); + codeBuilder.append("\n"); + } + int len = arguments.length; + StringBuilder argsBuilder = new StringBuilder(); + if (len > 0) { + for (int i = 0; i < len; i++) { + Expression argExpr = arguments[i]; + ExprCode argExprCode = argExpr.genCode(ctx); + if (StringUtils.isNotBlank(argExprCode.code())) { + codeBuilder.append(argExprCode.code()).append("\n"); + } + if (i != 0) { + argsBuilder.append(", "); + } + argsBuilder.append(argExprCode.value()); + } + } + if (!inlineCall && type != null && !PRIMITIVE_VOID_TYPE.equals(type)) { + Class rawType = getRawType(type); + if (StringUtils.isBlank(returnNamePrefix)) { + returnNamePrefix = ctx.namePrefix(getRawType(type)); + } + String[] freshNames = ctx.newNames(returnNamePrefix, returnNamePrefix + "IsNull"); + String value = freshNames[0]; + String isNull = freshNames[1]; + if (returnNullable) { + codeBuilder.append(String.format("boolean %s = false;\n", isNull)); + String callCode = + ExpressionUtils.callFunc( + ctx.type(type), + value, + targetExprCode.value().code(), + functionName, + argsBuilder.toString(), + needTryCatch); + codeBuilder.append(callCode).append('\n'); + } else { + String callCode = + ExpressionUtils.callFunc( + ctx.type(type), + value, + targetExprCode.value().code(), + functionName, + argsBuilder.toString(), + needTryCatch); + codeBuilder.append(callCode); + } + + if (returnNullable) { + String nullCode = + StringUtils.format( + "if (${value} == null) {\n" + " ${isNull} = true;\n" + "}", + "value", + value, + "isNull", + isNull); + codeBuilder.append(nullCode); + return new ExprCode( + codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value)); + } else { + return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, value)); + } + } else if (inlineCall) { + CodeGenerator.stripIfHasLastNewline(codeBuilder); + String call = + ExpressionUtils.callFunc( + targetExprCode.value().code(), functionName, argsBuilder.toString(), needTryCatch); + call = call.substring(0, call.length() - 1); + return new ExprCode(codeBuilder.toString(), null, Code.variable(getRawType(type), call)); + } else { + String call = + ExpressionUtils.callFunc( + targetExprCode.value().code(), functionName, argsBuilder.toString(), needTryCatch); + codeBuilder.append(call); + return new ExprCode(codeBuilder.toString(), null, null); + } + } + + @Override + public boolean nullable() { + return returnNullable; + } + + @Override + public String toString() { + return String.format("%s.%s", targetObject, functionName); + } + } + + class StaticInvoke implements Expression { + private Class staticObject; + private String functionName; + private String returnNamePrefix; + private TypeToken type; + private Expression[] arguments; + private boolean returnNullable; + + public StaticInvoke(Class staticObject, String functionName, TypeToken type) { + this(staticObject, functionName, type, false); + } + + public StaticInvoke(Class staticObject, String functionName, Expression... arguments) { + this(staticObject, functionName, PRIMITIVE_VOID_TYPE, false, arguments); + } + + public StaticInvoke( + Class staticObject, String functionName, TypeToken type, Expression... arguments) { + this(staticObject, functionName, "", type, false, arguments); + } + + public StaticInvoke( + Class staticObject, + String functionName, + TypeToken type, + boolean returnNullable, + Expression... arguments) { + this(staticObject, functionName, "", type, returnNullable, arguments); + } + + /** + * static invoke method. + * + * @param staticObject The target of the static call + * @param functionName The name of the method to call + * @param returnNamePrefix returnNamePrefix + * @param type return type of the function call + * @param returnNullable When false, indicating the invoked method will always return non-null + * value. + * @param arguments An optional list of expressions to pass as arguments to the function + */ + public StaticInvoke( + Class staticObject, + String functionName, + String returnNamePrefix, + TypeToken type, + boolean returnNullable, + Expression... arguments) { + this.staticObject = staticObject; + this.functionName = functionName; + this.type = type; + this.arguments = arguments; + this.returnNullable = returnNullable; + this.returnNamePrefix = returnNamePrefix; + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + int len = arguments.length; + StringBuilder argsBuilder = new StringBuilder(); + if (len > 0) { + for (int i = 0; i < len; i++) { + Expression argExpr = arguments[i]; + ExprCode argExprCode = argExpr.genCode(ctx); + if (StringUtils.isNotBlank(argExprCode.code())) { + codeBuilder.append(argExprCode.code()).append("\n"); + } + if (i != 0) { + argsBuilder.append(", "); + } + argsBuilder.append(argExprCode.value()); + } + } + + if (StringUtils.isBlank(returnNamePrefix)) { + returnNamePrefix = ctx.newName(getRawType(type)); + } + boolean needTryCatch = ReflectionUtils.hasException(staticObject, functionName); + if (type != null && !PRIMITIVE_VOID_TYPE.equals(type)) { + Class rawType = getRawType(type); + String[] freshNames = ctx.newNames(returnNamePrefix, "isNull"); + String value = freshNames[0]; + String isNull = freshNames[1]; + if (returnNullable) { + codeBuilder.append(String.format("boolean %s = false;\n", isNull)); + String callCode = + ExpressionUtils.callFunc( + ctx.type(type), + value, + ctx.type(staticObject), + functionName, + argsBuilder.toString(), + needTryCatch); + codeBuilder.append(callCode).append('\n'); + } else { + String callCode = + ExpressionUtils.callFunc( + ctx.type(type), + value, + ctx.type(staticObject), + functionName, + argsBuilder.toString(), + needTryCatch); + codeBuilder.append(callCode); + } + + if (returnNullable) { + String nullCode = + StringUtils.format( + "if (${value} == null) {\n" + " ${isNull} = true;\n" + "}", + "value", + value, + "isNull", + isNull); + codeBuilder.append(nullCode); + return new ExprCode( + codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value)); + } else { + return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, value)); + } + } else { + String call = + ExpressionUtils.callFunc( + ctx.type(staticObject), functionName, argsBuilder.toString(), needTryCatch); + codeBuilder.append(call); + return new ExprCode(codeBuilder.toString(), null, null); + } + } + + @Override + public boolean nullable() { + return returnNullable; + } + + @Override + public String toString() { + return String.format("%s.%s", staticObject, functionName); + } + } + + class NewInstance implements Expression { + private TypeToken type; + private String unknownClassName; + private List arguments; + private Expression outerPointer; + private final boolean needOuterPointer; + + /** + * Create an expr which create a new object instance. + * + * @param interfaceType object declared in this type. actually the type is {@code + * unknownClassName} + * @param unknownClassName unknownClassName that's unknown in compile-time + */ + public NewInstance( + TypeToken interfaceType, String unknownClassName, Expression... arguments) { + this(interfaceType, Arrays.asList(arguments), null); + this.unknownClassName = unknownClassName; + check(); + } + + public NewInstance(TypeToken type, Expression... arguments) { + this(type, Arrays.asList(arguments), null); + check(); + } + + private NewInstance(TypeToken type, List arguments, Expression outerPointer) { + this.type = type; + this.outerPointer = outerPointer; + this.arguments = arguments; + this.needOuterPointer = + getRawType(type).isMemberClass() && !Modifier.isStatic(getRawType(type).getModifiers()); + if (needOuterPointer && (outerPointer == null)) { + String msg = + String.format("outerPointer can't be null when %s is instance inner class", type); + throw new CodegenException(msg); + } + } + + private void check() { + Preconditions.checkArgument( + !type.isArray(), "Please use " + NewArray.class + " to create array."); + if (unknownClassName == null && arguments.size() > 0) { + // If unknownClassName is not null, we don't have actual type object, + // we assume we can create instance of unknownClassName. + // If arguments size is 0, we can always create instance of class, even by + // unsafe.allocateInstance + boolean anyMatchParamCount = + Stream.of(getRawType(type).getConstructors()) + .anyMatch(c -> c.getParameterCount() == arguments.size()); + if (!anyMatchParamCount) { + String msg = + String.format( + "%s doesn't have a public constructor that take %d params", + type, arguments.size()); + throw new IllegalArgumentException(msg); + } + } + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + int len = arguments.size(); + StringBuilder argsBuilder = new StringBuilder(); + if (len > 0) { + for (int i = 0; i < len; i++) { + Expression argExpr = arguments.get(i); + ExprCode argExprCode = argExpr.genCode(ctx); + if (StringUtils.isNotBlank(argExprCode.code())) { + codeBuilder.append(argExprCode.code()).append("\n"); + } + if (i != 0) { + argsBuilder.append(", "); + } + argsBuilder.append(argExprCode.value()); + } + } + + Class rawType = getRawType(type); + String type = ctx.type(rawType); + String clzName = unknownClassName; + if (clzName == null) { + clzName = type; + } + if (needOuterPointer) { + // "${gen.value}.new ${cls.getSimpleName}($argString)" + throw new UnsupportedOperationException(); + } else { + String value = ctx.newName(rawType); + // class don't have a public no-arg constructor + if (arguments.isEmpty() && !ReflectionUtils.hasPublicNoArgConstructor(rawType)) { + // janino doesn't generics, so we cast manually. + String instance = ctx.newName("instance"); + String code = + ExpressionUtils.callFunc( + "Object", + instance, + ctx.type(Platform.class), + "newInstance", + clzName + ".class", + false); + codeBuilder.append(code).append('\n'); + String cast = + StringUtils.format( + "${clzName} ${value} = (${clzName})${instance};", + "clzName", + clzName, + "value", + value, + "instance", + instance); + codeBuilder.append(cast); + } else { + String code = + StringUtils.format( + "${clzName} ${value} = new ${clzName}(${args});", + "clzName", + clzName, + "value", + value, + "args", + argsBuilder.toString()); + codeBuilder.append(code); + } + return new ExprCode(codeBuilder.toString(), null, Code.variable(rawType, value)); + } + } + + @Override + public boolean nullable() { + return false; + } + + @Override + public String toString() { + return String.format("newInstance(%s)", type); + } + } + + class NewArray implements Expression { + private TypeToken type; + private Expression[] elements; + + private int numDimensions; + private Class elemType; + + private Expression dim; + + private Expression dims; + + public NewArray(Class elemType, Expression dim) { + this.numDimensions = 1; + this.elemType = elemType; + this.dim = dim; + type = TypeToken.of(Array.newInstance(elemType, 1).getClass()); + } + + /** + * Dynamic created array doesn't have generic type info, so we don't pass in TypeToken. + * + * @param elemType elemType + * @param numDimensions numDimensions + * @param dims an int[] represent dims + */ + public NewArray(Class elemType, int numDimensions, Expression dims) { + this.numDimensions = numDimensions; + this.elemType = elemType; + this.dims = dims; + + int[] stubSizes = new int[numDimensions]; + for (int i = 0; i < numDimensions; i++) { + stubSizes[i] = 1; + } + type = TypeToken.of(Array.newInstance(elemType, stubSizes).getClass()); + } + + public NewArray(TypeToken type, Expression... elements) { + this.type = type; + this.elements = elements; + this.numDimensions = 1; + } + + /** ex: new int[3][][]. */ + public static NewArray newArrayWithFirstDim( + Class elemType, int numDimensions, Expression firstDim) { + NewArray array = new NewArray(elemType, firstDim); + array.numDimensions = numDimensions; + return array; + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + Class rawType = getRawType(type); + String arrayType = getArrayType(rawType); + String value = ctx.newName("arr"); + if (dims != null) { + // multi-dimension array + ExprCode dimsExprCode = dims.genCode(ctx); + if (StringUtils.isNotBlank(dimsExprCode.code())) { + codeBuilder.append(dimsExprCode.code()).append('\n'); + } + // "${arrType} ${value} = new ${elementType}[$?][$?]... + codeBuilder + .append(arrayType) + .append(' ') + .append(value) + .append(" = new ") + .append(ctx.type(elemType)); + for (int i = 0; i < numDimensions; i++) { + // dims is dimensions array, which store size of per dim. + String idim = StringUtils.format("${dims}[${i}]", "dims", dimsExprCode.value(), "i", i); + codeBuilder.append('[').append(idim).append("]"); + } + codeBuilder.append(';'); + } else if (dim != null) { + ExprCode dimExprCode = dim.genCode(ctx); + if (StringUtils.isNotBlank(dimExprCode.code())) { + codeBuilder.append(dimExprCode.code()).append('\n'); + } + if (numDimensions > 1) { + // multi-dimension array + // "${arrType} ${value} = new ${elementType}[$?][][][]... + codeBuilder + .append(arrayType) + .append(' ') + .append(value) + .append(" = new ") + .append(ctx.type(elemType)); + codeBuilder.append('[').append(dimExprCode.value()).append(']'); + for (int i = 1; i < numDimensions; i++) { + codeBuilder.append('[').append("]"); + } + codeBuilder.append(';'); + } else { + // one-dimension array + String code = + StringUtils.format( + "${type} ${value} = new ${elemType}[${dim}];", + "type", + arrayType, + "elemType", + ctx.type(elemType), + "value", + value, + "dim", + dimExprCode.value()); + codeBuilder.append(code); + } + } else { + // create array with init value + int len = elements.length; + StringBuilder argsBuilder = new StringBuilder(); + if (len > 0) { + for (int i = 0; i < len; i++) { + Expression argExpr = elements[i]; + ExprCode argExprCode = argExpr.genCode(ctx); + if (StringUtils.isNotBlank(argExprCode.code())) { + codeBuilder.append(argExprCode.code()).append("\n"); + } + if (i != 0) { + argsBuilder.append(", "); + } + argsBuilder.append(argExprCode.value()); + } + } + + String code = + StringUtils.format( + "${type} ${value} = new ${type} {${args}};", + "type", + arrayType, + "value", + value, + "args", + argsBuilder.toString()); + codeBuilder.append(code); + } + + return new ExprCode(codeBuilder.toString(), null, Code.variable(rawType, value)); + } + } + + class AssignArrayElem implements Expression { + private Expression targetArray; + private Expression value; + private Expression[] indexes; + + public AssignArrayElem(Expression targetArray, Expression value, Expression... indexes) { + this.targetArray = targetArray; + this.value = value; + this.indexes = indexes; + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = targetArray.genCode(ctx); + ExprCode valueCode = value.genCode(ctx); + Stream.of(targetExprCode, valueCode) + .forEach( + exprCode -> { + if (StringUtils.isNotBlank(exprCode.code())) { + codeBuilder.append(exprCode.code()).append('\n'); + } + }); + + ExprCode[] indexExprCodes = new ExprCode[indexes.length]; + for (int i = 0; i < indexes.length; i++) { + Expression indexExpr = indexes[i]; + ExprCode indexExprCode = indexExpr.genCode(ctx); + indexExprCodes[i] = indexExprCode; + if (StringUtils.isNotBlank(indexExprCode.code())) { + codeBuilder.append(indexExprCode.code()).append("\n"); + } + } + codeBuilder.append(targetExprCode.value()); + for (int i = 0; i < indexes.length; i++) { + codeBuilder.append('[').append(indexExprCodes[i].value()).append(']'); + } + codeBuilder.append(" = ").append(valueCode.value()).append(";"); + return new ExprCode(codeBuilder.toString(), FalseLiteral, null); + } + } + + class If implements Expression { + private Expression predicate; + private Expression trueExpr; + private Expression falseExpr; + private TypeToken type; + private boolean nullable; + + public If(Expression predicate, Expression trueExpr) { + this.predicate = predicate; + this.trueExpr = trueExpr; + this.nullable = false; + this.type = PRIMITIVE_VOID_TYPE; + } + + public If( + Expression predicate, + Expression trueExpr, + Expression falseExpr, + boolean nullable, + TypeToken type) { + this.predicate = predicate; + this.trueExpr = trueExpr; + this.falseExpr = falseExpr; + this.nullable = nullable; + this.type = type; + } + + public If(Expression predicate, Expression trueExpr, Expression falseExpr, boolean nullable) { + this(predicate, trueExpr, falseExpr); + this.nullable = nullable; + } + + /** if predicate eval to null, take predicate as false. */ + public If(Expression predicate, Expression trueExpr, Expression falseExpr) { + this.predicate = predicate; + this.trueExpr = trueExpr; + this.falseExpr = falseExpr; + + if (trueExpr.type() == falseExpr.type()) { + if (trueExpr.type() != null && !PRIMITIVE_VOID_TYPE.equals(trueExpr.type())) { + type = trueExpr.type(); + } else { + type = PRIMITIVE_VOID_TYPE; + } + } else { + if (trueExpr.type() != null && falseExpr.type() != null) { + if (Primitives.isWrapperType(getRawType(trueExpr.type())) + && trueExpr.type().equals(falseExpr.type().wrap())) { + type = trueExpr.type(); + } else if (Primitives.isWrapperType(getRawType(falseExpr.type())) + && falseExpr.type().equals(trueExpr.type().wrap())) { + type = falseExpr.type(); + } else if (trueExpr.type().isSupertypeOf(falseExpr.type())) { + type = trueExpr.type(); + } else if (falseExpr.type().isSupertypeOf(trueExpr.type())) { + type = falseExpr.type(); + } else if (PRIMITIVE_VOID_TYPE.equals(trueExpr.type()) + || PRIMITIVE_VOID_TYPE.equals(falseExpr.type())) { + type = PRIMITIVE_VOID_TYPE; + } else { + type = OBJECT_TYPE; + } + } else { + type = PRIMITIVE_VOID_TYPE; + } + } + nullable = !PRIMITIVE_VOID_TYPE.equals(type) && !type.isPrimitive(); + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + ExprCode condEval = predicate.doGenCode(ctx); + ExprCode trueEval = trueExpr.doGenCode(ctx); + StringBuilder codeBuilder = new StringBuilder(); + if (StringUtils.isNotBlank(condEval.code())) { + codeBuilder.append(condEval.code()).append('\n'); + } + String cond; + if (condEval.isNull() != null && !"false".equals(condEval.isNull().code())) { + // indicate condEval.isNull() is a variable. "false" is a java keyword, thus is not a + // variable + cond = + StringUtils.format( + "!${condEvalIsNull} && ${condEvalValue}", + "condEvalIsNull", + condEval.isNull(), + "condEvalValue", + condEval.value()); + } else { + cond = StringUtils.format("${condEvalValue}", "condEvalValue", condEval.value()); + } + + if (!PRIMITIVE_VOID_TYPE.equals(type.unwrap())) { + ExprCode falseEval = falseExpr.doGenCode(ctx); + Preconditions.checkArgument(trueEval.isNull() != null || falseEval.isNull() != null); + Preconditions.checkNotNull(trueEval.value()); + Preconditions.checkNotNull(falseEval.value()); + Class rawType = getRawType(type); + String[] freshNames = ctx.newNames(rawType, "isNull"); + String value = freshNames[0]; + String isNull = freshNames[1]; + codeBuilder.append(String.format("%s %s;\n", ctx.type(type), value)); + String ifCode; + if (nullable) { + codeBuilder.append(String.format("boolean %s = false;\n", isNull)); + String trueEvalIsNull; + if (trueEval.isNull() == null) { + trueEvalIsNull = "false"; + } else { + trueEvalIsNull = trueEval.isNull().code(); + } + String falseEvalIsNull; + if (falseEval.isNull() == null) { + falseEvalIsNull = "false"; + } else { + falseEvalIsNull = falseEval.isNull().code(); + } + ifCode = + StringUtils.format( + "" + + "if (${cond}) {\n" + + " ${trueEvalCode}\n" + + " ${isNull} = ${trueEvalIsNull};\n" + + " ${value} = ${trueEvalValue};\n" + + "} else {\n" + + " ${falseEvalCode}\n" + + " ${isNull} = ${falseEvalIsNull};\n" + + " ${value} = ${falseEvalValue};\n" + + "}", + "isNull", + isNull, + "value", + value, + "cond", + cond, + "trueEvalCode", + alignIndent(trueEval.code()), + "trueEvalIsNull", + trueEvalIsNull, + "trueEvalValue", + trueEval.value(), + "falseEvalCode", + alignIndent(falseEval.code()), + "falseEvalIsNull", + falseEvalIsNull, + "falseEvalValue", + falseEval.value()); + } else { + ifCode = + StringUtils.format( + "" + + "if (${cond}) {\n" + + " ${trueEvalCode}\n" + + " ${value} = ${trueEvalValue};\n" + + "} else {\n" + + " ${falseEvalCode}\n" + + " ${value} = ${falseEvalValue};\n" + + "}", + "cond", + cond, + "value", + value, + "trueEvalCode", + alignIndent(trueEval.code()), + "trueEvalValue", + trueEval.value(), + "falseEvalCode", + alignIndent(falseEval.code()), + "falseEvalValue", + falseEval.value()); + } + codeBuilder.append(StringUtils.stripBlankLines(ifCode)); + return new ExprCode( + codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value)); + } else { + String ifCode; + if (falseExpr != null) { + ExprCode falseEval = falseExpr.doGenCode(ctx); + ifCode = + StringUtils.format( + "if (${cond}) {\n" + + " ${trueEvalCode}\n" + + "} else {\n" + + " ${falseEvalCode}\n" + + "}", + "cond", + cond, + "trueEvalCode", + alignIndent(trueEval.code()), + "falseEvalCode", + alignIndent(falseEval.code())); + } else { + ifCode = + StringUtils.format( + "if (${cond}) {\n" + " ${trueEvalCode}\n" + "}", + "cond", + cond, + "trueEvalCode", + alignIndent(trueEval.code())); + } + codeBuilder.append(ifCode); + return new ExprCode(codeBuilder.toString()); + } + } + + @Override + public boolean nullable() { + return nullable; + } + + @Override + public String toString() { + if (falseExpr != null) { + return String.format("if (%s) %s else %s", predicate, trueExpr, falseExpr); + } else { + return String.format("if (%s) %s", predicate, trueExpr); + } + } + } + + class IsNull implements Expression { + private Expression expr; + + public IsNull(Expression expr) { + this.expr = expr; + } + + @Override + public TypeToken type() { + return PRIMITIVE_BOOLEAN_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + ExprCode targetExprCode = expr.genCode(ctx); + Preconditions.checkNotNull(targetExprCode.isNull()); + return new ExprCode(targetExprCode.code(), FalseLiteral, targetExprCode.isNull()); + } + + @Override + public boolean nullable() { + return false; + } + + @Override + public String toString() { + return String.format("IsNull(%s)", expr); + } + } + + class Not implements Expression { + private Expression target; + + public Not(Expression target) { + this.target = target; + Preconditions.checkArgument( + target.type() == PRIMITIVE_BOOLEAN_TYPE || target.type() == BOOLEAN_TYPE); + } + + @Override + public TypeToken type() { + return target.type(); + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + ExprCode targetExprCode = target.genCode(ctx); + // whether need to check null for BOOLEAN_TYPE. The question is what to do when target.value + // is null. + String value = String.format("(!%s)", targetExprCode.value()); + return new ExprCode(targetExprCode.code(), FalseLiteral, Code.variable(boolean.class, value)); + } + + @Override + public boolean nullable() { + return false; + } + + @Override + public String toString() { + return String.format("!(%s)", target); + } + } + + class BinaryOperator extends ValueExpression { + private final boolean inline; + private final String operator; + private final TypeToken type; + private Expression left; + private Expression right; + + public BinaryOperator(String operator, Expression left, Expression right) { + this(false, operator, left, right); + } + + public BinaryOperator(boolean inline, String operator, Expression left, Expression right) { + this.inline = inline; + this.operator = operator; + this.left = left; + this.right = right; + if (isPrimitive(getRawType(left.type()))) { + Preconditions.checkArgument(isPrimitive(getRawType(right.type()))); + type = + getSizeOfPrimitiveType(left.type()) > getSizeOfPrimitiveType(right.type()) + ? left.type() + : right.type(); + } else { + if (left.type().isSupertypeOf(right.type())) { + type = left.type(); + } else if (left.type().isSubtypeOf(right.type())) { + type = right.type(); + } else { + throw new IllegalArgumentException( + String.format("Arguments type %s vs %s inconsistent", left.type(), right.type())); + } + } + } + + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + StringBuilder arith = new StringBuilder(); + Expression[] operands = new Expression[] {left, right}; + for (int i = 0; i < operands.length; i++) { + Expression operand = operands[i]; + ExprCode code = operand.genCode(ctx); + if (StringUtils.isNotBlank(code.code())) { + appendNewlineIfNeeded(codeBuilder); + codeBuilder.append(code.code()); + } + if (i != operands.length - 1) { + arith.append(code.value()).append(' ').append(operator).append(' '); + } else { + arith.append(code.value()); + } + } + + if (inline) { + String value = String.format("(%s)", arith); + String code = StringUtils.isBlank(codeBuilder) ? null : codeBuilder.toString(); + return new ExprCode(code, FalseLiteral, Code.variable(getRawType(type), value)); + } else { + appendNewlineIfNeeded(codeBuilder); + String value = ctx.newName(valuePrefix); + String valueExpr = + StringUtils.format( + "${type} ${value} = ${arith};", + "type", + ctx.type(type), + "value", + value, + "arith", + arith); + codeBuilder.append(valueExpr); + return new ExprCode( + codeBuilder.toString(), FalseLiteral, Code.variable(getRawType(type), value)); + } + } + + @Override + public String toString() { + return String.format("%s %s %s", left, operator, right); + } + } + + class Comparator extends BinaryOperator { + public Comparator(String operator, Expression left, Expression right, boolean inline) { + super(inline, operator, left, right); + } + + @Override + public TypeToken type() { + return PRIMITIVE_BOOLEAN_TYPE; + } + } + + class Arithmetic extends BinaryOperator { + public Arithmetic(String operator, Expression left, Expression right) { + super(operator, left, right); + } + + public Arithmetic(boolean inline, String operator, Expression left, Expression right) { + super(inline, operator, left, right); + } + } + + class Add extends Arithmetic { + public Add(Expression left, Expression right) { + super("+", left, right); + } + + public Add(boolean inline, Expression left, Expression right) { + super(inline, "+", left, right); + } + } + + class Subtract extends Arithmetic { + public Subtract(Expression left, Expression right) { + super("-", left, right); + } + + public Subtract(boolean inline, Expression left, Expression right) { + super(inline, "-", left, right); + } + } + + class BitAnd extends Arithmetic { + public BitAnd(Expression left, Expression right) { + super(true, "&", left, right); + } + + public BitAnd(boolean inline, Expression left, Expression right) { + super(inline, "&", left, right); + } + } + + class BitOr extends Arithmetic { + + public BitOr(Expression left, Expression right) { + this(true, left, right); + } + + public BitOr(boolean inline, Expression left, Expression right) { + super(inline, "|", left, right); + } + } + + class BitShift extends Arithmetic { + + public BitShift(String operator, Expression operand, int numBits) { + this(true, operator, operand, new Literal(numBits, PRIMITIVE_INT_TYPE)); + } + + public BitShift(boolean inline, String operator, Expression left, Expression right) { + super(inline, operator, left, right); + } + } + + class LogicalOperator extends BinaryOperator { + + public LogicalOperator(String operator, Expression left, Expression right) { + super(operator, left, right); + } + + public LogicalOperator(boolean inline, String operator, Expression left, Expression right) { + super(inline, operator, left, right); + } + } + + class LogicalAnd extends LogicalOperator { + public LogicalAnd(Expression left, Expression right) { + super(true, "&&", left, right); + } + + public LogicalAnd(boolean inline, Expression left, Expression right) { + super(inline, "&", left, right); + } + } + + class While implements Expression { + private final BinaryOperator predicate; + private Expression action; + private Expression[] cutPoints; + + /** + * Create a while-block with specified predict and while body. + * + * @param predicate predicate must be inline. + */ + public While(BinaryOperator predicate, Expression action) { + this(predicate, action, new Expression[0]); + } + + public While(BinaryOperator predicate, Functions.SerializableSupplier action) { + this( + predicate, + action.get(), + Functions.extractCapturedVariables(action, o -> o instanceof Expression) + .toArray(new Expression[0])); + } + + public While(BinaryOperator predicate, Expression action, Expression[] cutPoints) { + this.predicate = predicate; + this.action = action; + this.cutPoints = cutPoints; + Preconditions.checkArgument(predicate.inline, predicate); + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + for (Expression cutPoint : cutPoints) { + ExprCode code = cutPoint.genCode(ctx); + if (StringUtils.isNotBlank(code.code())) { + appendNewlineIfNeeded(codeBuilder); + codeBuilder.append(code.code()).append("\n"); + } + } + ExprCode predicateExprCode = predicate.genCode(ctx); + if (StringUtils.isNotBlank(predicateExprCode.code())) { + codeBuilder.append(predicateExprCode.code()); + appendNewlineIfNeeded(codeBuilder); + } + ExprCode actionExprCode = action.genCode(ctx); + String whileCode = + StringUtils.format( + "while (${predicate}) {\n" + "${action}\n" + "}", + "predicate", + predicateExprCode.value(), + "action", + indent(actionExprCode.code())); + codeBuilder.append(whileCode); + return new ExprCode(codeBuilder.toString(), FalseLiteral, null); + } + + @Override + public String toString() { + return String.format("while(%s) {%s}", predicate, action); + } + } + + class ForEach implements Expression { + private Expression inputObject; + + @ClosureVisitable + private final Functions.SerializableBiFunction action; + + private final TypeToken elementType; + + /** + * inputObject.type() must be multi-dimension array or Collection, not allowed to be primitive + * array + */ + public ForEach( + Expression inputObject, + Functions.SerializableBiFunction action) { + this.inputObject = inputObject; + this.action = action; + TypeToken elementType; + if (inputObject.type().isArray()) { + elementType = inputObject.type().getComponentType(); + } else { + elementType = getElementType(inputObject.type()); + } + this.elementType = ReflectionUtils.getPublicSuperType(elementType); + } + + public ForEach( + Expression inputObject, + TypeToken beanType, + Functions.SerializableBiFunction action) { + this.inputObject = inputObject; + this.action = action; + this.elementType = beanType; + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = inputObject.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()).append("\n"); + } + String i = ctx.newName("i"); + String elemValue = ctx.newName("elemValue"); + Expression elementExpr = + action.apply(new Literal(i), new Reference(elemValue, elementType, false)); + ExprCode elementExprCode = elementExpr.genCode(ctx); + + if (inputObject.type().isArray()) { + String code = + StringUtils.format( + "" + + "int ${len} = ${arr}.length;\n" + + "int ${i} = 0;\n" + + "while (${i} < ${len}) {\n" + + " ${elemType} ${elemValue} = ${arr}[${i}];\n" + + " ${elementExprCode}\n" + + " ${i}++;\n" + + "}", + "arr", + targetExprCode.value(), + "len", + ctx.newName("len"), + "i", + i, + "elemType", + ctx.type(elementType), + "elemValue", + elemValue, + "i", + i, + "elementExprCode", + alignIndent(elementExprCode.code())); + codeBuilder.append(code); + return new ExprCode(codeBuilder.toString(), null, null); + } else { + // don't use forloop for List, because it may be LinkedList. + Preconditions.checkArgument( + ITERABLE_TYPE.isSupertypeOf(inputObject.type()), + "Unsupported type " + inputObject.type()); + String elemTypeCast = ""; + if (getRawType(elementType) != Object.class) { + // elementType may be unresolved generic`E` + elemTypeCast = "(" + ctx.type(elementType) + ")"; + } + String code = + StringUtils.format( + "" + + "java.util.Iterator ${iter} = ${input}.iterator();\n" + + "int ${i} = 0;\n" + + "while (${iter}.hasNext()) {\n" + + " ${elemType} ${elemValue} = ${elemTypeCast}${iter}.next();\n" + + " ${elementExprCode}\n" + + " ${i}++;\n" + + "}", + "iter", + ctx.newName("iter"), + "i", + i, + "input", + targetExprCode.value().code(), + "elemType", + ctx.type(elementType), + "elemTypeCast", + elemTypeCast, + "elemValue", + elemValue, + "elementExprCode", + alignIndent(elementExprCode.code())); + codeBuilder.append(code); + return new ExprCode(codeBuilder.toString(), null, null); + } + } + + @Override + public String toString() { + return String.format("ForEach(%s, %s)", inputObject, action); + } + } + + class ZipForEach implements Expression { + private Expression left; + private Expression right; + + @ClosureVisitable + private final Functions.SerializableTriFunction + action; + + public ZipForEach( + Expression left, + Expression right, + Functions.SerializableTriFunction action) { + this.left = left; + this.right = right; + this.action = action; + Preconditions.checkArgument( + left.type().isArray() || TypeToken.of(Collection.class).isSupertypeOf(left.type())); + Preconditions.checkArgument( + right.type().isArray() || TypeToken.of(Collection.class).isSupertypeOf(right.type())); + if (left.type().isArray()) { + Preconditions.checkArgument( + right.type().isArray(), "Should both be array or neither be array"); + } + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode leftExprCode = left.genCode(ctx); + ExprCode rightExprCode = right.genCode(ctx); + Stream.of(leftExprCode, rightExprCode) + .forEach( + exprCode -> { + if (StringUtils.isNotBlank(exprCode.code())) { + codeBuilder.append(exprCode.code()).append('\n'); + } + }); + String i = ctx.newName("i"); + String leftElemValue = ctx.newName("leftElemValue"); + String rightElemValue = ctx.newName("rightElemValue"); + TypeToken leftElemType; + if (left.type().isArray()) { + leftElemType = left.type().getComponentType(); + } else { + leftElemType = getElementType(left.type()); + } + leftElemType = ReflectionUtils.getPublicSuperType(leftElemType); + TypeToken rightElemType; + if (right.type().isArray()) { + rightElemType = right.type().getComponentType(); + } else { + rightElemType = getElementType(right.type()); + } + rightElemType = ReflectionUtils.getPublicSuperType(rightElemType); + Expression elemExpr = + action.apply( + new Literal(i), + new Reference(leftElemValue, leftElemType, true), + // elemValue nullability check use isNullAt inside action, so elemValueRef'nullable is + // false. + new Reference(rightElemValue, rightElemType, false)); + ExprCode elementExprCode = elemExpr.genCode(ctx); + + if (left.type().isArray()) { + String code = + StringUtils.format( + "" + + "int ${len} = ${leftArr}.length;\n" + + "int ${i} = 0;\n" + + "while (${i} < ${len}) {\n" + + " ${leftElemType} ${leftElemValue} = ${leftArr}[${i}];\n" + + " ${rightElemType} ${rightElemValue} = ${rightArr}[${i}];\n" + + " ${elementExprCode}\n" + + " ${i}++;\n" + + "}", + "leftArr", + leftExprCode.value(), + "len", + ctx.newName("len"), + "i", + i, + "leftElemType", + ctx.type(leftElemType), + "leftElemValue", + leftElemValue, + "rightElemType", + ctx.type(rightElemType), + "rightElemValue", + rightElemValue, + "rightArr", + rightExprCode.value(), + "elementExprCode", + alignIndent(elementExprCode.code())); + codeBuilder.append(code); + return new ExprCode(codeBuilder.toString(), null, null); + } else { + // CHECKSTYLE.OFF:LineLength + String code = + StringUtils.format( + "" + + "java.util.Iterator ${leftIter} = ${leftInput}.iterator();\n" + + "java.util.Iterator ${rightIter} = ${rightInput}.iterator();\n" + + "int ${i} = 0;\n" + + "while (${leftIter}.hasNext() && ${rightIter}.hasNext()) {\n" + + " ${leftElemType} ${leftElemValue} = (${leftElemType})${leftIter}.next();\n" + + " ${rightElemType} ${rightElemValue} = (${rightElemType})${rightIter}.next();\n" + + " ${elementExprCode}\n" + + " ${i}++;\n" + + "}", + "leftIter", + // CHECKSTYLE.ON:LineLength + ctx.newName("leftIter"), + "leftInput", + leftExprCode.value(), + "rightIter", + ctx.newName("rightIter"), + "rightInput", + rightExprCode.value(), + "i", + i, + "leftElemType", + ctx.type(leftElemType), + "leftElemValue", + leftElemValue, + "rightElemType", + ctx.type(rightElemType), + "rightElemValue", + rightElemValue, + "elementExprCode", + alignIndent(elementExprCode.code())); + codeBuilder.append(code); + return new ExprCode(codeBuilder.toString(), null, null); + } + } + } + + class ForLoop implements Expression { + public Expression start; + public Expression end; + public Expression step; + + @ClosureVisitable public final Functions.SerializableFunction action; + + public ForLoop( + Expression start, + Expression end, + Expression step, + Functions.SerializableFunction action) { + this.start = start; + this.end = end; + this.step = step; + this.action = action; + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + Class maxType = maxType(getRawType(start.type()), getRawType(end.type())); + Preconditions.checkArgument(maxType.isPrimitive()); + StringBuilder codeBuilder = new StringBuilder(); + String i = ctx.newName("i"); + Reference iref = new Reference(i, TypeToken.of(maxType)); + Expression loopAction = action.apply(iref); + ExprCode startExprCode = start.genCode(ctx); + ExprCode endExprCode = end.genCode(ctx); + ExprCode stepExprCode = step.genCode(ctx); + ExprCode actionExprCode = loopAction.genCode(ctx); + Stream.of(startExprCode, endExprCode, stepExprCode) + .forEach( + exprCode -> { + if (StringUtils.isNotBlank(exprCode.code())) { + codeBuilder.append(exprCode.code()).append('\n'); + } + }); + + String forCode = + StringUtils.format( + "" + + "for (${type} ${i} = ${start}; ${i} < ${end}; ${i}+=${step}) {\n" + + "${actionCode}\n" + + "}", + "type", + maxType.toString(), + "i", + i, + "start", + startExprCode.value(), + "end", + endExprCode.value(), + "step", + stepExprCode.value(), + "actionCode", + indent(actionExprCode.code())); + codeBuilder.append(forCode); + return new ExprCode(codeBuilder.toString(), null, null); + } + } + + /** + * build list from iterable. + * + *

Since value is declared to be {@code List}, no need to cast in other expression + * that need List + */ + class ListFromIterable implements Expression { + private final TypeToken elementType; + private Expression inputObject; + private final TypeToken type; + + public ListFromIterable(Expression inputObject) { + this.inputObject = inputObject; + Preconditions.checkArgument( + getRawType(inputObject.type()) == Iterable.class, + "wrong type of inputObject, get " + inputObject.type()); + elementType = getElementType(inputObject.type()); + this.type = inputObject.type().getSubtype(List.class); + } + + /** Returns inputObject.type(), not {@code List}. */ + @Override + public TypeToken type() { + return type; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = inputObject.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()).append("\n"); + } + String iter = ctx.newName("iter"); + String list = ctx.newName("list"); + String elemValue = ctx.newName("elemValue"); + + Class rawType = getRawType(elementType); + // janino don't support Type inference for generic instance creation + String code = + StringUtils.format( + "" + + "java.util.List<${className}> ${list} = new java.util.ArrayList<${className}>();\n" + + "java.util.Iterator ${iter} = ${iterable}.iterator();\n" + + "while (${iter}.hasNext()) {\n" + + " ${className} ${elemValue} = (${className})${iter}.next();\n" + + " ${list}.add(${elemValue});\n" + + "}", + "className", + ctx.type(rawType), + "list", + list, + "iter", + iter, + "iterable", + targetExprCode.value(), + "elemValue", + elemValue); + codeBuilder.append(code); + return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, list)); + } + + @Override + public String toString() { + return String.format("%s(%s)", getClass().getSimpleName(), inputObject); + } + } + + class Return implements Expression { + private Expression expression; + + public Return(Expression expression) { + this.expression = expression; + } + + @Override + public TypeToken type() { + return expression.type(); + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = expression.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()).append('\n'); + } + codeBuilder.append("return ").append(targetExprCode.value()).append(';'); + return new ExprCode(codeBuilder.toString(), null, null); + } + + @Override + public String toString() { + return String.format("return %s", expression); + } + } + + class Assign implements Expression { + private Expression from; + private Expression to; + + public Assign(Expression from, Expression to) { + this.from = from; + this.to = to; + } + + @Override + public TypeToken type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode fromExprCode = from.genCode(ctx); + ExprCode toExprCode = to.genCode(ctx); + Stream.of(fromExprCode, toExprCode) + .forEach( + exprCode -> { + if (StringUtils.isNotBlank(exprCode.code())) { + codeBuilder.append(exprCode.code()).append('\n'); + } + }); + String assign = + StringUtils.format( + "${from} = ${to};", "from", fromExprCode.value(), "to", toExprCode.value()); + codeBuilder.append(assign); + return new ExprCode(codeBuilder.toString(), null, null); + } + + @Override + public String toString() { + return String.format("%s = %s", from, to); + } + } } diff --git a/java/fury-core/src/main/java/io/fury/codegen/ExpressionUtils.java b/java/fury-core/src/main/java/io/fury/codegen/ExpressionUtils.java new file mode 100644 index 0000000000..b8038f0d67 --- /dev/null +++ b/java/fury-core/src/main/java/io/fury/codegen/ExpressionUtils.java @@ -0,0 +1,218 @@ +/* + * Copyright 2023 The Fury authors + * 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 io.fury.codegen; + +import static io.fury.codegen.Expression.Arithmetic; +import static io.fury.codegen.Expression.Comparator; +import static io.fury.codegen.Expression.IsNull; +import static io.fury.codegen.Expression.Literal; +import static io.fury.codegen.Expression.NewArray; +import static io.fury.codegen.Expression.Not; +import static io.fury.codegen.Expression.StaticInvoke; +import static io.fury.type.TypeUtils.getRawType; + +import com.google.common.base.Preconditions; +import com.google.common.reflect.TypeToken; +import io.fury.codegen.Expression.Cast; +import io.fury.codegen.Expression.Null; +import io.fury.util.Functions; +import io.fury.util.StringUtils; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Expression utils to create expression and code in a more convenient way. + * + * @author chaokunyang + */ +@SuppressWarnings("UnstableApiUsage") +public class ExpressionUtils { + + public static Expression newObjectArray(Expression... expressions) { + return new NewArray(TypeToken.of(Object[].class), expressions); + } + + public static Expression valueOf(TypeToken type, Expression value) { + return new StaticInvoke(getRawType(type), "valueOf", type, false, value); + } + + public static IsNull isNull(Expression target) { + return new IsNull(target); + } + + public static Expression notNull(Expression target) { + return new Not(new IsNull(target)); + } + + public static Expression eqNull(Expression target) { + Preconditions.checkArgument(!target.type().isPrimitive()); + return eq(target, new Null(target.type())); + } + + public static Not not(Expression target) { + return new Not(target); + } + + public static Literal nullValue(TypeToken type) { + return new Literal(null, type); + } + + public static Literal literalStr(String value) { + value = String.format("\"%s\"", value); + return new Literal(value); + } + + public static Comparator eq(Expression left, Expression right) { + return new Comparator("==", left, right, true); + } + + public static Comparator eq(Expression left, Expression right, String valuePrefix) { + Comparator comparator = new Comparator("==", left, right, false); + comparator.valuePrefix = valuePrefix; + return comparator; + } + + public static Comparator neq(Expression left, Expression right) { + return new Comparator("!=", left, right, true); + } + + public static Comparator egt(Expression left, Expression right) { + return new Comparator(">=", left, right, true); + } + + public static Comparator egt(Expression left, Expression right, String valuePrefix) { + Comparator comparator = new Comparator(">=", left, right, false); + comparator.valuePrefix = valuePrefix; + return comparator; + } + + public static Comparator lessThan(Expression left, Expression right) { + return new Comparator("<", left, right, true); + } + + public static Arithmetic add(Expression left, Expression right) { + return new Arithmetic(true, "+", left, right); + } + + public static Arithmetic add(Expression left, Expression right, String valuePrefix) { + Arithmetic arithmetic = new Arithmetic(true, "+", left, right); + arithmetic.valuePrefix = valuePrefix; + return arithmetic; + } + + public static Arithmetic subtract(Expression left, Expression right) { + return new Arithmetic(true, "-", left, right); + } + + public static Arithmetic subtract(Expression left, Expression right, String valuePrefix) { + Arithmetic arithmetic = new Arithmetic(true, "-", left, right); + arithmetic.valuePrefix = valuePrefix; + return arithmetic; + } + + public static Cast cast(Expression value, TypeToken typeToken) { + return new Cast(value, typeToken); + } + + static String callFunc( + String type, + String resultVal, + String target, + String functionName, + String args, + boolean needTryCatch) { + if (needTryCatch) { + return StringUtils.format( + "${type} ${value};\n" + + "try {\n" + + " ${value} = ${target}.${functionName}(${args});\n" + + "} catch (Exception e) {\n" + + " throw new RuntimeException(e);\n" + + "}", + "type", + type, + "value", + resultVal, + "target", + target, + "functionName", + functionName, + "args", + args); + } else { + return StringUtils.format( + "${type} ${value} = ${target}.${functionName}(${args});", + "type", + type, + "value", + resultVal, + "target", + target, + "functionName", + functionName, + "args", + args); + } + } + + static String callFunc(String target, String functionName, String args, boolean needTryCatch) { + if (needTryCatch) { + return StringUtils.format( + "try {\n" + + " ${target}.${functionName}(${args});\n" + + "} catch (Exception e) {\n" + + " throw new RuntimeException(e);\n" + + "}", + "target", + target, + "functionName", + functionName, + "args", + args); + } else { + return StringUtils.format( + "${target}.${functionName}(${args});", + "target", + target, + "functionName", + functionName, + "args", + args); + } + } + + public static List extractCapturedExpressions(Serializable closure) { + List expressions = new ArrayList<>(); + Functions.extractCapturedVariables( + closure, + capturedArg -> { + if (capturedArg instanceof Expression) { + // FIXME may need to check list/container values types? + expressions.add((Expression) capturedArg); + } else if (capturedArg instanceof Expression[]) { + Collections.addAll(Arrays.asList((Expression[]) capturedArg)); + } + return false; + }); + return expressions; + } +}