All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prompto.statement.MethodCall Maven / Gradle / Ivy

The newest version!
package prompto.statement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import prompto.compiler.CompilerUtils;
import prompto.compiler.Flags;
import prompto.compiler.IInstructionListener;
import prompto.compiler.MethodConstant;
import prompto.compiler.MethodInfo;
import prompto.compiler.OffsetListenerConstant;
import prompto.compiler.Opcode;
import prompto.compiler.ResultInfo;
import prompto.compiler.StackState;
import prompto.compiler.StringConstant;
import prompto.declaration.ArrowDeclaration;
import prompto.declaration.BuiltInMethodDeclaration;
import prompto.declaration.ClosureDeclaration;
import prompto.declaration.ConcreteMethodDeclaration;
import prompto.declaration.DispatchMethodDeclaration;
import prompto.declaration.IDeclaration;
import prompto.declaration.IMethodDeclaration;
import prompto.declaration.NativeMethodDeclaration;
import prompto.declaration.TestMethodDeclaration;
import prompto.error.NotMutableError;
import prompto.error.PromptoError;
import prompto.error.SyntaxError;
import prompto.expression.IAssertion;
import prompto.expression.IExpression;
import prompto.expression.MethodSelector;
import prompto.expression.ThisExpression;
import prompto.grammar.Argument;
import prompto.grammar.ArgumentList;
import prompto.grammar.Identifier;
import prompto.grammar.Specificity;
import prompto.javascript.JavaScriptNativeCall;
import prompto.param.CodeParameter;
import prompto.param.IParameter;
import prompto.parser.Dialect;
import prompto.problem.IProblemListener;
import prompto.problem.ProblemCollector;
import prompto.runtime.Context;
import prompto.runtime.Context.MethodDeclarationMap;
import prompto.runtime.MethodFinder;
import prompto.transpiler.Transpiler;
import prompto.type.CodeType;
import prompto.type.IType;
import prompto.type.MethodType;
import prompto.type.PropertiesType;
import prompto.type.VoidType;
import prompto.utils.CodeWriter;
import prompto.value.ArrowValue;
import prompto.value.BooleanValue;
import prompto.value.ClosureValue;
import prompto.value.IValue;

public class MethodCall extends SimpleStatement implements IAssertion {

	MethodSelector selector;
	MethodSelector fullSelector;
	ArgumentList arguments;
	String variableName;
	DispatchMethodDeclaration dispatcher;
	
	public MethodCall(MethodSelector selector) {
		this.selector = selector;
	}

	public MethodCall(MethodSelector selector, ArgumentList arguments) {
		this.selector = selector;
		this.arguments = arguments;
	}
	
	public void setVariableName(String variableName) {
		this.variableName = variableName;
	}

	public MethodSelector getSelector() {
		return selector;
	}

	public ArgumentList getArguments() {
		return arguments;
	}
	
	public String getName() {
		return selector.getName();
	}

	@Override
	public void toDialect(CodeWriter writer) {
		if (requiresInvoke(writer))
			writer.append("invoke: ");
		selector.toDialect(writer);
		if (arguments != null)
			arguments.toDialect(writer);
		else if (writer.getDialect() != Dialect.E)
			writer.append("()");
	}

	private boolean requiresInvoke(CodeWriter writer) {
		if (writer.getDialect() != Dialect.E || (arguments!=null && !arguments.isEmpty()))
			return false;
		try {
			MethodFinder finder = new MethodFinder(writer.getContext(), this);
			IMethodDeclaration declaration = finder.findBest(false);
			/* if method is a reference */
			return declaration.isAbstract() || declaration.getClosureOf()!=null;
		} catch(SyntaxError e) {
			// not an error
			return false;
		}
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(selector.toString());
		sb.append('(');
		if(arguments != null && arguments.size() > 0) {
			arguments.forEach((ass)->
				{
					sb.append(ass.toString());
					sb.append(", ");
				});
			sb.setLength(sb.length()-2);
		}
		sb.append(')');
		return sb.toString();
	}

	@Override
	public IType check(Context context) {
		return check(context, false);
	}
	
