package me.tomassetti.turin.parser.ast.expressions; import com.google.common.collect.ImmutableList; import me.tomassetti.turin.compiler.ParamUtils; import me.tomassetti.turin.compiler.errorhandling.ErrorCollector; import me.tomassetti.turin.parser.analysis.exceptions.UnsolvedInvokableException; import me.tomassetti.turin.resolvers.SymbolResolver; import me.tomassetti.turin.parser.ast.expressions.literals.StringLiteral; import me.tomassetti.turin.symbols.FormalParameter; import me.tomassetti.turin.typesystem.TypeUsage; import me.tomassetti.turin.util.Either; import java.util.*; import java.util.stream.Collectors; public abstract class InvokableExpr extends Expression { protected List<ActualParam> actualParams; public List<ActualParam> getActualParams() { return actualParams; } public InvokableExpr(List<ActualParam> actualParams) { this.actualParams = new ArrayList<>(); this.actualParams.addAll(actualParams); this.actualParams.forEach((p) ->p.setParent(this)); originalParams = actualParams; } public void desugarize(SymbolResolver resolver) { if (desugarized) { return; } concreteDesugarize(resolver); desugarized = true; } private boolean desugarized = false; protected List<ActualParam> originalParams; public abstract boolean isOnOverloaded(SymbolResolver resolver); @Override protected boolean specificValidate(SymbolResolver resolver, ErrorCollector errorCollector) { boolean otherParams = actualParams.stream().filter((p)->!p.isAsterisk()).findFirst().isPresent(); List<ActualParam> asterisks = actualParams.stream().filter((p)->p.isAsterisk()).collect(Collectors.toList()); if (asterisks.size() > 1) { for (ActualParam actualParam : asterisks.subList(1, asterisks.size())) { errorCollector.recordSemanticError(actualParam.getPosition(), "Only one asterisk parameter can be used"); } return false; } if (asterisks.size() > 0 && otherParams) { errorCollector.recordSemanticError(asterisks.get(0).getPosition(), "Asterisk parameter can be used only alone"); return false; } if (asterisks.size() > 0 && isOnOverloaded(resolver)) { errorCollector.recordSemanticError(asterisks.get(0).getPosition(), "Asterisk parameter cannot be used on overloaded methods"); return false; } return super.specificValidate(resolver, errorCollector); } /** * Return a list of param values in order (named param permits to be out of order) */ public List<Expression> getActualParamValuesInOrder() { List<Expression> values = new LinkedList<>(); for (ActualParam actualParam : actualParams) { if (actualParam.getName() != null) { throw new UnsupportedOperationException(); } values.add(actualParam.getValue()); } return values; } protected abstract List<? extends FormalParameter> formalParameters(SymbolResolver resolver); protected final List<? extends FormalParameter> defaultParameters(SymbolResolver resolver) { return formalParameters(resolver).stream().filter((p)->p.hasDefaultValue()).collect(Collectors.<FormalParameter>toList()); } protected final boolean hasDefaultParameters(SymbolResolver resolver) { return !defaultParameters(resolver).isEmpty(); } protected final boolean hasAsteriskActualParameter() { return originalParams.stream().filter((p)->p.isAsterisk()).findFirst().isPresent(); } private void concreteDesugarize(SymbolResolver resolver) { // all named parameters should be after the named ones if (!ParamUtils.verifyOrder(actualParams)) { throw new IllegalArgumentException("Named params should all be grouped after the positional ones:" + actualParams); } if (hasAsteriskActualParameter()){ concreteDesugarizeWithAsterisk(resolver); } else { concreteDesugarizeWithoutAsterisk(resolver); } } private void concreteDesugarizeWithAsterisk(SymbolResolver resolver) { if (actualParams.size() != 1) { throw new IllegalStateException(); } ActualParam asteriskParam = actualParams.get(0); Map<String, ActualParam> paramsAssigned = new HashMap<>(); List<? extends FormalParameter> formalParams = formalParameters(resolver); Either<String, List<ActualParam>> res = ParamUtils.desugarizeAsteriskParam(formalParams, asteriskParam.getValue(), resolver, this); if (res.isLeft()) { throw new IllegalArgumentException(res.getLeft()); } actualParams = res.getRight(); } private void concreteDesugarizeWithoutAsterisk(SymbolResolver resolver) { Map<String, ActualParam> paramsAssigned = new HashMap<>(); List<? extends FormalParameter> formalParams = formalParameters(resolver); formalParams.forEach((fp)-> { if (fp.isNode()) { fp.asNode().setParent(this); } }); List<ActualParam> unnamedParams = ParamUtils.unnamedParams(actualParams); List<ActualParam> namedParams = ParamUtils.namedParams(actualParams); // use the unnamed params if (unnamedParams.size() > formalParams.size()) { throw new IllegalArgumentException("Too many unnamed params: " + actualParams +". Formal params are: " + formalParams); } int i = 0; for (ActualParam param : unnamedParams) { if (formalParams.get(i).isNode() && formalParams.get(i).asNode().getParent() == null) { throw new IllegalStateException(); } TypeUsage actualParamType = param.getValue().calcType(); TypeUsage formalParamType = formalParams.get(i).getType(); if (!actualParamType.canBeAssignedTo(formalParamType)){ throw new UnsolvedInvokableException(this); } paramsAssigned.put(formalParams.get(i).getName(), param); i++; } // use the named params Map<String, FormalParameter> validNames = new HashMap<>(); formalParams.forEach((p) -> validNames.put(p.getName(), p)); for (ActualParam param : namedParams) { if (paramsAssigned.containsKey(param.getName())) { throw new IllegalArgumentException("Param " + param.getName() + " assigned several times"); } if (!validNames.containsKey(param.getName())) { throw new IllegalArgumentException("Unknown param " + param.getName()); } if (!param.getValue().calcType().canBeAssignedTo(validNames.get(param.getName()).getType())){ throw new UnsolvedInvokableException(this); } paramsAssigned.put(param.getName(), param); } // all parameters have been assigned for (FormalParameter formalParameter : formalParams) { if (!paramsAssigned.containsKey(formalParameter.getName()) && !formalParameter.hasDefaultValue()) { throw new IllegalArgumentException("Param not assigned: " + formalParameter.getName()); } } List<ActualParam> orderedParams = new ArrayList<>(); for (FormalParameter formalParameter : formalParams) { if (!formalParameter.hasDefaultValue()) { ActualParam actualParam = paramsAssigned.get(formalParameter.getName()); if (actualParam.isNamed()) { actualParam = actualParam.toUnnamed(); actualParam.setParent(this); } orderedParams.add(actualParam); } } // add the map with the default params if (hasDefaultParameters(resolver)) { Expression mapCreation = new Creation("turin.collections.MapBuilder", Collections.emptyList()); for (FormalParameter formalParameter : defaultParameters(resolver)) { if (paramsAssigned.containsKey(formalParameter.getName())) { List<ActualParam> params = new ArrayList<>(); params.add(new ActualParam(new StringLiteral(formalParameter.getName()))); params.add(new ActualParam(paramsAssigned.get(formalParameter.getName()).getValue())); mapCreation = new InstanceMethodInvokation(mapCreation, "put", params); } } mapCreation = new InstanceMethodInvokation(mapCreation, "build", ImmutableList.of()); mapCreation.setParent(this); ActualParam mapForDefaultParams = new ActualParam(mapCreation); mapForDefaultParams.setParent(this); orderedParams.add(mapForDefaultParams); } actualParams = orderedParams; } }