--- /dev/null
+package org.simantics.scl.compiler.internal.codegen.utils;
+
+import java.util.Arrays;
+
+import org.cojen.classfile.TypeDesc;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.simantics.scl.compiler.internal.codegen.references.Val;
+import org.simantics.scl.compiler.internal.codegen.types.JavaTypeTranslator;
+import org.simantics.scl.compiler.types.Type;
+
+public class CodeBuilderUtils {
+
+ /**
+ * Creates fields c0,...,c{N-1} to the given class, where c is fieldNamePrefix and N is the length of types.
+ * Creates also a constructor for the fields.
+ * @param classFile
+ * @param fieldModifiers
+ * @param fieldNamePrefix
+ * @param types
+ */
+ public static void makeRecord(ClassBuilder classBuilder, String recordName, int fieldModifiers, String fieldNamePrefix, TypeDesc[] types,
+ boolean generateEqualsAndHashCode) {
+ // Create fields
+ for(int i=0;i<types.length;++i)
+ if(!types[i].equals(TypeDesc.VOID))
+ classBuilder.addField(fieldModifiers, fieldNamePrefix+i, types[i]);
+
+ // Create constructor
+ MethodBuilderBase mb = classBuilder.addConstructor(
+ types.length == 0 ? Opcodes.ACC_PRIVATE : Opcodes.ACC_PUBLIC,
+ JavaTypeTranslator.filterVoid(types));
+ mb.loadThis();
+ mb.invokeConstructor(classBuilder.getSuperClassName(), Constants.EMPTY_TYPEDESC_ARRAY);
+ if(types.length == 0) {
+ TypeDesc thisClass = classBuilder.getType();
+ classBuilder.addField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "INSTANCE", thisClass);
+
+ MethodBuilderBase inimb = classBuilder.addInitializerBase();
+ inimb.newObject(thisClass);
+ inimb.dup();
+ inimb.invokeConstructor(classBuilder.getClassName(), Constants.EMPTY_TYPEDESC_ARRAY);
+ inimb.storeStaticField(classBuilder.getClassName(), "INSTANCE", thisClass);
+ inimb.returnVoid();
+ inimb.finish();
+ }
+ else {
+ for(int i=0,j=0;i<types.length;++i) {
+ if(!types[i].equals(TypeDesc.VOID)) {
+ mb.loadThis();
+ mb.loadLocal(mb.getParameter(j++));
+ mb.storeField(classBuilder.getClassName(), fieldNamePrefix+i, types[i]);
+ }
+ }
+ }
+ mb.returnVoid();
+ mb.finish();
+
+ // Create toString
+ {
+ MethodBuilderBase tsmb = classBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "toString", TypeDesc.STRING, Constants.EMPTY_TYPEDESC_ARRAY);
+
+ if(types.length > 0) {
+ tsmb.newObject(TypeDesc.forClass(StringBuilder.class));
+ tsmb.dup();
+ tsmb.invokeConstructor("java/lang/StringBuilder", Constants.EMPTY_TYPEDESC_ARRAY);
+
+ // build string
+ tsmb.loadConstant("(" + recordName);
+ StringBuilder_appendString(tsmb);
+ for(int i=0;i<types.length;++i) {
+ if(types[i].equals(TypeDesc.VOID)) {
+ tsmb.loadConstant(" ()");
+ StringBuilder_appendString(tsmb);
+ }
+ else {
+ tsmb.loadConstant(" ");
+ StringBuilder_appendString(tsmb);
+ tsmb.loadThis();
+ tsmb.loadField(classBuilder.getClassName(), fieldNamePrefix+i, types[i]);
+ StringBuilder_appendObject(tsmb, types[i]);
+ }
+ }
+ tsmb.loadConstant(")");
+ StringBuilder_appendString(tsmb);
+
+ // return
+ tsmb.invokeVirtual("java/lang/StringBuilder", "toString", TypeDesc.STRING, Constants.EMPTY_TYPEDESC_ARRAY);
+ }
+ else
+ tsmb.loadConstant(recordName);
+ tsmb.returnValue(TypeDesc.STRING);
+ tsmb.finish();
+ }
+
+ if(generateEqualsAndHashCode) {
+ // Create equals
+ {
+ TypeDesc CLASS = TypeDesc.forClass(Class.class);
+
+ MethodBuilderBase tsmb = classBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "equals", TypeDesc.BOOLEAN, Constants.OBJECTS[1]);
+ LocalVariable parameter = tsmb.getParameter(0);
+ Label success = tsmb.createLabel();
+ Label failure = tsmb.createLabel();
+
+ // Check type
+ tsmb.loadThis();
+ tsmb.loadLocal(parameter);
+ tsmb.ifComparisonBranch(success, "==", TypeDesc.OBJECT);
+ tsmb.loadLocal(parameter);
+ tsmb.ifNullBranch(failure, true);
+ tsmb.loadLocal(parameter);
+ tsmb.invokeVirtual("java/lang/Object", "getClass", CLASS, Constants.EMPTY_TYPEDESC_ARRAY);
+ tsmb.loadThis();
+ tsmb.invokeVirtual("java/lang/Object", "getClass", CLASS, Constants.EMPTY_TYPEDESC_ARRAY);
+ tsmb.ifComparisonBranch(failure, "!=", CLASS);
+ tsmb.loadLocal(parameter);
+ tsmb.checkCast(classBuilder.getType());
+ LocalVariable other = tsmb.createLocalVariable("other", classBuilder.getType());
+ tsmb.storeLocal(other);
+
+ // Compare fields
+ for(int i=0;i<types.length;++i) {
+ TypeDesc type = types[i];
+ if(type.equals(TypeDesc.VOID))
+ continue;
+ tsmb.loadThis();
+ tsmb.loadField(classBuilder.getClassName(), fieldNamePrefix+i, type);
+ tsmb.loadLocal(other);
+ tsmb.loadField(classBuilder.getClassName(), fieldNamePrefix+i, type);
+ if(type.isPrimitive())
+ tsmb.ifComparisonBranch(failure, "!=", type);
+ else {
+ Label isNull = tsmb.createLabel();
+ Label finished = tsmb.createLabel();
+ tsmb.swap();
+ tsmb.dup();
+ tsmb.ifNullBranch(isNull, true);
+ tsmb.swap();
+ tsmb.invokeVirtual("java/lang/Object", "equals", TypeDesc.BOOLEAN, Constants.OBJECTS[1]);
+ tsmb.ifZeroComparisonBranch(failure, "==");
+ tsmb.branch(finished);
+ tsmb.setLocation(isNull);
+ tsmb.pop();
+ tsmb.ifNullBranch(failure, false);
+ tsmb.setLocation(finished);
+ }
+ }
+
+ // Return
+ tsmb.setLocation(success);
+ tsmb.loadConstant(true);
+ tsmb.returnValue(TypeDesc.BOOLEAN);
+ tsmb.setLocation(failure);
+ tsmb.loadConstant(false);
+ tsmb.returnValue(TypeDesc.BOOLEAN);
+ tsmb.finish();
+ }
+
+ // Create hashCode
+ {
+ MethodBuilderBase tsmb = classBuilder.addMethodBase(Opcodes.ACC_PUBLIC, "hashCode", TypeDesc.INT, Constants.EMPTY_TYPEDESC_ARRAY);
+ tsmb.loadConstant(recordName.hashCode());
+ for(int i=0;i<types.length;++i) {
+ TypeDesc type = types[i];
+ if(type.equals(TypeDesc.VOID))
+ continue;
+ tsmb.loadConstant(31);
+ tsmb.math(Opcodes.IMUL);
+ tsmb.loadThis();
+ tsmb.loadField(classBuilder.getClassName(), fieldNamePrefix+i, type);
+ switch(type.getTypeCode()) {
+ case TypeDesc.INT_CODE:
+ break;
+ case TypeDesc.OBJECT_CODE: {
+ Label isNull = tsmb.createLabel();
+ Label finished = tsmb.createLabel();
+ tsmb.dup();
+ tsmb.ifNullBranch(isNull, true);
+ tsmb.invokeVirtual("java/lang/Object", "hashCode", TypeDesc.INT, Constants.EMPTY_TYPEDESC_ARRAY);
+ tsmb.branch(finished);
+ tsmb.setLocation(isNull);
+ tsmb.pop();
+ tsmb.loadConstant(0);
+ tsmb.setLocation(finished);
+ } break;
+ case TypeDesc.DOUBLE_CODE:
+ tsmb.invokeStatic("java/lang/Double", "doubleToLongBits", TypeDesc.LONG, new TypeDesc[] { TypeDesc.DOUBLE });
+ case TypeDesc.LONG_CODE:
+ tsmb.dup2();
+ tsmb.loadConstant(32);
+ tsmb.math(Opcodes.LSHR);
+ tsmb.math(Opcodes.LXOR);
+ tsmb.convert(TypeDesc.LONG, TypeDesc.INT);
+ break;
+ case TypeDesc.FLOAT_CODE:
+ tsmb.invokeStatic("java/lang/Float", "floatToIntBits", TypeDesc.INT, new TypeDesc[] { TypeDesc.FLOAT });
+ break;
+ default:
+ tsmb.convert(type, TypeDesc.INT);
+ }
+ tsmb.math(Opcodes.IADD);
+ }
+ tsmb.returnValue(TypeDesc.INT);
+ tsmb.finish();
+ }
+ }
+ }
+
+ public static void constructRecord(TypeDesc clazz, MethodBuilder mb,
+ Type[] parameterTypes, Val... parameters) {
+ if(parameters.length == 0) {
+ mb.loadStaticField(clazz, "INSTANCE", clazz);
+ }
+ else {
+ mb.newObject(clazz);
+ mb.dup();
+ for(int i=0;i<parameters.length;++i)
+ mb.push(parameters[i], parameterTypes[i]);
+ JavaTypeTranslator tt = mb.moduleBuilder.getJavaTypeTranslator();
+ mb.invokeConstructor(clazz, JavaTypeTranslator.filterVoid(
+ tt.toTypeDescs(Arrays.copyOf(parameterTypes, parameters.length))));
+ }
+ }
+
+ public static void StringBuilder_appendString(MethodBuilderBase mb) {
+ mb.invokeVirtual("java/lang/StringBuilder", "append", TypeDesc.forClass("java.lang.StringBuilder"),
+ new TypeDesc[] {TypeDesc.STRING});
+ }
+
+ public static void StringBuilder_appendObject(MethodBuilderBase mb, TypeDesc type) {
+ if(!type.isPrimitive() && type != TypeDesc.STRING)
+ type = TypeDesc.OBJECT;
+ mb.invokeVirtual("java/lang/StringBuilder", "append", TypeDesc.forClass("java.lang.StringBuilder"),
+ new TypeDesc[] {type});
+ }
+
+}