package scotch.compiler.syntax.definition; import static java.util.Collections.sort; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static me.qmx.jitescript.util.CodegenUtils.ci; import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static scotch.compiler.intermediate.Intermediates.constructor; import static scotch.compiler.syntax.builder.BuilderUtil.require; import static scotch.symbol.FieldSignature.fieldSignature; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import lombok.EqualsAndHashCode; import scotch.compiler.analyzer.NameAccumulator; import scotch.compiler.analyzer.NameQualifier; import scotch.compiler.intermediate.IntermediateConstructorDefinition; import scotch.compiler.intermediate.IntermediateGenerator; import scotch.compiler.syntax.builder.SyntaxBuilder; import scotch.compiler.text.SourceLocation; import scotch.runtime.Callable; import scotch.symbol.FieldSignature; import scotch.symbol.Symbol; import scotch.symbol.descriptor.DataConstructorDescriptor; @EqualsAndHashCode(callSuper = false, doNotUseGetters = true) public class DataConstructorDefinition implements Comparable<DataConstructorDefinition> { public static Builder builder() { return new Builder(); } private final SourceLocation sourceLocation; private final int ordinal; private final Symbol dataType; private final Symbol symbol; private final Map<String, DataFieldDefinition> fields; private final Optional<FieldSignature> constantField; private DataConstructorDefinition(SourceLocation sourceLocation, int ordinal, Symbol dataType, Symbol symbol, List<DataFieldDefinition> fields) { List<DataFieldDefinition> sortedFields = new ArrayList<>(fields); sort(sortedFields); this.sourceLocation = sourceLocation; this.ordinal = ordinal; this.dataType = dataType; this.symbol = symbol; this.fields = new LinkedHashMap<>(); sortedFields.forEach(field -> this.fields.put(field.getName(), field)); if (fields.isEmpty()) { String className = symbol.getClassNameAsChildOf(dataType); constantField = Optional.of(fieldSignature(className, ACC_STATIC | ACC_PUBLIC | ACC_FINAL, "INSTANCE", ci(Callable.class))); } else { constantField = Optional.empty(); } } public void accumulateNames(NameAccumulator state) { state.defineDataConstructor(symbol, getDescriptor()); } @Override public int compareTo(DataConstructorDefinition o) { return ordinal - o.ordinal; } public IntermediateConstructorDefinition generateIntermediateCode(IntermediateGenerator state) { return constructor(symbol, dataType, fields.values().stream() .map(field -> field.generateIntermediateCode(state)) .collect(toList())); } public FieldSignature getConstantField() { return constantField.orElseThrow(() -> new IllegalStateException("Data constructor " + symbol + " is not niladic")); } public Symbol getDataType() { return dataType; } public DataConstructorDescriptor getDescriptor() { return DataConstructorDescriptor.builder(ordinal, dataType, symbol, symbol.getClassNameAsChildOf(dataType)) .withFields(fields.values().stream() .map(DataFieldDefinition::getDescriptor) .collect(toList())) .build(); } public List<DataFieldDefinition> getFields() { return new ArrayList<>(fields.values()); } public SourceLocation getSourceLocation() { return sourceLocation; } public Symbol getSymbol() { return symbol; } public boolean isNiladic() { return fields.isEmpty(); } public DataConstructorDefinition qualifyNames(NameQualifier state) { DataConstructorDefinition definition = withFields(fields.values().stream() .map(field -> field.qualifyNames(state)) .collect(toList())); state.redefineDataConstructor(symbol, getDescriptor()); return definition; } @Override public String toString() { return symbol.getSimpleName() + (fields.isEmpty() ? "" : " { " + fields.values().stream().map(Object::toString).collect(joining(", ")) + " }"); } private Class<?>[] getParameters() { List<Class<?>> parameters = fields.values().stream() .map(DataFieldDefinition::getJavaType) .collect(toList()); return parameters.toArray(new Class<?>[parameters.size()]); } private DataConstructorDefinition withFields(List<DataFieldDefinition> fields) { return new DataConstructorDefinition(sourceLocation, ordinal, dataType, symbol, fields); } public static class Builder implements SyntaxBuilder<DataConstructorDefinition> { private Optional<SourceLocation> sourceLocation = Optional.empty(); private Optional<Integer> ordinal = Optional.empty(); private Optional<Symbol> dataType = Optional.empty(); private Optional<Symbol> symbol = Optional.empty(); private List<DataFieldDefinition> fields = new ArrayList<>(); public Builder addField(DataFieldDefinition field) { fields.add(field); return this; } @Override public DataConstructorDefinition build() { return new DataConstructorDefinition( require(sourceLocation, "Source location"), require(ordinal, "Ordinal"), require(dataType, "Constructor data type"), require(symbol, "Constructor symbol"), fields ); } public Builder withDataType(Symbol dataType) { this.dataType = Optional.of(dataType); return this; } public Builder withFields(List<DataFieldDefinition> fields) { fields.forEach(this::addField); return this; } public Builder withOrdinal(int ordinal) { this.ordinal = Optional.of(ordinal); return this; } @Override public Builder withSourceLocation(SourceLocation sourceLocation) { this.sourceLocation = Optional.of(sourceLocation); return this; } public Builder withSymbol(Symbol symbol) { this.symbol = Optional.of(symbol); return this; } } }