package scotch.compiler.syntax.value;
import static lombok.AccessLevel.PACKAGE;
import static scotch.compiler.error.SymbolNotFoundError.symbolNotFound;
import static scotch.compiler.intermediate.Intermediates.variable;
import static scotch.compiler.syntax.TypeError.typeError;
import static scotch.compiler.syntax.builder.BuilderUtil.require;
import static scotch.compiler.syntax.value.Values.arg;
import static scotch.symbol.Symbol.unqualified;
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.syntax.builder.SyntaxBuilder;
import scotch.compiler.syntax.pattern.PatternReducer;
import scotch.compiler.text.SourceLocation;
import scotch.symbol.Symbol;
import scotch.compiler.syntax.type.Type;
@AllArgsConstructor(access = PACKAGE)
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = "sourceLocation")
@Getter
public class Argument extends Value {
public static Builder builder() {
return new Builder();
}
private final SourceLocation sourceLocation;
private final String name;
private final Type type;
private final Optional<Symbol> tag;
@Override
public Argument accumulateDependencies(DependencyAccumulator state) {
return this;
}
@Override
public Argument accumulateNames(NameAccumulator state) {
state.defineValue(getSymbol(), type);
return this;
}
@Override
public IntermediateValue generateIntermediateCode(IntermediateGenerator state) {
state.reference(name);
return variable(name);
}
@Override
public Value mapTags(Function<Value, Value> mapper) {
return mapper.apply(this);
}
@Override
public Argument bindMethods(TypeChecker typeChecker) {
return this;
}
@Override
public Argument bindTypes(TypeChecker typeChecker) {
return withType(typeChecker.generate(getType()));
}
@Override
public Argument checkTypes(TypeChecker typeChecker) {
typeChecker.capture(getSymbol());
return typeChecker.scope().getValue(getSymbol())
.map(actualType -> withType(actualType.unify(type, typeChecker.scope())
.orElseGet(unification -> {
typeChecker.error(typeError(unification, sourceLocation));
return type;
})))
.orElseGet(() -> {
typeChecker.error(symbolNotFound(getSymbol(), sourceLocation));
return this;
});
}
@Override
public Argument defineOperators(OperatorAccumulator state) {
return this;
}
@Override
public boolean equalsBeta(Value o) {
return equals(o) || Objects.equals(name, ((Argument) o).name);
}
public Symbol getSymbol() {
return unqualified(name);
}
@Override
public Argument parsePrecedence(PrecedenceParser state) {
return this;
}
@Override
public Argument qualifyNames(NameQualifier state) {
return new Argument(sourceLocation, name, type.qualifyNames(state), tag);
}
@Override
public Value reducePatterns(PatternReducer reducer) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public Value withTag(Symbol tag) {
return arg(sourceLocation, name, type, Optional.of(tag));
}
@Override
public Argument withType(Type type) {
return arg(sourceLocation, name, type, tag);
}
public static class Builder implements SyntaxBuilder<Argument> {
private Optional<String> name;
private Optional<Type> type;
private Optional<SourceLocation> sourceLocation;
private Builder() {
name = Optional.empty();
type = Optional.empty();
sourceLocation = Optional.empty();
}
@Override
public Argument build() {
return arg(
require(sourceLocation, "Source location"),
require(name, "Argument name"),
require(type, "Argument type"),
Optional.empty()
);
}
public Builder withName(String name) {
this.name = Optional.of(name);
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;
}
}
}