package scotch.compiler.syntax.value;
import static scotch.compiler.intermediate.Intermediates.conditional;
import static scotch.compiler.syntax.TypeError.typeError;
import static scotch.compiler.syntax.builder.BuilderUtil.require;
import static scotch.compiler.syntax.type.Types.toType;
import java.util.Optional;
import java.util.function.BiFunction;
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.syntax.builder.SyntaxBuilder;
import scotch.compiler.syntax.pattern.PatternReducer;
import scotch.compiler.text.SourceLocation;
import scotch.data.bool.Bool;
import scotch.compiler.syntax.type.Type;
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = "sourceLocation")
public class Conditional extends Value {
public static Builder builder() {
return new Builder();
}
@Getter
private final SourceLocation sourceLocation;
@Getter
private final Type type;
private final Value condition;
private final Value whenTrue;
private final Value whenFalse;
Conditional(SourceLocation sourceLocation, Value condition, Value whenTrue, Value whenFalse, Type type) {
this.sourceLocation = sourceLocation;
this.condition = condition;
this.whenTrue = whenTrue;
this.whenFalse = whenFalse;
this.type = type;
}
@Override
public Value accumulateDependencies(DependencyAccumulator state) {
return parse(state, Value::accumulateDependencies);
}
@Override
public Value accumulateNames(NameAccumulator state) {
return parse(state, Value::accumulateNames);
}
@Override
public Value bindMethods(TypeChecker typeChecker) {
return parse(typeChecker, Value::bindMethods);
}
@Override
public Value bindTypes(TypeChecker typeChecker) {
return parse(typeChecker, Value::bindTypes);
}
@Override
public Value checkTypes(TypeChecker typeChecker) {
Value checkedCondition = condition.checkTypes(typeChecker);
Value checkedWhenTrue = whenTrue.checkTypes(typeChecker);
Value checkedWhenFalse = whenFalse.checkTypes(typeChecker);
Type resultType = toType(Bool.TYPE).unify(checkedCondition.getType(), typeChecker)
.map(ct -> checkedWhenTrue.getType().unify(checkedWhenFalse.getType(), typeChecker))
.orElseGet(unification -> {
typeChecker.error(typeError(unification, checkedWhenFalse.getSourceLocation()));
return type;
});
return new Conditional(sourceLocation, checkedCondition, checkedWhenTrue, checkedWhenFalse, resultType);
}
@Override
public Value defineOperators(OperatorAccumulator state) {
return parse(state, Value::defineOperators);
}
@Override
public IntermediateValue generateIntermediateCode(IntermediateGenerator state) {
return conditional(
condition.generateIntermediateCode(state),
whenTrue.generateIntermediateCode(state),
whenFalse.generateIntermediateCode(state)
);
}
@Override
public Value parsePrecedence(PrecedenceParser state) {
return parse(state, Value::parsePrecedence);
}
@Override
public Value qualifyNames(NameQualifier state) {
return parse(state, Value::qualifyNames)
.withType(type.qualifyNames(state));
}
@Override
public Value reducePatterns(PatternReducer reducer) {
return new Conditional(
sourceLocation,
condition.reducePatterns(reducer),
whenTrue.reducePatterns(reducer),
whenFalse.reducePatterns(reducer),
type
);
}
@Override
public Conditional withType(Type type) {
return new Conditional(sourceLocation, condition, whenTrue, whenFalse, type);
}
private <T> Value parse(T state, BiFunction<Value, T, Value> function) {
return builder()
.withSourceLocation(sourceLocation)
.withCondition(function.apply(condition, state))
.withWhenTrue(function.apply(whenTrue, state))
.withWhenFalse(function.apply(whenFalse, state))
.withType(type)
.build();
}
public static class Builder implements SyntaxBuilder<Conditional> {
private Optional<Value> condition;
private Optional<Value> whenTrue;
private Optional<Value> whenFalse;
private Optional<Type> type;
private Optional<SourceLocation> sourceLocation;
private Builder() {
condition = Optional.empty();
whenTrue = Optional.empty();
whenFalse = Optional.empty();
type = Optional.empty();
sourceLocation = Optional.empty();
}
@Override
public Conditional build() {
return new Conditional(
require(sourceLocation, "Source location"),
require(condition, "Condition"),
require(whenTrue, "True case"),
require(whenFalse, "False case"),
require(type, "Type")
);
}
public Builder withCondition(Value condition) {
this.condition = Optional.of(condition);
return this;
}
@Override
public Builder withSourceLocation(SourceLocation sourceLocation) {
this.sourceLocation = Optional.of(sourceLocation);
return this;
}
public Builder withType(Type type) {
this.type = Optional.of(type);
return this;
}
public Builder withWhenFalse(Value whenFalse) {
this.whenFalse = Optional.of(whenFalse);
return this;
}
public Builder withWhenTrue(Value whenTrue) {
this.whenTrue = Optional.of(whenTrue);
return this;
}
}
}