/* * Copyright 2003-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.generator.impl.interpreted; import jetbrains.mps.generator.impl.GenerationFailureException; import jetbrains.mps.generator.impl.GeneratorUtil; import jetbrains.mps.generator.impl.GeneratorUtilEx; import jetbrains.mps.generator.impl.RuleUtil; import jetbrains.mps.generator.impl.query.CallArgumentQuery; import jetbrains.mps.generator.impl.query.GeneratorQueryProvider; import jetbrains.mps.generator.impl.query.QueryKey; import jetbrains.mps.generator.impl.query.QueryKeyImpl; import jetbrains.mps.generator.runtime.TemplateContext; import jetbrains.mps.generator.template.TemplateArgumentContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.model.SNode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Runtime presentation of a template invocation. Handles arguments, prepares template context for a call. * @author Artem Tikhomirov */ public class TemplateCall { private final ArgumentExpression[] myArguments; private final String[] myParameters; // true to indicate no-op context, either no args/params, or their count doesn't match private final boolean myNoArgs; /** * At the moment, we handle ITemplateCall only, the reference to TemplateDeclaration with accompanying arguments. * Elements with references to TemplateDeclaration without arguments (like WeaveEach) shall not get here (although it's not a big deal * to handle it here, just keep myArguments empty, and return outerContext unchanged). The reason it's not done here as this knowledge * (which consequence is ITemplateCall) is static, and I don't want runtime checks for static knowledge * @param templateCall ITemplateCall node */ public TemplateCall(@NotNull SNode templateCall) { final List<SNode> args = RuleUtil.getTemplateCall_Arguments(templateCall); myArguments = toExpressionRuntime(args); final SNode template = RuleUtil.getTemplateCall_Template(templateCall); String[] paramNames = RuleUtil.getTemplateDeclarationParameterNames(template); myParameters = paramNames == null ? new String[0] : paramNames; myNoArgs = myArguments.length == 0 || myArguments.length != myParameters.length; } /** * @return <code>true</code> iff there are arguments or parameters, but their count doesn't match */ public boolean argumentsMismatch() { return myArguments.length != myParameters.length; } @NotNull public TemplateContext prepareCallContext(@NotNull TemplateContext outerContext) throws GenerationFailureException{ if (myNoArgs) { return outerContext; } final Map<String, Object> vars = new HashMap<String, Object>(myArguments.length * 2); for (int i = 0; i < myArguments.length; i++) { Object value = myArguments[i].evaluate(outerContext); vars.put(myParameters[i], value); } // variables drop mapping label, hence need to reinstall it return outerContext.subContext(vars).subContext(outerContext.getInputName()); } private static ArgumentExpression[] toExpressionRuntime(List<SNode> args) { final ArrayList<ArgumentExpression> ae = new ArrayList<ArgumentExpression>(args.size()); int i = 1; for (SNode argExpr : args) { final SConcept argConcept = argExpr.getConcept(); if (argConcept.isSubConceptOf(RuleUtil.concept_TemplateArgumentParameterExpression)) { ae.add(new TemplateParameterExpr(argExpr, i)); } else if (argConcept.isSubConceptOf(RuleUtil.concept_TemplateArgumentPatternRef)) { ae.add(new PatternRefExpr(argExpr, i)); } else if (argConcept.isSubConceptOf(RuleUtil.concept_TemplateArgumentQueryExpression)) { ae.add(new QueryExpr(argExpr)); } else if (argConcept.isSubConceptOf(RuleUtil.concept_TemplateArgumentVarRefExpression)) { ae.add(new VarRefExpr(argExpr)); } else if(GeneratorUtilEx.shallGenerateFunctionToEvaluate(argExpr)) { ae.add(new GeneratedExpr(argExpr)); } else { ae.add(new OtherExpr(argExpr, i)); } i++; } return ae.toArray(new ArgumentExpression[ae.size()]); } interface ArgumentExpression { public Object evaluate(TemplateContext context) throws GenerationFailureException; } // TemplateArgumentParameterExpression private static class TemplateParameterExpr implements ArgumentExpression { private final String myParameterName; private final SNode myParameterExpr; private final int myArgIndex; public TemplateParameterExpr(SNode parameterExpr, int index) { myParameterExpr = parameterExpr; myArgIndex = index; SNode parameter = RuleUtil.getTemplateArgumentParameterExpression_Parameter(parameterExpr); myParameterName = parameter == null ? null : parameter.getName(); } @Override public Object evaluate(TemplateContext context) throws GenerationFailureException { if (myParameterName == null) { context.getEnvironment().getLogger().error(myParameterExpr.getReference(), "cannot evaluate template argument #" + (myArgIndex) + ": invalid parameter reference", GeneratorUtil.describeInput(context)); return null; } return context.getVariable(myParameterName); } } // TemplateArgumentPatternRef private static class PatternRefExpr implements ArgumentExpression { private final SNode myPatternExpr; private final int myArgIndex; private final String myPatternVar; public PatternRefExpr(SNode patternExpr, int index) { myPatternExpr = patternExpr; myArgIndex = index; myPatternVar = GeneratorUtilEx.getPatternVariableName(patternExpr); } @Override public Object evaluate(TemplateContext context) throws GenerationFailureException { if (myPatternVar == null) { context.getEnvironment().getLogger().error(myPatternExpr.getReference(), "cannot evaluate template argument #" + (myArgIndex) + ": invalid pattern reference", GeneratorUtil.describeInput(context)); return null; } else { // TODO FIXME using PatternVarsUtil directly, which is loaded by MPS return context.getPatternVariable(myPatternVar); } } } // TemplateArgumentQueryExpression private static class QueryExpr implements ArgumentExpression { private final QueryKey myQueryKey; private CallArgumentQuery myQuery; public QueryExpr(SNode queryExpr) { SNode query = RuleUtil.getTemplateArgumentQueryExpression_Query(queryExpr); myQueryKey = new QueryKeyImpl(queryExpr.getReference(), query.getNodeId()); } protected QueryExpr(QueryKey key) { myQueryKey = key; } @Override public Object evaluate(TemplateContext context) throws GenerationFailureException { CallArgumentQuery q = myQuery; if (q == null) { // TODO perhaps, shall initialize it at construction time, rather than on first use? GeneratorQueryProvider queryProvider = context.getEnvironment().getQueryProvider(myQueryKey.getTemplateNode()); q = myQuery = queryProvider.getTemplateCallArgumentQuery(myQueryKey); } return context.getEnvironment().getQueryExecutor().evaluate(q, new TemplateArgumentContext(context, myQueryKey.getTemplateNode())); } } // TemplateArgumentVariableRefExpression private static class VarRefExpr implements ArgumentExpression { private final String myMacroVarName; public VarRefExpr(SNode varRefExpression) { SNode varmacro = RuleUtil.getTemplateArgumentVarRef_VarMacro(varRefExpression); myMacroVarName = RuleUtil.getVarMacro_Name(varmacro); } @Override public Object evaluate(TemplateContext context) throws GenerationFailureException { return context.getVariable(myMacroVarName); } } // Expression, requires generated code according to GeneratorUtilEx.shallGenerateFunctionToEvaluate private static class GeneratedExpr extends QueryExpr implements ArgumentExpression { public GeneratedExpr(SNode expr) { // Here we utilize the fact we generate identical methods both for TemplateArgumentQueryExpression and plain Expressions, // and rely on the fact query provider needs only SNodeId to build a method name. super(new QueryKeyImpl(expr.getReference(), expr.getNodeId())); } } // Expression, primitive value. Unlike GeneratedExpr, doesn't require generated code to evaluate private static class OtherExpr implements ArgumentExpression { private final SNode myExpression; private final int myArgIndex; public OtherExpr(SNode expression, int index) { myExpression = expression; myArgIndex = index; } @Override public Object evaluate(TemplateContext context) throws GenerationFailureException { try { return GeneratorUtilEx.evaluateExpression(myExpression); } catch(IllegalArgumentException ex) { context.getEnvironment().getLogger().error(myExpression.getReference(), String.format("cannot evaluate template argument #%d: %s", myArgIndex, ex.toString()), GeneratorUtil.describeInput(context)); } return null; } } }