package scotch.compiler.syntax.pattern;
import static java.util.stream.Collectors.toList;
import static scotch.compiler.syntax.builder.BuilderUtil.require;
import static scotch.compiler.syntax.value.Values.isConstructor;
import static scotch.compiler.text.TextUtil.repeat;
import static scotch.compiler.syntax.type.Types.sum;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import scotch.compiler.analyzer.DependencyAccumulator;
import scotch.compiler.analyzer.NameAccumulator;
import scotch.compiler.analyzer.NameQualifier;
import scotch.compiler.analyzer.TypeChecker;
import scotch.compiler.syntax.builder.SyntaxBuilder;
import scotch.compiler.syntax.scope.Scope;
import scotch.compiler.syntax.value.Value;
import scotch.compiler.text.SourceLocation;
import scotch.symbol.Symbol;
import scotch.compiler.syntax.type.Type;
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = "sourceLocation")
public class StructMatch extends PatternMatch {
public static Builder builder() {
return new Builder();
}
@Getter
private final SourceLocation sourceLocation;
private final Optional<Value> argument;
private final Symbol constructor;
@Getter
private final Type type;
private final List<StructField> fields;
StructMatch(SourceLocation sourceLocation, Optional<Value> argument, Symbol constructor, Type type, List<StructField> fields) {
this.sourceLocation = sourceLocation;
this.argument = argument;
this.constructor = constructor;
this.type = type;
this.fields = ImmutableList.copyOf(fields);
}
@Override
public PatternMatch accumulateDependencies(DependencyAccumulator state) {
return this;
}
@Override
public PatternMatch accumulateNames(NameAccumulator state) {
return map(field -> field.accumulateNames(state));
}
@Override
public PatternMatch bind(Value argument, Scope scope) {
return withArgument(argument).map(field -> field.bind(argument, scope));
}
@Override
public PatternMatch bindMethods(TypeChecker state) {
return map(field -> field.bindMethods(state));
}
@Override
public PatternMatch bindTypes(TypeChecker state) {
return map(field -> field.bindTypes(state))
.withType(state.generate(type))
.withArgument(argument
.orElseThrow(IllegalStateException::new)
.bindTypes(state));
}
@Override
public PatternMatch checkTypes(TypeChecker state) {
return map(field -> field.checkTypes(state)).bindType(state);
}
@Override
public PatternMatch qualifyNames(NameQualifier state) {
return new StructMatch(
sourceLocation,
argument.map(arg -> arg.qualifyNames(state)),
state.qualifyType(constructor),
type,
fields.stream().map(field -> field.qualifyNames(state)).collect(toList())
);
}
@Override
public void reducePatterns(PatternReducer reducer) {
Value taggedArgument = argument.orElseThrow(IllegalStateException::new).withTag(constructor);
reducer.addTaggedArgument(taggedArgument);
reducer.addCondition(isConstructor(sourceLocation, reducer.getTaggedArgument(taggedArgument), constructor));
fields.forEach(field -> field.reducePatterns(reducer));
}
@Override
public StructMatch withType(Type type) {
return new StructMatch(sourceLocation, argument, constructor, type, fields);
}
private StructMatch bindType(TypeChecker state) {
Type type = sum(
"scotch.data.tuple.(" + repeat(",", fields.size() - 1) + ")",
fields.stream()
.map(StructField::getType)
.collect(toList()));
argument.map(arg -> type.unify(arg.getType(), state));
return withType(type);
}
private StructMatch map(Function<StructField, StructField> mapper) {
return new StructMatch(
sourceLocation, argument, constructor, type,
fields.stream().map(mapper::apply).collect(toList())
);
}
private StructMatch withArgument(Value argument) {
return new StructMatch(sourceLocation, Optional.of(argument), constructor, type, fields);
}
public static class Builder implements SyntaxBuilder<StructMatch> {
private Optional<SourceLocation> sourceLocation = Optional.empty();
private List<StructField> fields = new ArrayList<>();
private Optional<Symbol> constructor = Optional.empty();
private Optional<Type> type = Optional.empty();
@Override
public StructMatch build() {
return new StructMatch(
require(sourceLocation, "Source location"),
Optional.empty(),
require(constructor, "Constructor"),
require(type, "Type"),
fields
);
}
public Builder withConstructor(Symbol constructor) {
this.constructor = Optional.of(constructor);
return this;
}
public Builder withField(StructField field) {
fields.add(field);
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;
}
}
}