	@Override
	public IType checkReference(Context context) {
		MethodFinder finder = new MethodFinder(context, this);
		IMethodDeclaration method = finder.findBest(false);
		if(method!=null)
			return new MethodType(method);
		else
			return null;
	}
	
	
	public IType check(Context context, boolean updateSelectorParent) {
		MethodFinder finder = new MethodFinder(context, this);
		IMethodDeclaration declaration = finder.findBest(false);
		if(declaration==null)
			return VoidType.instance();
		if(updateSelectorParent && declaration.getMemberOf()!=null && this.selector.getParent()==null)
			this.selector.setParent(new ThisExpression());
		if(declaration.isAbstract()) {
			checkAbstractOnly(context, declaration);
			return declaration.getReturnType()!=null ? declaration.getReturnType() : VoidType.instance();
		} else {
			Context local = isLocalClosure(context) ? context : selector.newLocalCheckContext(context, declaration);
			// don't bubble up problems when collecting
			IProblemListener listener = local.getProblemListener();
			if(listener instanceof ProblemCollector)
				listener = new ProblemCollector();
			local.pushProblemListener(listener);
			try {
				return checkDeclaration(declaration, context, local);
			} finally {
				local.popProblemListener();
			}
		}
	}
	
	public ArgumentList makeArguments(Context context) {
		MethodFinder finder = new MethodFinder(context, this);
		IMethodDeclaration declaration = finder.findBest(false);
		return makeArguments(context, declaration);
	}

	private void checkAbstractOnly(Context context, IMethodDeclaration declaration) {
		if(declaration.isReference()) // parameter or variable populated from a method call
			return;
		if(declaration.getMemberOf()!=null) // the category could be subclassed (if constructor called on abstract, that would raise an error anyway)
			return;
		// if a global method, need to check for runtime dispatch
		MethodFinder finder = new MethodFinder(context, this);
		Set potential = finder.findPotential();
		potential = potential.stream().filter(m->!m.isAbstract()).collect(Collectors.toSet());
		if(potential.isEmpty()) {
			// raise error if direct call to pure abstract method
			context.getProblemListener().reportIllegalAbstractMethodCall(this, declaration.getSignature(Dialect.O));
		}
	}

	private boolean isLocalClosure(Context context) {
		if(this.selector.getParent()!=null)
			return false;
		IDeclaration decl = context.getLocalDeclaration(IDeclaration.class, this.selector.getId());
		return decl instanceof MethodDeclarationMap;
	}

	private IType checkDeclaration(IMethodDeclaration declaration, Context parent, Context local) {
		if (declaration.isTemplate())
			return fullCheck((ConcreteMethodDeclaration) declaration, parent, local);
		else
			return lightCheck(declaration, parent, local);
	}

	private IType lightCheck(IMethodDeclaration declaration, Context parent, Context local) {
		declaration.registerParameters(local);
		return declaration.check(local, false);
	}

	private IType fullCheck(ConcreteMethodDeclaration declaration, Context parent, Context local) {
		try {
			ArgumentList arguments = makeArguments(parent, declaration);
			declaration.registerParameters(local);
			for (Argument argument : arguments) {
				IExpression expression = argument.resolve(local, declaration, true, false);
				IValue value = argument.getParameter().checkValue(parent, expression);
				local.setValue(argument.getParameterId(), value);
			}
			return declaration.check(local, false);
		} catch (PromptoError e) {
			throw new SyntaxError(e.getMessage());
		}
	}

	public ArgumentList makeArguments(Context context, IMethodDeclaration declaration) {
		ArgumentList arguments = this.arguments!=null ? this.arguments : new ArgumentList();
		return arguments.makeArguments(context, declaration);
	}

	public ArgumentList makeCodeAssignments(Context context, IMethodDeclaration declaration) {
		if (arguments == null)
			return new ArgumentList();
		else {
			ArgumentList list = new ArgumentList();
			list.addAll(arguments.stream()
					.filter((a)->
						(a.getExpression().check(context)==CodeType.instance()))
					.collect(Collectors.toList()));
			return list.resolveAndCheck(context, declaration);
		}
	}

