package org.smoothbuild.parse; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; import static org.smoothbuild.lang.function.base.Name.name; import static org.smoothbuild.lang.function.def.Argument.namedArgument; import static org.smoothbuild.lang.function.def.Argument.namelessArgument; import static org.smoothbuild.lang.function.def.Argument.pipedArgument; import static org.smoothbuild.lang.type.Types.BLOB; import static org.smoothbuild.lang.type.Types.FILE; import static org.smoothbuild.lang.type.Types.NIL; import static org.smoothbuild.lang.type.Types.NOTHING; import static org.smoothbuild.lang.type.Types.STRING; import static org.smoothbuild.lang.type.Types.basicTypes; import static org.smoothbuild.parse.LocationHelpers.locationOf; import static org.smoothbuild.util.StringUnescaper.unescaped; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.antlr.v4.runtime.tree.TerminalNode; import org.smoothbuild.antlr.SmoothParser.ArgContext; import org.smoothbuild.antlr.SmoothParser.ArgListContext; import org.smoothbuild.antlr.SmoothParser.ArrayContext; import org.smoothbuild.antlr.SmoothParser.CallContext; import org.smoothbuild.antlr.SmoothParser.ExpressionContext; import org.smoothbuild.antlr.SmoothParser.FunctionContext; import org.smoothbuild.antlr.SmoothParser.FunctionNameContext; import org.smoothbuild.antlr.SmoothParser.ParamNameContext; import org.smoothbuild.antlr.SmoothParser.PipeContext; import org.smoothbuild.cli.Console; import org.smoothbuild.lang.expr.ArrayExpression; import org.smoothbuild.lang.expr.Expression; import org.smoothbuild.lang.expr.ImplicitConverter; import org.smoothbuild.lang.expr.InvalidExpression; import org.smoothbuild.lang.expr.StringLiteralExpression; import org.smoothbuild.lang.function.Functions; import org.smoothbuild.lang.function.base.Function; import org.smoothbuild.lang.function.base.Name; import org.smoothbuild.lang.function.base.Signature; import org.smoothbuild.lang.function.def.Argument; import org.smoothbuild.lang.function.def.ArgumentExpressionCreator; import org.smoothbuild.lang.function.def.DefinedFunction; import org.smoothbuild.lang.message.CodeLocation; import org.smoothbuild.lang.type.ArrayType; import org.smoothbuild.lang.type.Type; import org.smoothbuild.lang.type.Types; import org.smoothbuild.lang.value.Value; import org.smoothbuild.util.UnescapingFailedException; public class DefinedFunctionsCreator { private final Functions functions; private final ArgumentExpressionCreator argumentExpressionCreator; private final ImplicitConverter implicitConverter; @Inject public DefinedFunctionsCreator(Functions functions, ArgumentExpressionCreator argumentExpressionCreator, ImplicitConverter implicitConverter) { this.functions = functions; this.argumentExpressionCreator = argumentExpressionCreator; this.implicitConverter = implicitConverter; } public void createDefinedFunctions(Console console, Map<Name, FunctionContext> functionContexts, List<Name> sorted) { Worker worker = new Worker(console, functions, argumentExpressionCreator, implicitConverter); for (Name name : sorted) { functions.add(worker.build(functionContexts.get(name))); } if (console.isErrorReported()) { throw new ParsingException(); } } private static class Worker { private final Console console; private final Functions functions; private final ArgumentExpressionCreator argumentExpressionCreator; private final ImplicitConverter implicitConverter; public Worker(Console console, Functions functions, ArgumentExpressionCreator argumentExpressionCreator, ImplicitConverter implicitConverter) { this.console = console; this.functions = functions; this.argumentExpressionCreator = argumentExpressionCreator; this.implicitConverter = implicitConverter; } public DefinedFunction build(FunctionContext functionContext) { return toFunction(functionContext); } private DefinedFunction toFunction(FunctionContext functionContext) { Expression expression = toExpression(functionContext.pipe()); Name name = name(functionContext.functionName().getText()); Signature signature = new Signature(expression.type(), name, asList()); return new DefinedFunction(signature, expression); } private Expression toExpression(PipeContext pipeContext) { Expression result = toExpression(pipeContext.expression()); List<CallContext> calls = pipeContext.call(); for (int i = 0; i < calls.size(); i++) { CallContext call = calls.get(i); List<Argument> arguments = toArguments(call.argList()); // nameless piped argument's location is set to the pipe character '|' CodeLocation codeLocation = locationOf(pipeContext.p.get(i)); arguments.add(pipedArgument(result, codeLocation)); result = toExpression(call, arguments); } return result; } private List<Expression> toExpression(List<ExpressionContext> expressionContexts) { return expressionContexts.stream().map(this::toExpression).collect(toList()); } private Expression toExpression(ExpressionContext expressionContext) { if (expressionContext.array() != null) { return toExpression(expressionContext.array()); } if (expressionContext.call() != null) { return toExpression(expressionContext.call()); } if (expressionContext.STRING() != null) { return toStringExpression(expressionContext.STRING()); } throw new RuntimeException("Illegal parse tree: " + ExpressionContext.class.getSimpleName() + " without children."); } private Expression toExpression(ArrayContext array) { List<ExpressionContext> elems = array.expression(); List<Expression> expressions = toExpression(elems); CodeLocation location = locationOf(array); Type elemType = commonSuperType(expressions, location); if (elemType != null) { return toArrayExpression(elemType, expressions, location); } else { return new InvalidExpression(NIL, location); } } private <T extends Value> Expression toArrayExpression(Type elemType, List<Expression> expressions, CodeLocation location) { ArrayType arrayType = Types.arrayTypeContaining(elemType); if (arrayType == null) { console.error(location, "Array cannot contain element with type " + elemType + ". Only following types are allowed: " + basicTypes() + "."); throw new ParsingException(); } return new ArrayExpression(arrayType, toConvertedExpressions(elemType, expressions), location); } public <T extends Value> List<Expression> toConvertedExpressions(Type type, List<Expression> expressions) { return expressions.stream().map((expression) -> implicitConverter.apply(type, expression)) .collect(toList()); } private Type commonSuperType(List<Expression> expressions, CodeLocation location) { if (expressions.size() == 0) { return NOTHING; } Type firstType = expressions.get(0).type(); Type superType = firstType; for (int i = 1; i < expressions.size(); i++) { Type type = expressions.get(i).type(); superType = commonSuperType(superType, type); if (superType == null) { console.error(location, "Array cannot contain elements of incompatible types.\n" + "First element has type " + firstType + " while element at index " + i + " has type " + type + "."); throw new ParsingException(); } } return superType; } private static Type commonSuperType(Type type1, Type type2) { if (type1 == STRING) { if (type2 == STRING) { return STRING; } else { return null; } } else if (type1 == BLOB) { if (type2 == BLOB || type2 == FILE) { return BLOB; } else { return null; } } else if (type1 == FILE) { if (type2 == FILE) { return FILE; } else if (type2 == BLOB) { return BLOB; } else { return null; } } return null; } private Expression toExpression(CallContext callContext) { return toExpression(callContext, toArguments(callContext.argList())); } private Expression toExpression(CallContext callContext, List<Argument> arguments) { FunctionNameContext functionNameContext = callContext.functionName(); Function function = functions.get(name(functionNameContext.getText())); CodeLocation codeLocation = locationOf(functionNameContext); List<Expression> argumentExpressions = argumentExpressionCreator.createArgExprs(codeLocation, console, function, arguments); if (argumentExpressions == null) { return new InvalidExpression(function.type(), codeLocation); } else { return function.createCallExpression(argumentExpressions, false, codeLocation); } } private List<Argument> toArguments(ArgListContext argListContext) { List<Argument> result = new ArrayList<>(); if (argListContext != null) { List<ArgContext> argContexts = argListContext.arg(); for (int i = 0; i < argContexts.size(); i++) { result.add(toArgument(i, argContexts.get(i))); } } return result; } private Argument toArgument(int index, ArgContext arg) { Expression expression = toExpression(arg.expression()); CodeLocation location = locationOf(arg); ParamNameContext paramName = arg.paramName(); if (paramName == null) { return namelessArgument(index + 1, expression, location); } else { return namedArgument(index + 1, paramName.getText(), expression, location); } } private Expression toStringExpression(TerminalNode stringToken) { String quotedString = stringToken.getText(); String string = quotedString.substring(1, quotedString.length() - 1); CodeLocation location = locationOf(stringToken.getSymbol()); try { return new StringLiteralExpression(unescaped(string), location); } catch (UnescapingFailedException e) { console.error(location, e.getMessage()); return new InvalidExpression(STRING, location); } } } }