package scotch.compiler.syntax.value;
import static lombok.AccessLevel.PACKAGE;
import static scotch.compiler.syntax.TypeError.typeError;
import static scotch.compiler.syntax.type.Types.toType;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import scotch.compiler.analyzer.DependencyAccumulator;
import scotch.compiler.analyzer.NameAccumulator;
import scotch.compiler.analyzer.OperatorAccumulator;
import scotch.compiler.analyzer.PrecedenceParser;
import scotch.compiler.analyzer.NameQualifier;
import scotch.compiler.analyzer.TypeChecker;
import scotch.compiler.intermediate.IntermediateGenerator;
import scotch.compiler.intermediate.IntermediateValue;
import scotch.compiler.intermediate.Intermediates;
import scotch.compiler.syntax.pattern.PatternReducer;
import scotch.compiler.text.SourceLocation;
import scotch.symbol.Symbol;
import scotch.symbol.descriptor.DataConstructorDescriptor;
import scotch.symbol.descriptor.DataTypeDescriptor;
import scotch.compiler.syntax.type.SumType;
import scotch.compiler.syntax.type.Type;
import scotch.symbol.type.TypeDescriptor;
@AllArgsConstructor(access = PACKAGE)
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = "sourceLocation")
public class Accessor extends Value {
@Getter
private final SourceLocation sourceLocation;
private final Value target;
private final String field;
@Getter
private final Type type;
@Getter
private final Optional<Symbol> tag;
@Override
public Value accumulateDependencies(DependencyAccumulator state) {
return new Accessor(sourceLocation, target.accumulateDependencies(state), field, type, tag);
}
@Override
public Value accumulateNames(NameAccumulator state) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public Value bindMethods(TypeChecker typeChecker) {
return new Accessor(sourceLocation, target.bindMethods(typeChecker), field, type, tag);
}
@Override
public Value bindTypes(TypeChecker typeChecker) {
return new Accessor(sourceLocation, target.bindTypes(typeChecker), field, typeChecker.generate(type), tag);
}
@Override
public Value checkTypes(TypeChecker typeChecker) {
Value checkedTarget = target.checkTypes(typeChecker);
SumType targetType = (SumType) checkedTarget.getType();
Type fieldType = getFieldType(typeChecker, checkedTarget, targetType);
return new Accessor(sourceLocation, checkedTarget, field, fieldType, tag);
}
private Type getFieldType(TypeChecker typeChecker, Value checkedTarget, SumType targetType) {
return target.getTag()
.map(tag -> {
// TODO absent tag makes field access fundamentally unsafe, may require a post-type check step to verify field access
DataTypeDescriptor dataType = typeChecker.getDataType(checkedTarget.getType()).get();
HashMap<TypeDescriptor, TypeDescriptor> mappedParameters = new HashMap<TypeDescriptor, TypeDescriptor>() {{
for (int i = 0; i < targetType.getParameters().size(); i++) {
put(dataType.getParameters().get(i), targetType.getParameters().get(i).toDescriptor());
}
}};
DataConstructorDescriptor mappedConstructor = dataType.getConstructor(tag).get().mapParameters(mappedParameters);
return toType(mappedConstructor.getField(field).get().getType()).unify(type, typeChecker).orElseGet(unification -> {
typeChecker.error(typeError(unification, sourceLocation));
return type;
});
})
.orElse(type);
}
@Override
public Value defineOperators(OperatorAccumulator state) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public boolean equalsBeta(Value o) {
if (equals(o)) {
return true;
} else if (o instanceof Accessor) {
Accessor other = (Accessor) o;
return target.equalsBeta(other.target)
&& Objects.equals(field, other.field);
} else {
return false;
}
}
@Override
public IntermediateValue generateIntermediateCode(IntermediateGenerator state) {
IntermediateValue intermediateTarget = target.generateIntermediateCode(state);
return Intermediates.access(state.capture(), intermediateTarget, field, state.getFieldMethod(target.getTag().get(), field));
}
public DataConstructorDescriptor mapConstructor(TypeChecker typeChecker, Value checkedTarget, SumType targetType) {
DataTypeDescriptor dataType = typeChecker.getDataType(checkedTarget.getType()).get();
HashMap<TypeDescriptor, TypeDescriptor> mappedParameters = new HashMap<TypeDescriptor, TypeDescriptor>() {{
for (int i = 0; i < targetType.getParameters().size(); i++) {
put(dataType.getParameters().get(i), targetType.getParameters().get(i).toDescriptor());
}
}};
return dataType.getConstructor(target.getTag().get()).get().mapParameters(mappedParameters);
}
@Override
public Value mapTags(Function<Value, Value> mapper) {
Accessor accessor = new Accessor(sourceLocation, target.mapTags(mapper), field, type, tag);
return mapper.apply(accessor);
}
@Override
public Value parsePrecedence(PrecedenceParser state) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public Value qualifyNames(NameQualifier state) {
return tag
.map(t -> withTag(state.qualifyType(t)))
.orElse(this);
}
@Override
public Value reducePatterns(PatternReducer reducer) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public Value withTag(Symbol tag) {
return new Accessor(sourceLocation, target, field, type, Optional.of(tag));
}
@Override
public Value withType(Type type) {
return new Accessor(sourceLocation, target, field, type, tag);
}
}