package scotch.compiler.syntax.value; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; import static scotch.compiler.error.SymbolNotFoundError.symbolNotFound; import static scotch.compiler.syntax.builder.BuilderUtil.require; import static scotch.compiler.syntax.reference.DefinitionReference.valueRef; import static scotch.compiler.syntax.type.Types.toType; import static scotch.compiler.syntax.value.Values.apply; import static scotch.compiler.syntax.value.Values.arg; import static scotch.compiler.syntax.value.Values.id; import static scotch.compiler.syntax.value.Values.method; import static scotch.compiler.syntax.value.Values.unboundMethod; import static scotch.util.Pair.pair; import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.EqualsAndHashCode; 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.analyzer.DelegatingTypeQualifier; 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.syntax.scope.Scope; import scotch.compiler.text.SourceLocation; import scotch.util.Pair; import scotch.symbol.Operator; import scotch.symbol.Symbol; import scotch.symbol.Symbol.QualifiedSymbol; import scotch.symbol.Symbol.SymbolVisitor; import scotch.symbol.Symbol.UnqualifiedSymbol; import scotch.symbol.descriptor.DataFieldDescriptor; import scotch.compiler.syntax.type.Type; @EqualsAndHashCode(callSuper = false) @ToString(exclude = "sourceLocation") public class Identifier extends Value { public static Builder builder() { return new Builder(); } private final SourceLocation sourceLocation; private final Symbol symbol; private final Type type; Identifier(SourceLocation sourceLocation, Symbol symbol, Type type) { this.sourceLocation = sourceLocation; this.symbol = symbol; this.type = type; } @Override public Value accumulateDependencies(DependencyAccumulator state) { return state.addDependency(this); } @Override public Value accumulateNames(NameAccumulator state) { return this; } @Override public Optional<Value> asInitializer(Initializer initializer, TypeChecker state) { if (symbol.isConstructorName()) { return state.getDataConstructor(symbol) .flatMap(constructor -> { List<InitializerField> initializerFields = checkInitializerFields(initializer.getFields(), state); List<DataFieldDescriptor> descriptorFields = checkConstructorFields(constructor.getFields(), state); if (!initializerFields.stream().map(InitializerField::getName).collect(toList()) .containsAll(descriptorFields.stream().map(DataFieldDescriptor::getName).collect(toList())) || !descriptorFields.stream().map(DataFieldDescriptor::getName).collect(toList()) .containsAll(initializerFields.stream().map(InitializerField::getName).collect(toList()))) { state.error(symbolNotFound(symbol, sourceLocation)); // TODO return Optional.empty(); // TODO } List<InitializerField> sortedFields = sort(initializerFields, descriptorFields); Value value = this; for (InitializerField field : sortedFields) { value = apply(value, field.getValue(), state.reserveType()); } return Optional.of(value.checkTypes(state)); }); } else { return super.asInitializer(initializer, state); } } @Override public Optional<Pair<Identifier, Operator>> asOperator(Scope scope) { return scope.qualify(symbol) .flatMap(scope::getOperator) .map(operator -> pair(this, operator)); } public Optional<Value> bind(Scope scope) { return scope.getValue(symbol).map( valueType -> symbol.accept(new SymbolVisitor<Value>() { @Override public Value visit(QualifiedSymbol symbol) { if (scope.isMember(symbol) || valueType.hasContext()) { return unboundMethod(sourceLocation, valueRef(symbol), valueType); } else { return method(sourceLocation, valueRef(symbol), asList(), valueType); } } @Override public Value visit(UnqualifiedSymbol symbol) { return arg(sourceLocation, symbol.getSimpleName(), valueType, Optional.empty()); } })); } @Override public Value bindMethods(TypeChecker typeChecker) { return this; } @Override public Value bindTypes(TypeChecker typeChecker) { return withType(typeChecker.generate(type)); } @Override public Value checkTypes(TypeChecker typeChecker) { return bind(typeChecker.scope()) .map(value -> value.checkTypes(typeChecker)) .orElseGet(() -> { typeChecker.error(symbolNotFound(symbol, sourceLocation)); return this; }); } @Override public Value defineOperators(OperatorAccumulator state) { return this; } @Override public IntermediateValue generateIntermediateCode(IntermediateGenerator state) { throw new UnsupportedOperationException(); // TODO } @Override public SourceLocation getSourceLocation() { return sourceLocation; } public Symbol getSymbol() { return symbol; } public Type getType() { return type; } public boolean isConstructor() { return symbol.isConstructorName(); } @Override public boolean isOperator(Scope scope) { return scope.isOperator(symbol); } @Override public Identifier parsePrecedence(PrecedenceParser state) { if (state.isOperator(symbol)) { return state.qualify(symbol) .map(this::withSymbol) .orElseGet(() -> { state.symbolNotFound(symbol, sourceLocation); return this; }); } else { return this; } } @Override public Identifier qualifyNames(NameQualifier state) { return withSymbol(state.qualifyType(symbol)); } @Override public Value reducePatterns(PatternReducer reducer) { return this; } public Identifier withSourceLocation(SourceLocation sourceLocation) { return new Identifier(sourceLocation, symbol, type); } public Identifier withSymbol(Symbol symbol) { return new Identifier(sourceLocation, symbol, type); } public Identifier withType(Type type) { return new Identifier(sourceLocation, symbol, type); } private List<DataFieldDescriptor> checkConstructorFields(List<DataFieldDescriptor> fields, TypeChecker state) { List<DataFieldDescriptor> checkedFields = new ArrayList<>(); fields.forEach(field -> { checkedFields.add(field.withType(toType(field.getType()).qualifyNames(new DelegatingTypeQualifier(state)).toDescriptor())); // TODO should not have to qualify type names here }); return checkedFields; } private List<InitializerField> checkInitializerFields(List<InitializerField> fields, TypeChecker state) { return fields.stream() .map(field -> field.withType(field.getType().qualifyNames(new DelegatingTypeQualifier(state)))) // TODO should not have to qualify type names here .reduce( new ArrayList<>(), (list, field) -> { list.add(field); return list; }, (left, right) -> { left.addAll(right); return left; } ); } private List<InitializerField> sort(List<InitializerField> initializerFields, List<DataFieldDescriptor> descriptorFields) { List<InitializerField> sortedFields = new ArrayList<>(); List<String> initializerNames = initializerFields.stream().map(InitializerField::getName).collect(toList()); List<String> descriptorNames = descriptorFields.stream().map(DataFieldDescriptor::getName).collect(toList()); for (int i = 0; i < descriptorNames.size(); i++) { int index = initializerNames.indexOf(descriptorNames.get(i)); sortedFields.add(i, initializerFields.get(index)); } return sortedFields; } public static class Builder implements SyntaxBuilder<Identifier> { private Optional<Symbol> symbol; private Optional<Type> type; private Optional<SourceLocation> sourceLocation; private Builder() { symbol = Optional.empty(); type = Optional.empty(); sourceLocation = Optional.empty(); } @Override public Identifier build() { return id( require(sourceLocation, "Source location"), require(symbol, "Identifier symbol"), require(type, "Identifier type") ); } @Override public Builder withSourceLocation(SourceLocation sourceLocation) { this.sourceLocation = Optional.of(sourceLocation); return this; } public Builder withSymbol(Symbol symbol) { this.symbol = Optional.of(symbol); return this; } public Builder withType(Type type) { this.type = Optional.of(type); return this; } } }