package scotch.compiler.analyzer;
import static java.util.stream.Collectors.toList;
import static scotch.compiler.syntax.definition.DefinitionEntry.entry;
import static scotch.compiler.syntax.reference.DefinitionReference.rootRef;
import static scotch.compiler.syntax.definition.Definitions.scopeDef;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import scotch.compiler.error.SyntaxError;
import scotch.symbol.Symbol;
import scotch.compiler.syntax.type.Type;
import scotch.compiler.syntax.Scoped;
import scotch.compiler.syntax.definition.Definition;
import scotch.compiler.syntax.definition.DefinitionEntry;
import scotch.compiler.syntax.definition.DefinitionGraph;
import scotch.compiler.syntax.reference.DefinitionReference;
import scotch.compiler.syntax.scope.Scope;
import scotch.compiler.syntax.value.Identifier;
import scotch.compiler.syntax.pattern.PatternCase;
public class DependencyAccumulator {
private final DefinitionGraph graph;
private final Deque<Scope> scopes;
private final Map<DefinitionReference, Scope> functionScopes;
private final Map<DefinitionReference, DefinitionEntry> entries;
private final List<SyntaxError> errors;
private final Deque<Symbol> symbols;
public DependencyAccumulator(DefinitionGraph graph) {
this.graph = graph;
this.scopes = new ArrayDeque<>();
this.functionScopes = new HashMap<>();
this.entries = new HashMap<>();
this.errors = new ArrayList<>();
this.symbols = new ArrayDeque<>();
}
public DefinitionGraph accumulateDependencies() {
Definition root = getDefinition(rootRef()).orElseThrow(() -> new IllegalStateException("No root found!"));
scoped(root, () -> root.accumulateDependencies(this));
return graph
.copyWith(entries.values())
.appendErrors(errors)
.build()
.sort();
}
public List<DefinitionReference> accumulateDependencies(List<DefinitionReference> references) {
return references.stream()
.map(this::getDefinition)
.filter(Optional::isPresent)
.map(Optional::get)
.map(definition -> definition.accumulateDependencies(this))
.map(Definition::getReference)
.collect(toList());
}
public Identifier addDependency(Identifier identifier) {
return identifier.withSymbol(addDependency(identifier.getSymbol()));
}
public Symbol addDependency(Symbol symbol) {
return symbol.map(qualifiedSymbol -> {
if (!symbols.contains(qualifiedSymbol)) {
scope().addDependency(qualifiedSymbol);
}
return qualifiedSymbol;
});
}
public Definition collect(Definition definition) {
entries.put(definition.getReference(), entry(scope(), definition));
return definition;
}
public Definition collect(PatternCase pattern) {
return collect(scopeDef(pattern));
}
public void enterScope(Definition definition) {
enterScope(definition.getReference());
}
public void error(SyntaxError error) {
errors.add(error);
}
public Optional<Definition> getDefinition(DefinitionReference reference) {
return graph.getDefinition(reference);
}
@SuppressWarnings("unchecked")
public <T extends Scoped> T keep(Scoped scopedThing) {
return (T) scoped(scopedThing, () -> scopedThing);
}
public void leaveScope() {
scopes.pop();
}
public void popSymbol() {
symbols.pop();
}
public void pushSymbol(Symbol symbol) {
symbols.push(symbol);
}
public Scope scope() {
return scopes.peek();
}
public <T extends Definition> T scoped(T definition, Supplier<? extends T> supplier) {
enterScope(definition);
try {
T result = supplier.get();
collect(result);
return result;
} finally {
leaveScope();
}
}
public <T extends Scoped> T scoped(Scoped value, Supplier<? extends T> supplier) {
enterScope(value.getReference());
try {
T result = supplier.get();
collect(result.getDefinition());
return result;
} finally {
leaveScope();
}
}
private void enterScope(DefinitionReference reference) {
scopes.push(getScope(reference));
}
private Scope getScope(DefinitionReference reference) {
return graph.tryGetScope(reference).orElseGet(() -> functionScopes.get(reference));
}
public void defineValue(Symbol symbol, Type type) {
scope().defineValue(symbol, type);
}
public boolean isOperator(Symbol symbol) {
return scope().isOperator(symbol);
}
}