/* * 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.GenerationSessionContext; import jetbrains.mps.generator.GenerationTrace; import jetbrains.mps.generator.GenerationTracerUtil; import jetbrains.mps.generator.IGeneratorLogger; import jetbrains.mps.generator.impl.MapSrcProcessor.MapSrcMacroProcessorInterpreted; import jetbrains.mps.generator.impl.RoleValidation.RoleValidator; import jetbrains.mps.generator.impl.RoleValidation.Status; import jetbrains.mps.generator.impl.interpreted.TemplateCall; import jetbrains.mps.generator.impl.query.GeneratorQueryProvider; import jetbrains.mps.generator.impl.query.IfMacroCondition; import jetbrains.mps.generator.impl.query.InsertMacroQuery; import jetbrains.mps.generator.impl.query.MapNodeQuery; import jetbrains.mps.generator.impl.query.MapPostProcessor; import jetbrains.mps.generator.impl.query.QueryKeyImpl; import jetbrains.mps.generator.impl.query.QueryProviderBase; import jetbrains.mps.generator.impl.query.SourceNodeQuery; import jetbrains.mps.generator.impl.query.SourceNodesQuery; import jetbrains.mps.generator.impl.query.VariableValueQuery; import jetbrains.mps.generator.impl.query.WeaveAnchorQuery; import jetbrains.mps.generator.impl.template.QueryExecutor; import jetbrains.mps.generator.runtime.GenerationException; import jetbrains.mps.generator.runtime.TemplateContext; import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment; import jetbrains.mps.generator.runtime.TemplateSwitchMapping; import jetbrains.mps.generator.runtime.WeavingWithAnchor; import jetbrains.mps.generator.template.ITemplateProcessor; import jetbrains.mps.generator.template.IfMacroContext; import jetbrains.mps.generator.template.InsertMacroContext; import jetbrains.mps.generator.template.SourceSubstituteMacroNodeContext; import jetbrains.mps.generator.template.SourceSubstituteMacroNodesContext; import jetbrains.mps.generator.template.TemplateVarContext; import jetbrains.mps.generator.template.WeavingAnchorContext; import jetbrains.mps.smodel.NodeReadEventsCaster; import jetbrains.mps.smodel.SNodePointer; import jetbrains.mps.textgen.trace.TracingUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Applies template to input node. * Generally, there's single template processor per generation step as it keeps runtime presentation of template model being applied. * Rule that need to apply template might access instance of {@link jetbrains.mps.generator.impl.TemplateProcessor} via * {@link jetbrains.mps.generator.runtime.TemplateExecutionEnvironment#getTemplateProcessor()} * TODO make MacroNode aware of container TemplateNode and don't pass both arguments when only one is sufficient */ public final class TemplateProcessor implements ITemplateProcessor { private final TemplateGenerator myGenerator; private final SModel myOutputModel; private final MacroImplFactory myImplFactory; private final Map<SNode, TemplateNode> myTemplateRuntimeMap = new ConcurrentHashMap<SNode, TemplateNode>(); public TemplateProcessor(@NotNull TemplateGenerator generator) { myGenerator = generator; myOutputModel = myGenerator.getOutputModel(); myImplFactory = new MacroImplFactory(this); } /*package*/ TemplateGenerator getGenerator() { return myGenerator; } /*package*/ GeneratorQueryProvider getQueryProvider(SNodeReference templateNode) { return myGenerator.getQueryProvider(templateNode); } @Override @NotNull public List<SNode> apply(@NotNull SNode templateNode, @NotNull TemplateContext context) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { if (myGenerator.isIncremental()) { // turn off tracing NodeReadEventsCaster.setNodesReadListener(null); } try { final TemplateNode rtTemplateNode = getTemplateNodeRuntime(templateNode); if (rtTemplateNode.getFirstMacro() != null) { return applyMacro(rtTemplateNode.getFirstMacro(), context); } else { return applyTemplate(rtTemplateNode, context); } } finally { if (myGenerator.isIncremental()) { // restore tracing NodeReadEventsCaster.removeNodesReadListener(); } } } @NotNull /*package*/List<SNode> applyMacro(@NotNull MacroNode rtMacro, @NotNull TemplateContext context) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { return rtMacro.apply(context.subContext(rtMacro.getMappingLabel())); } @NotNull /*package*/List<SNode> applyTemplate(@NotNull TemplateNode rtTemplateNode, @NotNull TemplateContext context) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { final TemplateExecutionEnvironment env = context.getEnvironment(); SNode outputNode = env.createOutputNode(rtTemplateNode.getConcept()); // use same env method as reduce_TemplateNode does // XXX reduce_TemplateNode looks into incoming references now, not to save template node id if there are no // references inside template model (and hence no attempt to restore the reference using template node id) // Would be great to do smth similar here env.nodeCopied(context, outputNode, rtTemplateNode.getTemplateNodeId()); env.registerLabel(context.getInput(), outputNode, context.getInputName()); // XXX reduce_TemplateNode doesn't do that rtTemplateNode.apply(context, outputNode); // process children context = context.subContext(); // drop label for (SNode templateChildNode : rtTemplateNode.getChildTemplates()) { TemplateNode rtTemplateChildNode = getTemplateNodeRuntime(templateChildNode); final List<SNode> outputChildNodes; if(rtTemplateChildNode.getFirstMacro() != null) { outputChildNodes = applyMacro(rtTemplateChildNode.getFirstMacro(), context); } else { outputChildNodes = applyTemplate(rtTemplateChildNode, context); } SContainmentLink role = rtTemplateChildNode.getRoleInParent(); RoleValidator validator = myGenerator.getChildRoleValidator(outputNode, role); for (SNode outputChildNode : outputChildNodes) { // check child Status status = validator.validate(outputChildNode); if (status != null) { myGenerator.getLogger().warning(rtTemplateChildNode.getTemplateNodeReference(), status.getMessage("apply template"), status.describe( GeneratorUtil.describe(context.getInput(), "input"), GeneratorUtil.describe(outputNode, "output"), GeneratorUtil.describe(rtTemplateNode.getTemplateNodeReference(), "template node") )); } outputNode.addChild(role, outputChildNode); } } return Collections.singletonList(outputNode); } private TemplateNode getTemplateNodeRuntime(SNode templateNode) { // template nodes may belong to different models, hence can't use anything simpler than and identity that includes model // and since template models don't change during generation, Object identity is enough as a key. @SuppressWarnings("redundant") final SNode key = templateNode; TemplateNode rv = myTemplateRuntimeMap.get(key); if (rv == null) { rv = new TemplateNode(templateNode, myImplFactory); myTemplateRuntimeMap.put(key, rv); } return rv; } private static class MacroImplFactory implements MacroNode.Factory { private final TemplateProcessor myTemplateProcessor; private final Map<SConcept, Integer> macroImplMap = new HashMap<SConcept, Integer>(32); MacroImplFactory(@NotNull TemplateProcessor templateProcessor) { myTemplateProcessor = templateProcessor; macroImplMap.put(RuleUtil.concept_NodeMacro, 0); macroImplMap.put(RuleUtil.concept_LoopMacro, 1); macroImplMap.put(RuleUtil.concept_CopySrcNodeMacro, 2); macroImplMap.put(RuleUtil.concept_CopySrcListMacro, 3); macroImplMap.put(RuleUtil.concept_InsertMacro, 4); macroImplMap.put(RuleUtil.concept_WeaveMacro, 5); macroImplMap.put(RuleUtil.concept_LabelMacro, 6); macroImplMap.put(RuleUtil.concept_VarMacro, 7); macroImplMap.put(RuleUtil.concept_IfMacro, 8); macroImplMap.put(RuleUtil.concept_MapSrcNodeMacro, 9); macroImplMap.put(RuleUtil.concept_MapSrcListMacro, 10); macroImplMap.put(RuleUtil.concept_TemplateSwitchMacro, 12); macroImplMap.put(RuleUtil.concept_IncludeMacro, 13); macroImplMap.put(RuleUtil.concept_TemplateCallMacro, 14); macroImplMap.put(RuleUtil.concept_TraceMacro, 15); macroImplMap.put(RuleUtil.concept_ExportMacro, 16); } @Override public MacroNode create(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next) { Integer k = macroImplMap.get(macro.getConcept()); if (k == null) { return new NoMacro(macro, templateNode, next, myTemplateProcessor); } switch(k) { case 0 : return new NoMacro(macro, templateNode, next, myTemplateProcessor); case 1 : return new LoopMacro(macro, templateNode, next, myTemplateProcessor); case 2 : return new CopySrcMacros(macro, templateNode, next, myTemplateProcessor, true); case 3 : return new CopySrcMacros(macro, templateNode, next, myTemplateProcessor, false); case 4 : return new InsertMacro(macro, templateNode, next, myTemplateProcessor); case 5 : return new WeaveMacro(macro, templateNode, next, myTemplateProcessor); case 6 : return new LabelMacro(macro, templateNode, next, myTemplateProcessor); case 7 : return new VarMacro(macro, templateNode, next, myTemplateProcessor); case 8 : return new IfMacro(macro, templateNode, next, myTemplateProcessor); case 9 : return new MapSrcMacros(macro, templateNode, next, myTemplateProcessor, true); case 10 : return new MapSrcMacros(macro, templateNode, next, myTemplateProcessor, false); case 12 : return new SwitchMacro(macro, templateNode, next, myTemplateProcessor); case 13 : return new IncludeMacro(macro, templateNode, next, myTemplateProcessor); case 14 : return new CallMacro(macro, templateNode, next, myTemplateProcessor); case 15 : return new TraceMacro(macro, templateNode, next, myTemplateProcessor); case 16 : return new ExportMacro(macro, templateNode, next, myTemplateProcessor); default: return new NoMacro(macro, templateNode, next, myTemplateProcessor); } } } /** * Record value with name */ private static class ExportMacro extends MacroImpl { public ExportMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { final List<SNode> value = nextMacro(templateContext); final GenerationSessionContext ctx = myTemplateProcessor.getGenerator().getGeneratorSessionContext(); ctx.getExports().record(templateContext, macro.getReferenceTarget("label"), value); return value; } } private static abstract class MacroImpl implements MacroNode { protected final TemplateProcessor myTemplateProcessor; // XXX now, with macro and tn as fields, perhaps shall pass TP as an argument to apply? protected final SNode macro; protected final TemplateNode templateNode; private final SNodeReference myMacroNodeRef; private final String myMappingLabel; private final MacroNode myNextMacro; protected MacroImpl(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { this.macro = macro; this.templateNode = templateNode; myNextMacro = next; myTemplateProcessor = templateProcessor; myMacroNodeRef = new SNodePointer(macro); myMappingLabel = GeneratorUtilEx.getMappingName_NodeMacro(macro, null); } @Override public final MacroNode getNextMacro() { return myNextMacro; } @NotNull @Override public final SNodeReference getMacroNodeRef() { return myMacroNodeRef; } @Nullable @Override public final String getMappingLabel() { // instead of this accessor may implement #apply here, and delegate to another method in subclass with updated context return myMappingLabel; } protected final List<SNode> nextMacro(TemplateContext context) throws GenerationFailureException, DismissTopMappingRuleException, GenerationCanceledException { if (getNextMacro() != null) { return myTemplateProcessor.applyMacro(getNextMacro(), context); } else { return myTemplateProcessor.applyTemplate(templateNode, context); } } protected final IGeneratorLogger getLogger() { return myTemplateProcessor.getGenerator().getLogger(); } } // could be standalone facility, not an element in class hierarchy private static abstract class MacroWithInput extends MacroImpl { private volatile SourceNodeQuery mySourceNodeQuery; private volatile SourceNodesQuery mySourceNodesQuery; protected MacroWithInput(SNode macro, TemplateNode templateNode, MacroNode next, TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } protected final SNode getNewInputNode(@NotNull TemplateContext context) throws GenerationFailureException { SourceNodeQuery q = mySourceNodeQuery; if (q == null) { synchronized (this) { if ((q = mySourceNodeQuery) == null) { q = mySourceNodeQuery = createNodeQuery(); } } } final QueryExecutor qe = context.getEnvironment().getQueryExecutor(); return qe.evaluate(q, new SourceSubstituteMacroNodeContext(context, getMacroNodeRef())); } protected final Collection<SNode> getNewInputNodes(@NotNull TemplateContext context) throws GenerationFailureException { SourceNodesQuery q = mySourceNodesQuery; if (q == null) { synchronized (this) { if ((q = mySourceNodesQuery) == null) { q = mySourceNodesQuery = createNodesQuery(); } } } if (q == null) { getLogger().error(getMacroNodeRef(), "couldn't get input nodes", GeneratorUtil.describeInput(context)); throw new GenerationFailureException("couldn't get input nodes"); } final QueryExecutor qe = context.getEnvironment().getQueryExecutor(); final Collection<SNode> result = qe.evaluate(q, new SourceSubstituteMacroNodesContext(context, getMacroNodeRef())); checkInputNodesForNulls(context, result); return result; } static <T> List<T> wrapAsList(T node) { return node == null ? Collections.<T>emptyList() : Collections.singletonList(node); } private void checkInputNodesForNulls(TemplateContext context, Iterable<SNode> result) throws GenerationFailureException { for (SNode n : result) { if (n == null) { final String msg = String.format("Unexpected null value among new input nodes in %s macro", macro.getPresentation()); context.getEnvironment().getLogger().error(getMacroNodeRef(), msg, GeneratorUtil.describeInput(context)); throw new GenerationFailureException(msg); } } } private SourceNodeQuery createNodeQuery() { SNode query = RuleUtil.getSourceNodeQuery(macro); if (query != null) { return myTemplateProcessor.getQueryProvider(getMacroNodeRef()).getSourceNodeQuery(new QueryKeyImpl(getMacroNodeRef(), query.getNodeId(), query)); } else { // <default> : propagate current input node return new SourceNodeQuery() { @Nullable @Override public SNode evaluate(@NotNull SourceSubstituteMacroNodeContext context) throws GenerationFailureException { return context.getInputNode(); } }; } } // return null iff there's no sourceNodesQuery but macro does require input private SourceNodesQuery createNodesQuery() { SNode nodesQuery = RuleUtil.getSourceNodesQuery(macro); if (nodesQuery != null) { return myTemplateProcessor.getQueryProvider(getMacroNodeRef()).getSourceNodesQuery(new QueryKeyImpl(getMacroNodeRef(), nodesQuery.getNodeId(), nodesQuery)); } if (RuleUtil.doesMacroRequireInput(macro)) { return null; } // <default> : propagate current input node return new SourceNodesQuery() { @NotNull @Override public Collection<SNode> evaluate(@NotNull SourceSubstituteMacroNodesContext context) throws GenerationFailureException { return wrapAsList(context.getInputNode()); } }; } } // $LOOP$ private static class LoopMacro extends MacroWithInput { LoopMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { Collection<SNode> newInputNodes = getNewInputNodes(templateContext); if (newInputNodes.isEmpty()) { return Collections.emptyList(); } final GenerationTrace trace = templateContext.getEnvironment().getTrace(); String counterVarName = RuleUtil.getLoopMacroCounterVarName(macro); ArrayList<SNode> outputNodes = new ArrayList<SNode>(); int i = 0; for (SNode newInputNode : newInputNodes) { TemplateContext ctx = templateContext; if (counterVarName != null) { ctx = ctx.withVariable("cv:" + counterVarName, i); } ctx = ctx.subContext(newInputNode); List<SNode> _outputNodes = nextMacro(ctx); outputNodes.addAll(_outputNodes); i++; trace.trace(newInputNode.getNodeId(), GenerationTracerUtil.translateOutput(_outputNodes), getMacroNodeRef()); } return outputNodes; } } // $COPY-SRC$ / $COPY-SRCL$ private static class CopySrcMacros extends MacroWithInput { private final boolean myIsSoleInput; protected CopySrcMacros(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor, boolean soleInput) { super(macro, templateNode, next, templateProcessor); myIsSoleInput = soleInput; } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { final Collection<SNode> newInputNodes; if (myIsSoleInput) { newInputNodes = wrapAsList(getNewInputNode(templateContext)); } else { newInputNodes = getNewInputNodes(templateContext); } SNodeReference templateNodeRef = templateNode.getTemplateNodeReference(); String tempNodeId = templateNode.getTemplateNodeId(); return templateContext.getEnvironment().copyNodes(newInputNodes, templateNodeRef, tempNodeId, templateContext); } } // $INSERT$ private static class InsertMacro extends MacroImpl { private final InsertMacroQuery myQuery; protected InsertMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); QueryKeyImpl qk = new QueryKeyImpl(getMacroNodeRef(), RuleUtil.getInsertMacro_Query(macro).getNodeId()); myQuery = templateProcessor.getQueryProvider(getMacroNodeRef()).getInsertMacroQuery(qk); } private SNode getNodeToInsert(TemplateContext context) throws GenerationFailureException { return context.getEnvironment().getQueryExecutor().evaluate(myQuery, new InsertMacroContext(context, getMacroNodeRef())); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { SNode child = getNodeToInsert(templateContext); if (child != null) { child = templateContext.getEnvironment().insertNode(child, getMacroNodeRef(), templateContext); // XXX TEEI.insertNode doesn't register ML, perhaps shall behave the same as this code? Or it's done in generated code? // label myTemplateProcessor.getGenerator().registerMappingLabel(templateContext.getInput(), templateContext.getInputName(), child); return Collections.singletonList(child); } return Collections.emptyList(); } } // $WEAVE$ private static class WeaveMacro extends MacroWithInput implements WeavingWithAnchor { private WeaveAnchorQuery myAnchorQuery; protected WeaveMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { List<SNode> _outputNodes = nextMacro(templateContext); if (_outputNodes.isEmpty()) { return Collections.emptyList(); } if (_outputNodes.size() > 1) { getLogger().error(macro.getReference(), "cannot apply $WEAVE$ to a list of nodes", GeneratorUtil.describe(templateContext.getInput(), "input")); return _outputNodes; } SNode consequence = RuleUtil.getWeaveMacro_Consequence(macro); if (consequence == null) { getLogger().error(macro.getReference(), "couldn't evaluate weave macro: no consequence", GeneratorUtil.describeIfExists(templateContext.getInput(), "input")); return _outputNodes; } SNode template = RuleUtil.getTemplateDeclarationReference_Template(consequence); //// if (template == null) { getLogger().error(macro.getReference(), "couldn't evaluate weave macro: no template", GeneratorUtil.describeIfExists(templateContext.getInput(), "input")); return _outputNodes; } WeaveTemplateContainer wtc = new WeaveTemplateContainer(template, this); wtc.initialize(getLogger()); SNode contextNode = _outputNodes.get(0); for (SNode node : getNewInputNodes(templateContext)) { wtc.apply(contextNode, templateContext.subContext(node)); } return _outputNodes; } @Nullable @Override public SNode getAnchorNode(@NotNull TemplateContext context, @NotNull SNode outputParent, @NotNull SNode outputNode) throws GenerationFailureException { if (myAnchorQuery == null) { myAnchorQuery = context.getEnvironment().getQueryProvider(getMacroNodeRef()).getWeaveAnchorQuery(macro); } return myAnchorQuery.anchorNode(new WeavingAnchorContext(context, getMacroNodeRef(), outputParent, outputNode)); } } // $LABEL$ private static class LabelMacro extends MacroImpl { protected LabelMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { return nextMacro(templateContext); } } // $VAR$ private static class VarMacro extends MacroImpl { private final VariableValueQuery myValueQuery; protected VarMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); QueryKeyImpl qk = new QueryKeyImpl(getMacroNodeRef(), RuleUtil.getVarMacro_Query(macro).getNodeId()); myValueQuery = templateProcessor.getQueryProvider(getMacroNodeRef()).getVariableValueQuery(qk); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { String varName = RuleUtil.getVarMacro_Name(macro); Object varValue = templateContext.getEnvironment().getQueryExecutor().evaluate(myValueQuery, new TemplateVarContext(templateContext, getMacroNodeRef())); TemplateContext newContext = templateContext.subContext(Collections.singletonMap(varName, varValue)); // tc.subContext(Map props) doesn't save mapping label, so "LABEL aaa VAR bb <templateNode>" fails to // establish mapping aaa:templateNode. However, instead of passing ML here once again, shall consider updating subContext(Map) // contract to preserve mapping label. Can't do it without thorough check of the method usage in generated templates return nextMacro(newContext.subContext(templateContext.getInputName())); } } // $IF$ private static class IfMacro extends MacroImpl { private final IfMacroCondition myCondition; private final RuleConsequenceProcessor myAlternativeConsequence; protected IfMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); SNode alternativeConsequence = RuleUtil.getIfMacro_AlternativeConsequence(macro); myAlternativeConsequence = alternativeConsequence == null ? null : RuleConsequenceProcessor.prepare(alternativeConsequence); SNode function = RuleUtil.getIfMacro_ConditionFunction(macro); if (function != null) { myCondition = templateProcessor.getQueryProvider(getMacroNodeRef()).getIfMacroCondition(new QueryKeyImpl(getMacroNodeRef(), function.getNodeId(), macro)); } else { myCondition = new QueryProviderBase.Missing(macro); } } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext context) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { final QueryExecutor queryExecutor = context.getEnvironment().getQueryExecutor(); if (queryExecutor.evaluate(myCondition, new IfMacroContext(context, getMacroNodeRef()))) { return nextMacro(context); } else { // alternative consequence if (myAlternativeConsequence == null) { return Collections.emptyList(); } try { return myAlternativeConsequence.processRuleConsequence(context); } catch (AbandonRuleInputException ex) { // it's ok. just ignore return Collections.emptyList(); } } } } // $MAP-SRC$ or $MAP-SRCL$ private static class MapSrcMacros extends MacroWithInput { private final boolean myIsSoleInput; protected MapSrcMacros(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor, boolean soleInput) { super(macro, templateNode, next, templateProcessor); myIsSoleInput = soleInput; } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { final Collection<SNode> newInputNodes; if (myIsSoleInput) { newInputNodes = wrapAsList(getNewInputNode(templateContext)); } else { newInputNodes = getNewInputNodes(templateContext); } if (newInputNodes.isEmpty()) { return Collections.emptyList(); } GeneratorQueryProvider queryProvider = myTemplateProcessor.getQueryProvider(getMacroNodeRef()); SNode mf = RuleUtil.getMapSrc_MapperFunction(macro); SNode ppf = RuleUtil.getMapSrc_PostMapperFunction(macro); MapNodeQuery mapNodeQuery = mf == null ? null : queryProvider.getMapNodeQuery(new QueryKeyImpl(getMacroNodeRef(), mf.getNodeId())); MapPostProcessor postProcessor = ppf == null ? null : queryProvider.getMapPostProcessor(new QueryKeyImpl(getMacroNodeRef(), ppf.getNodeId())); // it's perfectly legal to have neither mapNodeQuery nor postProcessor final TemplateExecutionEnvironment env = templateContext.getEnvironment(); ArrayList<SNode> outputNodes = new ArrayList<SNode>(newInputNodes.size()); final DelayedChanges delayedChanges = myTemplateProcessor.getGenerator().getDelayedChanges(); for (SNode newInputNode : newInputNodes) { TemplateContext newcontext = templateContext.subContext(newInputNode); if (mapNodeQuery != null) { SNode childToReplaceLater = env.createOutputNode(templateNode.getConcept()); outputNodes.add(childToReplaceLater); // execute the 'mapper' function later delayedChanges.add(new MapSrcMacroProcessorInterpreted(mapNodeQuery, postProcessor, getMacroNodeRef(), childToReplaceLater, newcontext)); } else { List<SNode> _outputNodes = nextMacro(newcontext); outputNodes.addAll(_outputNodes); if (postProcessor != null) { for (SNode outputNode : _outputNodes) { delayedChanges.add(new MapSrcMacroProcessorInterpreted(postProcessor, getMacroNodeRef(), outputNode, newcontext)); } } } } return outputNodes; } } // $SWITCH$ private static class SwitchMacro extends MacroWithInput { private volatile TemplateCall myCallProcessor; protected SwitchMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } protected SNode getTemplateSwitch() { return RuleUtil.getTemplateSwitchMacro_TemplateSwitch(macro); } protected TemplateContext prepareContext(TemplateContext templateContext) throws GenerationFailureException { TemplateCall tc = myCallProcessor; if (tc == null) { tc = new TemplateCall(macro); if (tc.argumentsMismatch()) { getLogger().error(getMacroNodeRef(), "number of arguments doesn't match template", GeneratorUtil.describeInput(templateContext)); // fall-through } myCallProcessor = tc; } return tc.prepareCallContext(templateContext); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { SNode templateSwitch = getTemplateSwitch(); if (templateSwitch == null) { throw new TemplateProcessingFailureException(macro, "error processing $SWITCH$ - bad TemplateSwitch reference", GeneratorUtil.describeInput(templateContext)); } final SNodeReference switchPtr = templateSwitch.getReference(); SNode newInputNode = getNewInputNode(templateContext); if (newInputNode == null) { TemplateSwitchMapping tswitch = myTemplateProcessor.getGenerator().getSwitch(switchPtr); if (tswitch != null) { tswitch.processNull(templateContext.getEnvironment(), switchPtr, templateContext); } return Collections.emptyList(); // skip template } final TemplateContext switchContext = prepareContext(templateContext).subContext(newInputNode); Collection<SNode> collection = null; try { collection = templateContext.getEnvironment().trySwitch(switchPtr, switchContext); } catch (GenerationCanceledException | GenerationFailureException | DismissTopMappingRuleException e) { throw e; } catch (GenerationException e) { getLogger().error(switchPtr, "internal error in switch: " + e.toString(), GeneratorUtil.describe(macro, "macro")); } if (collection == null) { // XXX why tryDefault is part of trySwitch, and not here? For the sake of generated code, perhaps (not to generate conditions 'if nothing generated')? // no switch-case found for the inputNode - continue with templateNode under the $switch$ // use initial context, not the one prepared (could be filled with switch arguments) collection = nextMacro(templateContext.subContext(newInputNode)); } return new ArrayList<SNode>(collection); } } // subclass is responsible to initialize myInvokedTemplate field private static abstract class InvokeTemplateMacro extends MacroWithInput { private final String myName; protected SNode myInvokedTemplate; private volatile TemplateContainer myTemplates; protected InvokeTemplateMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor, String macroName) { super(macro, templateNode, next, templateProcessor); myName = macroName; } protected abstract TemplateContext prepareContext(TemplateContext templateContext) throws GenerationFailureException; private TemplateContainer getTemplates() { TemplateContainer rv = myTemplates; if (rv == null) { synchronized (this) { if ((rv = myTemplates) == null) { rv = new TemplateContainer(myInvokedTemplate); myTemplates = rv; } } } return rv; } @NotNull @Override public final List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { SNode newInputNode = getNewInputNode(templateContext); if (newInputNode == null) { return Collections.emptyList(); // skip template } SNode invokedTemplate = myInvokedTemplate; if (invokedTemplate == null) { throw new TemplateProcessingFailureException(macro, String.format("error processing %s : no template to invoke", myName)); } TemplateContext newcontext = prepareContext(templateContext).subContext(newInputNode); if (newcontext == null) { throw new TemplateProcessingFailureException(macro, String.format("error processing %s : failed to prepare new context", myName), GeneratorUtil.describe(invokedTemplate, "invoked template")); } final TemplateContainer tc = getTemplates(); final List<SNode> rv = tc.processRuleConsequence(newcontext); templateContext.getEnvironment().getTrace().trace(newInputNode.getNodeId(), GenerationTracerUtil.translateOutput(rv), getMacroNodeRef()); return rv; } } // $INCLUDE$ private static class IncludeMacro extends InvokeTemplateMacro { protected IncludeMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor, "$INCLUDE$"); myInvokedTemplate = RuleUtil.getIncludeMacro_Template(macro); } @Override protected TemplateContext prepareContext(TemplateContext templateContext) throws GenerationFailureException { final String[] parameterNames = RuleUtil.getTemplateDeclarationParameterNames(myInvokedTemplate); if (parameterNames == null) { getLogger().error(getMacroNodeRef(), "error processing $INCLUDE$: target template is broken", GeneratorUtil.describeInput(templateContext)); return null; } for (String name : parameterNames) { if (!templateContext.hasVariable(name)) { getLogger().error(getMacroNodeRef(), String.format("error processing $INCLUDE$: parameter '%s' is missing", name), GeneratorUtil.describeInput(templateContext)); } } return templateContext; } } // $CALL$ private static class CallMacro extends InvokeTemplateMacro { private volatile TemplateCall myCallProcessor; protected CallMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor, "$CALL$"); myInvokedTemplate = RuleUtil.getCallMacro_Template(macro); } @Override protected TemplateContext prepareContext(TemplateContext templateContext) throws GenerationFailureException { TemplateCall tc = myCallProcessor; if (tc == null) { tc = new TemplateCall(macro); if (tc.argumentsMismatch()) { getLogger().error(getMacroNodeRef(), "number of arguments doesn't match template", GeneratorUtil.describeInput(templateContext)); // fall-through } myCallProcessor = tc; } return tc.prepareCallContext(templateContext); } } // $TRACE$ private static class TraceMacro extends MacroWithInput { protected TraceMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { List<SNode> _outputNodes = nextMacro(templateContext); if (!_outputNodes.isEmpty()) { SNode inputNode = getNewInputNode(templateContext); if (inputNode != null) { for (SNode outputNode : _outputNodes) { TracingUtil.fillOriginalNode(inputNode, outputNode, false); } } } return _outputNodes; } } // $$ private static class NoMacro extends MacroWithInput { protected NoMacro(@NotNull SNode macro, @NotNull TemplateNode templateNode, @Nullable MacroNode next, @NotNull TemplateProcessor templateProcessor) { super(macro, templateNode, next, templateProcessor); } @NotNull @Override public List<SNode> apply(@NotNull TemplateContext templateContext) throws DismissTopMappingRuleException, GenerationFailureException, GenerationCanceledException { Collection<SNode> newInputNodes = getNewInputNodes(templateContext); if (newInputNodes.isEmpty()) { return Collections.emptyList(); } ArrayList<SNode> outputNodes = new ArrayList<SNode>(); for (SNode newInputNode : newInputNodes) { List<SNode> _outputNodes = nextMacro(templateContext.subContext(newInputNode)); outputNodes.addAll(_outputNodes); } return outputNodes; } } }