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.Operator;
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.pattern.PatternCase;
import scotch.compiler.syntax.value.Value;
public class OperatorAccumulator {
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;
public OperatorAccumulator(DefinitionGraph graph) {
this.graph = graph;
this.scopes = new ArrayDeque<>();
this.functionScopes = new HashMap<>();
this.entries = new HashMap<>();
this.errors = new ArrayList<>();
}
public Definition collect(Definition definition) {
entries.put(definition.getReference(), entry(scope(), definition));
return definition;
}
public Definition collect(PatternCase pattern) {
return collect(scopeDef(pattern));
}
public DefinitionGraph accumulateOperators() {
Definition root = getDefinition(rootRef()).orElseThrow(() -> new IllegalStateException("No root found!"));
scoped(root, () -> root.defineOperators(this));
return graph
.copyWith(entries.values())
.appendErrors(errors)
.build();
}
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 scoped) {
return (T) scoped(scoped, () -> scoped);
}
public void leaveScope() {
scopes.pop();
}
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 defineOperator(Symbol symbol, Operator operator) {
scope().defineOperator(symbol, operator);
}
public void defineValue(Symbol symbol, Type type) {
scope().defineValue(symbol, type);
}
public boolean isOperator(Symbol symbol) {
return scope().isOperator(symbol);
}
public List<DefinitionReference> defineDefinitionOperators(List<DefinitionReference> references) {
return references.stream()
.map(this::getDefinition)
.filter(Optional::isPresent)
.map(Optional::get)
.map(definition -> definition.defineOperators(this))
.map(Definition::getReference)
.collect(toList());
}
public List<Value> defineValueOperators(List<Value> values) {
return values.stream()
.map(value -> value.defineOperators(this))
.collect(toList());
}
}