package scotch.compiler.syntax.value;
import static lombok.AccessLevel.PACKAGE;
import static scotch.compiler.intermediate.Intermediates.apply;
import static scotch.compiler.syntax.TypeError.typeError;
import static scotch.compiler.syntax.type.Types.fn;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
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.syntax.pattern.PatternReducer;
import scotch.compiler.text.SourceLocation;
import scotch.compiler.syntax.type.FunctionType;
import scotch.compiler.syntax.type.Type;
import scotch.compiler.syntax.type.Unification;
@AllArgsConstructor(access = PACKAGE)
@EqualsAndHashCode(callSuper = false)
@ToString(of = { "type", "function", "argument" })
public class Apply extends Value {
private final SourceLocation sourceLocation;
private final Value function;
private final Value argument;
private final Type type;
@Override
public Value accumulateDependencies(DependencyAccumulator state) {
return withFunction(function.accumulateDependencies(state)).withArgument(argument.accumulateDependencies(state));
}
@Override
public Value accumulateNames(NameAccumulator state) {
return withFunction(function.accumulateNames(state))
.withArgument(argument.accumulateNames(state));
}
@Override
public Value bindMethods(TypeChecker typeChecker) {
return withFunction(function.bindMethods(typeChecker))
.withArgument(argument.bindMethods(typeChecker));
}
@Override
public Value bindTypes(TypeChecker typeChecker) {
return new Apply(sourceLocation, function.bindTypes(typeChecker), argument.bindTypes(typeChecker), typeChecker.generate(type));
}
@Override
public Value checkTypes(TypeChecker typeChecker) {
Value checkedFunction = function.checkTypes(typeChecker);
Value checkedArgument = argument.checkTypes(typeChecker);
Unification unify = fn(checkedArgument.getType(), type)
.unify(checkedFunction.getType(), typeChecker.scope());
return new Apply(sourceLocation, checkedFunction, checkedArgument, unify
.mapType(t -> ((FunctionType) t).getResult())
.orElseGet(unification -> {
typeChecker.error(typeError(unification.flip(), checkedArgument.getSourceLocation()));
return type;
}));
}
@Override
public Value defineOperators(OperatorAccumulator state) {
return this;
}
@Override
public IntermediateValue generateIntermediateCode(IntermediateGenerator state) {
IntermediateValue intermediateFunction = function.generateIntermediateCode(state);
IntermediateValue intermediateArgument = argument.generateIntermediateCode(state);
return apply(state.capture(), intermediateFunction, intermediateArgument);
}
public Value getArgument() {
return argument;
}
public Value getFunction() {
return function;
}
@Override
public SourceLocation getSourceLocation() {
return sourceLocation;
}
@Override
public Type getType() {
return type;
}
@Override
public Value parsePrecedence(PrecedenceParser state) {
return withFunction(function.parsePrecedence(state))
.withArgument(argument.parsePrecedence(state));
}
@Override
public Value qualifyNames(NameQualifier state) {
return withFunction(function.qualifyNames(state)).withArgument(argument.qualifyNames(state));
}
@Override
public Value reducePatterns(PatternReducer reducer) {
return new Apply(sourceLocation, function.reducePatterns(reducer), argument.reducePatterns(reducer), type);
}
public Apply withArgument(Value argument) {
return new Apply(sourceLocation, function, argument, type);
}
public Apply withFunction(Value function) {
return new Apply(sourceLocation, function, argument, type);
}
public Apply withSourceLocation(SourceLocation sourceLocation) {
return new Apply(sourceLocation, function, argument, type);
}
@Override
public Apply withType(Type type) {
return new Apply(sourceLocation, function, argument, type);
}
}