/* * Copyright 2003-2017 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; import jetbrains.mps.generator.GenerationCanceledException; import jetbrains.mps.generator.IGeneratorLogger; import jetbrains.mps.generator.impl.GeneratorUtilEx.ConsequenceDispatch; import jetbrains.mps.generator.impl.interpreted.TemplateCall; import jetbrains.mps.generator.impl.query.GeneratorQueryProvider; import jetbrains.mps.generator.impl.query.InlineSwitchCaseCondition; import jetbrains.mps.generator.impl.query.QueryKey; import jetbrains.mps.generator.impl.query.QueryKeyImpl; import jetbrains.mps.generator.impl.query.QueryProviderBase; import jetbrains.mps.generator.impl.template.QueryExecutor; import jetbrains.mps.generator.runtime.TemplateContext; import jetbrains.mps.generator.template.InlineSwitchCaseContext; import jetbrains.mps.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import java.util.ArrayList; import java.util.List; /** * Handles rule consequences. * * @author Artem Tikhomirov */ public abstract class RuleConsequenceProcessor { /*package*/ RuleConsequenceProcessor() { } /** * Factory method for rule consequences */ public static RuleConsequenceProcessor prepare(@NotNull SNode ruleConsequence) { return new ConsequenceHandler().dispatch(ruleConsequence); } @NotNull public abstract List<SNode> processRuleConsequence(@NotNull TemplateContext context) throws GenerationFailureException, GenerationCanceledException, DismissTopMappingRuleException, AbandonRuleInputException; private static class InlineSwitch extends RuleConsequenceProcessor { @NotNull private final SNode mySwitchNode; private volatile CaseRuntime[] myCases; InlineSwitch(@NotNull SNode inlineSwitchNode) { mySwitchNode = inlineSwitchNode; } @NotNull @Override public List<SNode> processRuleConsequence(@NotNull TemplateContext context) throws GenerationFailureException, GenerationCanceledException, DismissTopMappingRuleException, AbandonRuleInputException { final QueryExecutor queryExecutor = context.getEnvironment().getQueryExecutor(); for (CaseRuntime switchCase : getCases(context.getEnvironment())) { if (queryExecutor.evaluate(switchCase.condition, new InlineSwitchCaseContext(context, switchCase.nodeReference))) { return switchCase.consequence.processRuleConsequence(context); } } SNode defaultConsequence = RuleUtil.getInlineSwitch_defaultConsequence(mySwitchNode); if (defaultConsequence == null) { GenerationFailureException ex = new GenerationFailureException("no default consequence in switch"); ex.setTemplateContext(context); ex.setTemplateModelLocation(mySwitchNode.getReference()); throw ex; } else { RuleConsequenceProcessor rcp = RuleConsequenceProcessor.prepare(defaultConsequence); return rcp.processRuleConsequence(context); } } private CaseRuntime[] getCases(GeneratorQueryProvider.Source qps) { CaseRuntime[] rv = myCases; if (rv == null) { ArrayList<CaseRuntime> l = new ArrayList<>(); for (SNode switchCase : RuleUtil.getInlineSwitch_case(mySwitchNode)) { SNode caseConditionNode = RuleUtil.getInlineSwitch_caseCondition(switchCase); final InlineSwitchCaseCondition condition; if (caseConditionNode != null) { QueryKey identity = new QueryKeyImpl(switchCase.getReference(), caseConditionNode.getNodeId(), switchCase); condition = qps.getQueryProvider(mySwitchNode.getReference()).getInlineSwitchCaseCondition(identity); } else { condition = new QueryProviderBase.Missing(switchCase); } SNode caseConsequence = RuleUtil.getInlineSwitch_caseConsequence(switchCase); RuleConsequenceProcessor rcp = RuleConsequenceProcessor.prepare(caseConsequence); l.add(new CaseRuntime(condition, rcp, switchCase.getReference())); } if (myCases == null) { myCases = rv = l.toArray(new CaseRuntime[l.size()]); } else { rv = myCases; } } return rv; } private static class CaseRuntime { public final SNodeReference nodeReference; public final RuleConsequenceProcessor consequence; public final InlineSwitchCaseCondition condition; public CaseRuntime(InlineSwitchCaseCondition condition, RuleConsequenceProcessor rcp, SNodeReference nodeRef) { this.condition = condition; this.consequence = rcp; this.nodeReference = nodeRef; } } } private static class AbandonRuleControlFlowConsequence extends RuleConsequenceProcessor { private final SNodeReference myLocation; public AbandonRuleControlFlowConsequence(SNodeReference location) { myLocation = location; } @NotNull @Override public List<SNode> processRuleConsequence(@NotNull TemplateContext context) throws AbandonRuleInputException { AbandonRuleInputException ex = new AbandonRuleInputException(); ex.setTemplateContext(context); ex.setTemplateModelLocation(myLocation); throw ex; } } private static class DismissRuleControlFlowConsequence extends RuleConsequenceProcessor { private final SNodeReference myLocation; private final DismissTopMappingRuleException.MessageType myMessageType; private final String myText; public DismissRuleControlFlowConsequence(SNodeReference location, DismissTopMappingRuleException.MessageType messageType, String text) { myLocation = location; myMessageType = messageType; myText = text; } @NotNull @Override public List<SNode> processRuleConsequence(@NotNull TemplateContext context) throws DismissTopMappingRuleException { DismissTopMappingRuleException ex = new DismissTopMappingRuleException(myMessageType, myText); ex.setTemplateContext(context); ex.setTemplateModelLocation(myLocation); throw ex; } } private static class BadConsequence extends RuleConsequenceProcessor { private final SNode myConsequence; private final String myMessage; public BadConsequence(@NotNull SNode consequence, @NotNull String message) { myConsequence = consequence; myMessage = message; } @NotNull @Override public List<SNode> processRuleConsequence(@NotNull TemplateContext context) throws GenerationFailureException { IGeneratorLogger log = context.getEnvironment().getLogger(); log.error(myConsequence.getReference(), myMessage, GeneratorUtil.describeInput(context)); GenerationFailureException ex = new GenerationFailureException(myMessage); ex.setTemplateModelLocation(myConsequence.getReference()); ex.setTemplateContext(context); throw ex; } } private static class TemplateDeclarationReference extends RuleConsequenceProcessor { private final RuleConsequenceProcessor myTemplateContainer; private final TemplateCall myTemplateCall; public TemplateDeclarationReference(@NotNull SNode ruleConsequence, @NotNull RuleConsequenceProcessor templateContainer) { myTemplateContainer = templateContainer; myTemplateCall = new TemplateCall(ruleConsequence); } @NotNull @Override public List<SNode> processRuleConsequence(@NotNull TemplateContext context) throws GenerationFailureException, GenerationCanceledException, DismissTopMappingRuleException, AbandonRuleInputException { TemplateContext ctx = myTemplateCall.prepareCallContext(context); return myTemplateContainer.processRuleConsequence(ctx); } } private static class ConsequenceHandler implements ConsequenceDispatch { private RuleConsequenceProcessor myConsequence; @NotNull public RuleConsequenceProcessor dispatch(@NotNull SNode ruleConsequence) { myConsequence = null; GeneratorUtilEx.dispatchRuleConsequence(ruleConsequence, this); if (myConsequence == null) { assert false; // can't happen provided there's no mistake in GeneratorUtilEx.dispatchRuleConsequence myConsequence = new BadConsequence(ruleConsequence, "unknown consequence kind"); } return myConsequence; } @Override public void inlineSwitch(SNode ruleConsequence) { myConsequence = new InlineSwitch(ruleConsequence); } @Override public void inlineTemplateWithContext(SNode ruleConsequence) { myConsequence = getTemplateContainer(ruleConsequence, RuleUtil.getInlineTemplateWithContext_contentNode(ruleConsequence)); } @Override public void inlineTemplate(SNode ruleConsequence) { SNode templateNode = RuleUtil.getInlineTemplate_templateNode(ruleConsequence); if (templateNode != null) { myConsequence = new TemplateContainer(new Pair<>(templateNode, null)); } else { myConsequence = new BadConsequence(ruleConsequence, "no template node"); } } @Override public void templateDeclarationReference(SNode ruleConsequence) { // XXX the reason we don't use TemplateDeclarationInterpreted here seems to be // limitation of the TemplateDeclarationInterpreted - the way arguments are supplied there is different // from the one we use here (latter evaluates, while former get actual values) final RuleConsequenceProcessor templateContainer = getTemplateContainer(ruleConsequence, RuleUtil.getTemplateDeclarationReference_Template(ruleConsequence)); // XXX what if we invoked generated external template from non-generated generator? Shouldn't we use TEE.applyTemplate() here, which would load proper // TemplateDeclaration (either from model or generated code)? // Besides, the fact we use TemplateContainer, not TemplateDeclarationInterpreted, forces us to duplicate invocation code // and leads to errors like https://youtrack.jetbrains.com/issue/MPS-24406, when invocation of a template from generated code yields results // different from that of invocation from interpreted code. myConsequence = new TemplateDeclarationReference(ruleConsequence, templateContainer); } private static RuleConsequenceProcessor getTemplateContainer(SNode ruleConsequence, SNode templateContainer) { if (templateContainer != null) { return new TemplateContainer(templateContainer); } else { return new BadConsequence(ruleConsequence, "error processing template consequence: no 'template'"); } } @Override public void weaveEach(SNode ruleConsequence) { myConsequence = new BadConsequence(ruleConsequence, "weaveEach is not expected here"); } @Override public void abandonInput(SNode ruleConsequence) { myConsequence = new AbandonRuleControlFlowConsequence(ruleConsequence.getReference()); } @Override public void dismissTopRule(SNode ruleConsequence) { SNode message = RuleUtil.getDismissTopRule_message(ruleConsequence); DismissTopMappingRuleException.MessageType messageType = GeneratorUtilEx.getGeneratorMessage_kind(message); String text = GeneratorUtilEx.getGeneratorMessage_text(message); myConsequence = new DismissRuleControlFlowConsequence(ruleConsequence.getReference(), messageType, text); } @Override public void unknown(SNode ruleConsequence) { myConsequence = new BadConsequence(ruleConsequence, "unsupported rule consequence"); } } }