package scotch.compiler.syntax.value;
import static java.util.stream.Collectors.toList;
import static lombok.AccessLevel.PACKAGE;
import static scotch.compiler.error.SymbolNotFoundError.symbolNotFound;
import static scotch.compiler.syntax.value.NoBindingError.noBinding;
import static scotch.compiler.syntax.value.Values.method;
import static scotch.compiler.syntax.value.Values.unboundMethod;
import static scotch.compiler.syntax.type.Types.fn;
import static scotch.compiler.syntax.type.Types.instance;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import scotch.compiler.analyzer.DependencyAccumulator;
import scotch.compiler.analyzer.NameAccumulator;
import scotch.compiler.analyzer.NameQualifier;
import scotch.compiler.analyzer.OperatorAccumulator;
import scotch.compiler.analyzer.PrecedenceParser;
import scotch.compiler.analyzer.TypeChecker;
import scotch.compiler.intermediate.IntermediateGenerator;
import scotch.compiler.intermediate.IntermediateValue;
import scotch.compiler.syntax.pattern.PatternReducer;
import scotch.compiler.syntax.reference.ValueReference;
import scotch.compiler.text.SourceLocation;
import scotch.symbol.Symbol;
import scotch.compiler.syntax.type.InstanceType;
import scotch.compiler.syntax.type.Type;
@AllArgsConstructor(access = PACKAGE)
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = "sourceLocation")
public class UnboundMethod extends Value {
private final SourceLocation sourceLocation;
private final ValueReference valueRef;
private final Type type;
@Override
public Value accumulateDependencies(DependencyAccumulator state) {
throw new UnsupportedOperationException();
}
@Override
public Value accumulateNames(NameAccumulator state) {
throw new UnsupportedOperationException();
}
@Override
public IntermediateValue generateIntermediateCode(IntermediateGenerator state) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public Value bindMethods(TypeChecker typeChecker) {
throw new UnsupportedOperationException();
}
@Override
public Value bindTypes(TypeChecker typeChecker) {
return bind(typeChecker).bindTypes(typeChecker);
}
@Override
public Value checkTypes(TypeChecker typeChecker) {
return this;
}
@Override
public Value defineOperators(OperatorAccumulator state) {
throw new UnsupportedOperationException();
}
@Override
public SourceLocation getSourceLocation() {
return sourceLocation;
}
public Symbol getSymbol() {
return valueRef.getSymbol();
}
@Override
public Type getType() {
return type;
}
@Override
public Value parsePrecedence(PrecedenceParser state) {
throw new UnsupportedOperationException();
}
@Override
public Value qualifyNames(NameQualifier state) {
throw new UnsupportedOperationException();
}
@Override
public Value reducePatterns(PatternReducer reducer) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public Value withType(Type type) {
return unboundMethod(sourceLocation, valueRef, type);
}
private Value bind(TypeChecker state) {
return state.getRawValue(valueRef)
.map(valueType -> {
List<InstanceType> instances = listInstanceTypes(valueType);
return state.getRawValue(valueRef)
.map(rawValue -> rawValue.zip(type, state)
.map(map -> instances.stream()
.map(instance -> instance.withBinding(map.get(instance.getBinding())))
.collect(toList()))
.map(instanceTypes -> method(sourceLocation, valueRef, instanceTypes, state.generate(getMethodType(instanceTypes))))
.orElseGet(() -> {
state.error(noBinding(getSymbol(), sourceLocation));
return this;
}))
.orElseGet(() -> notFound(state));
})
.orElseGet(() -> notFound(state));
}
private UnboundMethod notFound(TypeChecker state) {
state.error(symbolNotFound(valueRef.getSymbol(), sourceLocation));
return this;
}
private Type getMethodType(List<InstanceType> instances) {
List<Type> reversedInstances = new ArrayList<>(instances);
Collections.reverse(reversedInstances);
return reversedInstances.stream().reduce(type, (left, right) -> fn(right, left));
}
private List<InstanceType> listInstanceTypes(Type valueType) {
return valueType.getContexts().stream()
.map(pair -> pair.into((type, symbol) -> instance(symbol, type.simplify())))
.collect(toList());
}
}