package scotch.compiler.syntax.definition;
import static scotch.compiler.syntax.builder.BuilderUtil.require;
import static scotch.compiler.syntax.definition.Definitions.module;
import static scotch.compiler.syntax.reference.DefinitionReference.moduleRef;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
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.PatternAnalyzer;
import scotch.compiler.analyzer.PrecedenceParser;
import scotch.compiler.analyzer.TypeChecker;
import scotch.compiler.intermediate.IntermediateGenerator;
import scotch.compiler.syntax.builder.SyntaxBuilder;
import scotch.compiler.syntax.reference.DefinitionReference;
import scotch.compiler.text.SourceLocation;
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = "sourceLocation")
public class ModuleDefinition extends Definition {
public static Builder builder() {
return new Builder();
}
private final SourceLocation sourceLocation;
private final String symbol;
private final List<DefinitionReference> importScopes;
ModuleDefinition(SourceLocation sourceLocation, String symbol, List<DefinitionReference> importScopes) {
this.sourceLocation = sourceLocation;
this.symbol = symbol;
this.importScopes = ImmutableList.copyOf(importScopes);
}
@Override
public Definition accumulateDependencies(DependencyAccumulator state) {
return state.scoped(this, () -> withImportScopes(state.accumulateDependencies(importScopes)));
}
@Override
public Definition accumulateNames(NameAccumulator state) {
return state.scoped(this, () -> withImportScopes(state.accumulateNames(importScopes)));
}
@Override
public Definition checkTypes(TypeChecker state) {
return state.keep(this);
}
@Override
public Definition defineOperators(OperatorAccumulator state) {
return state.scoped(this, () -> withImportScopes(state.defineDefinitionOperators(importScopes)));
}
@Override
public Optional<DefinitionReference> generateIntermediateCode(IntermediateGenerator generator) {
return generator.scoped(this,
() -> generator.defineModule(symbol,
() -> importScopes.forEach(generator::generateIntermediateCode)));
}
@Override
public DefinitionReference getReference() {
return moduleRef(symbol);
}
@Override
public SourceLocation getSourceLocation() {
return sourceLocation;
}
@Override
public Optional<Definition> parsePrecedence(PrecedenceParser state) {
return Optional.of(state.scoped(this, () -> withImportScopes(new ArrayList<DefinitionReference>() {{
addAll(state.mapOptional(importScopes, Definition::parsePrecedence));
addAll(state.processPatterns());
}})));
}
@Override
public Definition qualifyNames(NameQualifier state) {
return state.scoped(this, () -> withImportScopes(state.qualifyDefinitionNames(importScopes)));
}
@Override
public Definition reducePatterns(PatternAnalyzer state) {
return state.scoped(this, () -> withImportScopes(state.reducePatterns(importScopes)));
}
public ModuleDefinition withImportScopes(List<DefinitionReference> importScopes) {
return new ModuleDefinition(sourceLocation, symbol, importScopes);
}
public static class Builder implements SyntaxBuilder<ModuleDefinition> {
private final List<DefinitionReference> importScopes = new ArrayList<>();
private Optional<String> symbol = Optional.empty();
private Optional<SourceLocation> sourceLocation = Optional.empty();
private Builder() {
// intentionally empty
}
@Override
public ModuleDefinition build() {
return module(
require(sourceLocation, "Source location"),
require(symbol, "Module symbol"),
importScopes
);
}
public Builder withImportScope(DefinitionReference importScope) {
importScopes.add(importScope);
return this;
}
@Override
public Builder withSourceLocation(SourceLocation sourceLocation) {
this.sourceLocation = Optional.of(sourceLocation);
return this;
}
public Builder withSymbol(String symbol) {
this.symbol = Optional.of(symbol);
return this;
}
}
}