/* * 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.text.impl; import jetbrains.mps.logging.Log4jUtil; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.text.BasicToken; import jetbrains.mps.text.TextArea; import jetbrains.mps.text.TextAreaToken; import jetbrains.mps.text.TextMark; import jetbrains.mps.text.rt.TextGenContext; import jetbrains.mps.textgen.trace.ScopePositionInfo; import jetbrains.mps.textgen.trace.TraceablePositionInfo; import jetbrains.mps.textgen.trace.UnitPositionInfo; import jetbrains.mps.util.SNodeOperations; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNode; import java.util.List; /** * Facility to keep implementation methods I don't want to get exposed in TextGenDescriptorBase or elsewhere deemed API. * Besides it helps to keep state of the {@link jetbrains.mps.text.rt.TextGenDescriptor#generateText(TextGenContext)} invocation * context and to shorten argument list of utility methods. * Generated code shall use this class to perform various operations that are not straightforward enough to get generated * right into <code>TextGenDescriptor</code>. * @author Artem Tikhomirov */ public final class TextGenSupport implements TextArea { private final TextGenContext myContext; private final TraceInfoCollector myTraceInfoCollector; public TextGenSupport(@NotNull TextGenContext context) { myContext = context; myTraceInfoCollector = ((TextGenTransitionContext) context).getTextUnitContextObject(TraceInfoCollector.class.getName(), TraceInfoCollector.class); } public boolean needPositions() { return getTraceInfoCollector() != null; } public void createPositionInfo() { if (needPositions()) { myContext.getBuffer().pushMark(); } } public void createScopeInfo( ) { if (needPositions()) { myContext.getBuffer().pushMark(); } } public void createUnitInfo() { if (needPositions()) { myContext.getBuffer().pushMark(); } } public void fillPositionInfo(String propertyString) { final TraceInfoCollector tic = getTraceInfoCollector(); if (tic == null) { return; } TextMark m = myContext.getBuffer().popMark(); final TraceablePositionInfo pi = tic.createTracePosition(m, myContext.getPrimaryInput()); pi.setPropertyString(propertyString); } public void fillScopeInfo(List<SNode> vars) { final TraceInfoCollector tic = getTraceInfoCollector(); if (tic == null) { return; } TextMark m = myContext.getBuffer().popMark(); final ScopePositionInfo pi = tic.createScopePosition(m, myContext.getPrimaryInput()); for (SNode var : vars) { if (var != null) { pi.addVarInfo(var); } } } public void fillUnitInfo(String unitName) { final TraceInfoCollector tic = getTraceInfoCollector(); if (tic == null) { return; } TextMark m = myContext.getBuffer().popMark(); final UnitPositionInfo pi = tic.createUnitPosition(m, myContext.getPrimaryInput()); pi.setUnitName(unitName); warnIfUnitNameInvalid(unitName, myContext.getPrimaryInput()); } private TraceInfoCollector getTraceInfoCollector() { return myTraceInfoCollector; } public void appendAttributedNode() { final SNode currentNode = myContext.getPrimaryInput(); if (!SNodeUtil.link_BaseConcept_smodelAttribute.equals(currentNode.getContainmentLink())) { throw new IllegalStateException("Attempt to reference attributed node from a non-attribute node"); } final SNode attributeParent = currentNode.getParent(); assert attributeParent != null; // just to get rid of idea warning, getContainmentLink.notNull above guaranties that // if no more attributes with TextGen present left, just do the attributed node itself SNode attributedNode = attributeParent; boolean found = false; for (SNode attribute : attributeParent.getChildren(SNodeUtil.link_BaseConcept_smodelAttribute)) { if (attribute == currentNode) { found = true; break; } if (TextGenRegistry.getInstance().hasTextGen(attribute)) { attributedNode = attribute; } } assert found; // shall we process attribute of an attribute? // Right now we do not. Unlike appendNode(), doAppendNode doesn't look for attributes of a node being appended doAppendNode(attributedNode); } public void appendNode(@Nullable SNode node) { if (node == null) { append("???"); reportError("possible broken reference"); return; } // start with last attribute with textgen, if any SNode n = node; for (SNode attribute : node.getChildren(SNodeUtil.link_BaseConcept_smodelAttribute)) { if (TextGenRegistry.getInstance().hasTextGen(attribute)) { n = attribute; } } doAppendNode(n); } private void doAppendNode(SNode node) { ((TextGenTransitionContext) myContext).generateText(node); } // FIXME copy of SNodeTextGen.foundError() public void reportError(String info) { String message = info != null ? "textgen error: '" + info + "' in " + SNodeOperations.getDebugText(myContext.getPrimaryInput()) : "textgen error in " + SNodeOperations.getDebugText(myContext.getPrimaryInput()); getTransitionContext().foundError(message, myContext.getPrimaryInput(), null); } private TextGenTransitionContext getTransitionContext() { return ((TextGenTransitionContext) myContext); } private void warnIfUnitNameInvalid(String unitName, SNode node) { String modelName = node.getModel().getName().getLongName(); if (!(unitName.startsWith(modelName))) { final String msg = String.format("Unit name has to start with model fqName. Fix %s in %s.", unitName, modelName); LogManager.getLogger(getClass()).warn(Log4jUtil.createMessageObject(msg, node)); } else if (unitName.length() <= modelName.length() + 1 || unitName.charAt(modelName.length())!= '.' || unitName.indexOf('.', modelName.length()+1) != -1) { String msg = String.format("Unit name has to match \"modelFqName.shortUnitName\" where short unit name does not contain dots. Fix %s in %s", unitName, modelName); LogManager.getLogger(getClass()).warn(Log4jUtil.createMessageObject(msg, node)); } } /** * Mechanism to access context object instance in a typed manner. */ public <T> T getContextObject(String identity, Class<T> kind) { return getTransitionContext().getTextUnitContextObject(identity, kind); } /** * Similar to {@link jetbrains.mps.text.TextBuffer#pushTextArea(TextAreaToken)}, except that * lang.textgen.LayoutPart -> TextAreaToken conversion is hidden here. * This is provisional code, to get some freedom to change implementation before the new TextGen API settles down */ public void pushTextArea(@NotNull Object textAreaIdentity) { myContext.getBuffer().pushTextArea(new BasicToken(textAreaIdentity)); } public void popTextArea() { myContext.getBuffer().popTextArea(); } //////////// // TextArea. Simply delegate to whatever actual text area of the buffer is. @Override public TextArea append(CharSequence text) { return myContext.getBuffer().area().append(text); } @Override public TextArea newLine() { return myContext.getBuffer().area().newLine(); } @Override public TextArea indent() { return myContext.getBuffer().area().indent(); } @Override public TextArea increaseIndent() { return myContext.getBuffer().area().increaseIndent(); } @Override public TextArea decreaseIndent() { return myContext.getBuffer().area().decreaseIndent(); } @Override public int length() { return myContext.getBuffer().area().length(); } }