	@Override
	public ResultInfo compile(Context context, MethodInfo method, Flags flags) {
		MethodFinder finder = new MethodFinder(context, this);
		Collection potential = finder.findPotential();
		switch(potential.size()) {
		case 0:
			throw new SyntaxError("No matching prototype for:" + this.toString()); 
		case 1:
			return compileExact(context, method, flags, potential.iterator().next());
		default:
			return compileDynamic(context, method, flags, finder.findLessSpecific(potential));
		}
	}
	
	private ResultInfo compileDynamic(Context context, MethodInfo method, Flags flags, IMethodDeclaration declaration) {
		Context local = this.selector.newLocalCheckContext(context, declaration);
		declaration.registerParameters(local);
		ArgumentList arguments = this.arguments!=null ? this.arguments : new ArgumentList();
		return this.selector.compileDynamic(local, method, flags, declaration, arguments);
	}

	private ResultInfo compileExact(Context context, MethodInfo method, Flags flags, IMethodDeclaration declaration) {
		if(declaration.isTemplate())
			return compileTemplate(context, method, flags, declaration);
		else
			return compileConcrete(context, method, flags, declaration);
	}

	private ResultInfo compileConcrete(Context context, MethodInfo method, Flags flags, IMethodDeclaration declaration) {
		Context local = isLocalClosure(context) ? context : selector.newLocalCheckContext(context, declaration);
		declaration.registerParameters(local);
		ArgumentList arguments = this.arguments!=null ? this.arguments : new ArgumentList();
		return this.selector.compileExact(local, method, flags, declaration, arguments);
	}

	private ResultInfo compileTemplate(Context context, MethodInfo method, Flags flags, IMethodDeclaration declaration) {
		// compile the method as a member method
		Context local = context.newLocalContext();
		declaration.registerParameters(local);
		registerCodeAssignments(context, local, declaration);
		String methodName = declaration.compileTemplate(local, false, method.getClassFile());
		// compile the method call
		IExpression parent = method.isStatic() ? null : new ThisExpression();
		MethodSelector selector = new MethodSelector(parent, new Identifier(methodName));
		local = selector.newLocalContext(context, declaration);
		declaration.registerParameters(local);
		registerCodeAssignments(context, local, declaration);
		ArgumentList arguments = this.arguments!=null ? this.arguments : new ArgumentList();
		return selector.compileTemplate(local, method, flags, declaration, arguments, methodName);
	}

	private void registerCodeAssignments(Context context, Context local, IMethodDeclaration declaration) {
		ArgumentList arguments = makeCodeAssignments(context, declaration);
		for (Argument argument : arguments) {
			IExpression expression = argument.resolve(local, declaration, true, false);
			IParameter parameter = argument.getParameter();
			IValue value = parameter.checkValue(context, expression);
			local.setValue(argument.getParameterId(), value);
		}	
	}

	@Override
	public IValue interpret(Context context) throws PromptoError {
		MethodFinder finder = new MethodFinder(context, this);
		IMethodDeclaration declaration = finder.findBest(true);
		if(declaration == null)
			context.getProblemListener().reportUnknownMethod(this, this.toString());
		Context local = selector.newLocalContext(context, declaration);
		local.enterMethod(declaration);
		try {
			declaration.registerParameters(local);
			assignArguments(context, local, declaration);
			return declaration.interpret(local);
		} finally {
			local.leaveSection(declaration);
		}
	}
	
	@Override
	public IValue interpretReference(Context context) {
		IMethodDeclaration declaration = findDeclaration(context, true);
		return new ClosureValue(context, new MethodType(declaration));
	}

	private void assignArguments(Context calling, Context local, IMethodDeclaration declaration) throws PromptoError {
		ArgumentList arguments = makeArguments(calling, declaration);
		for (Argument argument : arguments) {
			IExpression expression = argument.resolve(local, declaration, true, false);
			IParameter parameter = argument.getParameter();
			IValue value = parameter.checkValue(calling, expression);
			if(value!=null && parameter.isMutable() && !value.isMutable()) 
				throw new NotMutableError();
			local.setValue(argument.getParameterId(), value);
		}
	}

	@Override
	public boolean interpretAssert(Context context, TestMethodDeclaration test) throws PromptoError {
		IValue value = this.interpret(context);
		if(value instanceof BooleanValue) {
			if(((BooleanValue)value).getValue())
				return true;
			else {
				String expected = buildExpectedMessage(context, test);
				String actual = value.toString();
				test.printFailedAssertion(context, expected, actual);
				return false;
			}
		} else {
			CodeWriter writer = new CodeWriter(this.getDialect(), context);
			this.toDialect(writer);
			throw new SyntaxError("Cannot test '" + writer.toString() + "'");
		}
	}
	
	private String buildExpectedMessage(Context context, TestMethodDeclaration test) {
		CodeWriter writer = new CodeWriter(test.getDialect(), context);
		this.toDialect(writer);
		return writer.toString();
	}

	@Override
	public void compileAssert(Context context, MethodInfo method, Flags flags, TestMethodDeclaration test) {
		StackState finalState = method.captureStackState();
		// compile
		ResultInfo info = this.compile(context, method, flags.withPrimitive(true));
		if(java.lang.Boolean.class==info.getType())
			CompilerUtils.BooleanToboolean(method);
		// 1 = success 
		IInstructionListener finalListener = method.addOffsetListener(new OffsetListenerConstant());
		method.activateOffsetListener(finalListener);
		method.addInstruction(Opcode.IFNE, finalListener); 
		// increment failure counter
		method.addInstruction(Opcode.ICONST_1);
		method.addInstruction(Opcode.IADD);
		// build failure message
		String message = buildExpectedMessage(context, test);
		message = test.buildFailedAssertionMessagePrefix(message);
		method.addInstruction(Opcode.LDC, new StringConstant(message));
		method.addInstruction(Opcode.LDC, new StringConstant(BooleanValue.FALSE.toString()));
		MethodConstant concat = new MethodConstant(String.class, "concat", String.class, String.class);
		method.addInstruction(Opcode.INVOKEVIRTUAL, concat);
		test.compileFailure(context, method, flags);
		// success/final
		method.restoreFullStackState(finalState);
		method.placeLabel(finalState);
		method.inhibitOffsetListener(finalListener);
	}
	
	IMethodDeclaration findDeclaration(Context context, boolean checkInstance) {
		IMethodDeclaration method = findRegistered(context);
		if(method!=null)
			return method;
		else {
			MethodFinder finder = new MethodFinder(context, this);
			return finder.findBest(checkInstance);
		}
	}
	
	private IMethodDeclaration findRegistered(Context context) {
		if(selector.getParent()==null) try {
			Object o = context.getValue(selector.getId());
			if (o instanceof ClosureValue)
				return getClosureDeclaration(context, (ClosureValue)o);
			else if (o instanceof ArrowValue)
				return new ArrowDeclaration((ArrowValue)o);
		} catch (PromptoError e) {
		}
		return null;
	}

	private IMethodDeclaration getClosureDeclaration(Context context, ClosureValue closure) {
		IMethodDeclaration decl = closure.getMethod();
		if(decl.getMemberOf()!=null) {
			// the closure references a member method (useful when a method reference is needed)
			// in which case we may simply want to return that method to avoid spilling context into method body
			// this is only true if the closure comes straight from the method's instance context
			// if the closure comes from an accessible context that is not the instance context
			// then it is a local variable that needs the closure context to be interpreted
			Context declaring = context.contextForValue(selector.getId());
			if( declaring == closure.getContext())
				return decl;
		}
		return new ClosureDeclaration(closure);
	}

	@Override
	public void declare(Transpiler transpiler) {
		Context context = transpiler.getContext();
		// don't bubble up problems, they will emerge when transpiling
		context.pushProblemListener(new ProblemCollector());
		try {
			doDeclare(transpiler);
		} finally {
			context.popProblemListener();
		}
	}
	
	private void doDeclare(Transpiler transpiler) {
		MethodFinder finder = new MethodFinder(transpiler.getContext(), this);
		IMethodDeclaration reference = finder.findBestReference(false, new ArrayList<>());
		if(reference != null)
			return; // already declared
		Set candidates = finder.findCandidates(false);
		if(candidates.size()==0) {
			transpiler.getContext().getProblemListener().reportUnknownMethod(getSelector().getId(), this.getName());
		} else {
		    Set compatibles = finder.filterCompatible(candidates, false, true, spec -> spec!=Specificity.INCOMPATIBLE);
		    if(compatibles==null || compatibles.isEmpty())
		    	transpiler.getContext().getProblemListener().reportNoMatchingPrototype(this, this.toString(), candidates.stream().map(m->m.getSignature(Dialect.O)).collect(Collectors.toSet()));
		    else if(compatibles.size()==1 && compatibles.iterator().next() instanceof BuiltInMethodDeclaration) {
	            ((BuiltInMethodDeclaration)compatibles.iterator().next()).declareCall(transpiler);
		    } else {
	        	if(!this.isLocalClosure(transpiler.getContext())) {
	        		compatibles.forEach(declaration -> {
			            Context local = this.selector.newLocalCheckContext(transpiler.getContext(), declaration);
			            this.declareDeclaration(transpiler, declaration, local);
			        });
	        	}
		        if(compatibles.size()>1 && this.dispatcher==null) {
		        	IMethodDeclaration declaration = finder.findBest(false);
		        	List sorted = finder.sortMostSpecificFirst(compatibles);
		            this.dispatcher = new DispatchMethodDeclaration(transpiler.getContext(), this, declaration, sorted);
		            transpiler.declare(this.dispatcher);
		        }
		    }
		}
	}

	private void declareDeclaration(Transpiler transpiler, IMethodDeclaration declaration, Context local) {
        if (this.arguments != null)
            this.arguments.declare(transpiler, declaration);
	    if(declaration.isTemplate()) {
	        this.fullDeclareDeclaration(declaration, transpiler, local);
	    } else {
	        this.lightDeclareDeclaration(declaration, transpiler, local);
	    }
	}

	static AtomicLong fullDeclareCounter = new AtomicLong();
	
	private void fullDeclareDeclaration(IMethodDeclaration declaration, Transpiler transpiler, Context local) {
	    if(this.fullSelector==null) {
	    	ArgumentList arguments = this.makeArguments(transpiler.getContext(), declaration);
	        declaration.registerParameters(local);
	        arguments.forEach(argument -> {
	            IExpression expression = argument.resolve(local, declaration, true, false);
	            IValue value = argument.getParameter().checkValue(transpiler.getContext(), expression);
	            local.setValue(argument.getParameter().getId(), value);
	        });
	        Transpiler localTranspiler = transpiler.copyTranspiler(local);
	        this.fullSelector = this.selector.newFullSelector(fullDeclareCounter.incrementAndGet());
	        declaration.fullDeclare(localTranspiler, this.fullSelector.getId());
	    }
	}

	private void lightDeclareDeclaration(IMethodDeclaration declaration, Transpiler transpiler, Context local) {
	    transpiler = transpiler.copyTranspiler(local);
	    declaration.declare(transpiler);
	}
	
	@Override
	public boolean transpile(Transpiler transpiler) {
		MethodFinder finder = new MethodFinder(transpiler.getContext(), this);
		IMethodDeclaration reference = finder.findBestReference(false, new ArrayList<>());
		if(reference != null) {
	        transpileSelector(transpiler, reference);
	        transpileAssignments(transpiler, reference, false);
			return false;
		}
		Set candidates = finder.findCandidates(false);
		if(candidates.size()==0) {
	    	transpiler.getContext().getProblemListener().reportUnknownMethod(getSelector().getId(), this.getName());
	    	return false;
		}
	    Set compatibles = finder.filterCompatible(candidates, false, true, spec -> spec!=Specificity.INCOMPATIBLE);
	    if(compatibles==null || compatibles.isEmpty())
	    	transpiler.getContext().getProblemListener().reportNoMatchingPrototype(this, this.toString(), candidates.stream().map(m->m.getSignature(Dialect.O)).collect(Collectors.toSet()));
		else if (compatibles.size() == 1)
	        transpileSingle(transpiler, compatibles.iterator().next(), false);
	    else
	        transpileMultiple(transpiler, compatibles);
	    return false;
	}

	private void transpileSingle(Transpiler transpiler, IMethodDeclaration declaration, boolean allowDerived) {
	   if (declaration instanceof BuiltInMethodDeclaration)
	        transpileBuiltin(transpiler, (BuiltInMethodDeclaration)declaration);
	   else if(mustInlineMethodCall(transpiler.getContext(), declaration))
		   transpileInlinedMethodCall(transpiler, (NativeMethodDeclaration)declaration);
	   else {
	        transpileSelector(transpiler, declaration);
	        transpileAssignments(transpiler, declaration, allowDerived);
	    }
	}

	private boolean mustInlineMethodCall(Context context, IMethodDeclaration declaration) {
		return declaration instanceof NativeMethodDeclaration
				&& (declaration.hasAnnotation(context, "Inlined")
					|| declaration.containerHasAnnotation(context, "Inlined"));
	}

	private void transpileInlinedMethodCall(Transpiler transpiler, NativeMethodDeclaration declaration) {
		JavaScriptNativeCall call = declaration.findCall(JavaScriptNativeCall.class);
		if(call==null)
			throw new UnsupportedOperationException("Missing native JavaScript call!");
		call.transpileInlineMethodCall(transpiler, declaration, this);
	}
	
	
	private void transpileAssignments(Transpiler transpiler, IMethodDeclaration declaration, boolean allowDerived) {
		List arguments = makeArguments(transpiler.getContext(), declaration);
		transpileAssignments(transpiler, arguments, declaration, allowDerived);
	}
	
	public void transpileAssignments(Transpiler transpiler, List arguments, IMethodDeclaration declaration, boolean allowDerived) {
	    arguments = arguments.stream().filter(argument->!(argument.getParameter() instanceof CodeParameter)).collect(Collectors.toList());
	    if(!arguments.isEmpty()) {
	        transpiler.append("(");
	        arguments.forEach(argument -> {
	            IParameter parameter = argument.getParameter();
	            IExpression expression = argument.resolve(transpiler.getContext(), declaration, false, allowDerived);
	            parameter.transpileCall(transpiler, expression);
	            transpiler.append(", ");
	        });
	        transpiler.trimLast(2);
	        transpiler.append(")");
	    } else
	        transpiler.append("()");
	}

	public void transpileSelector(Transpiler transpiler, IMethodDeclaration declaration) {
		MethodSelector selector = resolveSelector(transpiler, declaration);
	    selector.transpile(transpiler);
	}
	
	public MethodSelector resolveSelector(Transpiler transpiler, IMethodDeclaration declaration) {
	    MethodSelector selector = /*this.fullSelector ||*/ this.selector;
	    IExpression parent = selector.resolveParent(transpiler.getContext());
	    if (parent == null && declaration.getMemberOf()!=null && transpiler.getContext().getClosestInstanceContext()!=null)
	        parent = new ThisExpression();
	    String name = null;
	    if(this.variableName!=null)
	        name = this.variableName;
	    // don't transpile name of local abstract method references 
	    else if(!selector.getName().equals(declaration.getName()))
	        name = selector.getName();
        // don't transpile name of method references in widget property value 
	    else if(parent!=null && parent.check(transpiler.getContext()) instanceof PropertiesType)
	    	name = selector.getName();
	    else 
	        name = declaration.getTranspiledName(transpiler.getContext());
	    return new MethodSelector(parent, new Identifier(name));
	}

	private void transpileBuiltin(Transpiler transpiler, BuiltInMethodDeclaration declaration) {
	    IExpression parent = this.selector.resolveParent(transpiler.getContext());
	    parent.transpileParent(transpiler);
	    transpiler.append(".");
	    declaration.transpileCall(transpiler, makeArguments(transpiler.getContext(), declaration));
	}

	private void transpileMultiple(Transpiler transpiler, Set declarations) {
	    String name = this.dispatcher.getTranspiledName(transpiler.getContext());
	    IExpression parent = this.selector.resolveParent(transpiler.getContext());
	    if(parent==null && declarations.iterator().next().getMemberOf()!=null && transpiler.getContext().getClosestInstanceContext()!=null)
	        parent = new ThisExpression();
	    MethodSelector selector = new MethodSelector(parent, new Identifier(name));
	    selector.transpile(transpiler);
	    this.transpileAssignments(transpiler, this.dispatcher, false);
	}

	@Override
	public void transpileFound(Transpiler transpiler, Dialect dialect) {
		CodeWriter writer = new CodeWriter(dialect, transpiler.getContext());
		this.toDialect(writer);
		transpiler.append(writer.toString());
	}
	
	



}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy