| /*=============================================================================# |
| # Copyright (c) 2014, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.redocs.r.core.model; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Region; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.string.InternStringCache; |
| import org.eclipse.statet.jcommons.text.core.BasicTextRegion; |
| import org.eclipse.statet.jcommons.text.core.TextLineInformation; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| import org.eclipse.statet.jcommons.text.core.input.StringParserInput; |
| |
| import org.eclipse.statet.ltk.ast.core.AstInfo; |
| import org.eclipse.statet.ltk.ast.core.AstNode; |
| import org.eclipse.statet.ltk.ast.core.EmbeddingAstNode; |
| import org.eclipse.statet.ltk.core.SourceContent; |
| import org.eclipse.statet.ltk.issues.core.ProblemRequestor; |
| import org.eclipse.statet.ltk.model.core.build.EmbeddingForeignReconcileTask; |
| import org.eclipse.statet.ltk.model.core.build.SourceUnitModelContainer; |
| import org.eclipse.statet.ltk.model.core.element.SourceUnitModelInfo; |
| import org.eclipse.statet.r.core.model.RCompositeSourceElement; |
| import org.eclipse.statet.r.core.model.RElementName; |
| import org.eclipse.statet.r.core.model.RLangSourceElement; |
| import org.eclipse.statet.r.core.model.RModel; |
| import org.eclipse.statet.r.core.model.RModelManager; |
| import org.eclipse.statet.r.core.model.RSourceUnit; |
| import org.eclipse.statet.r.core.model.RSourceUnitModelInfo; |
| import org.eclipse.statet.r.core.model.build.RProblemReporter; |
| import org.eclipse.statet.r.core.rsource.ast.FCall.Arg; |
| import org.eclipse.statet.r.core.rsource.ast.FCall.Args; |
| import org.eclipse.statet.r.core.rsource.ast.NodeType; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| import org.eclipse.statet.r.core.rsource.ast.RParser; |
| import org.eclipse.statet.r.core.rsource.ast.SourceComponent; |
| |
| |
| @NonNullByDefault |
| public class RChunkReconciler< |
| TSourceUnit extends RSourceUnit, |
| TModelInfo extends SourceUnitModelInfo, |
| TModelContainer extends SourceUnitModelContainer<TSourceUnit, TModelInfo>> { |
| |
| |
| private final String name; |
| |
| private final RModelManager rManager; |
| |
| private final Matcher raChunkStartLineMatcher; |
| private final @Nullable Matcher raChunkRefLineMatcher; |
| private final Matcher raChunkEndLineMatcher; |
| |
| private final RParser raParser= new RParser(AstInfo.LEVEL_MODEL_DEFAULT, new InternStringCache(0x20)); |
| private final StringParserInput raInput= new StringParserInput(0x1000); |
| |
| protected final RProblemReporter rpReporter; |
| |
| |
| public RChunkReconciler(final String name, final Pattern chunkStartLinePattern, |
| final Pattern chunkRefLinePattern, final Pattern chunkEndLinePattern) { |
| this.name= name; |
| this.rManager= RModel.getRModelManager(); |
| |
| this.raChunkStartLineMatcher= chunkStartLinePattern.matcher(""); //$NON-NLS-1$ |
| this.raChunkEndLineMatcher= chunkEndLinePattern.matcher(""); //$NON-NLS-1$ |
| this.raChunkRefLineMatcher= (chunkRefLinePattern != null) ? chunkRefLinePattern.matcher("") : null; //$NON-NLS-1$ |
| |
| this.rpReporter= new RProblemReporter(); |
| } |
| |
| |
| public String getName() { |
| return this.name; |
| } |
| |
| public RModelManager getRModelManager() { |
| return this.rManager; |
| } |
| |
| public void reconcileAst(final TModelContainer adapter, |
| final SourceContent sourceContent, final List<? extends EmbeddingAstNode> list, |
| final IProgressMonitor monitor) { |
| if (sourceContent.getStartOffset() != 0) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| final String source= sourceContent.getString(); |
| final TextLineInformation lines= sourceContent.getStringLines(); |
| |
| this.raChunkStartLineMatcher.reset(source); |
| if (this.raChunkRefLineMatcher != null) { |
| this.raChunkRefLineMatcher.reset(source); |
| } |
| this.raChunkEndLineMatcher.reset(source); |
| |
| this.raInput.reset(source); |
| |
| for (final EmbeddingAstNode embeddingNode : list) { |
| if (embeddingNode.getForeignTypeId() != RModel.R_TYPE_ID) { |
| continue; |
| } |
| |
| final RChunkNode rChunk= new RChunkNode(embeddingNode); |
| rChunk.startOffset= embeddingNode.getStartOffset(); |
| rChunk.endOffset= embeddingNode.getEndOffset(); |
| embeddingNode.setForeignNode(rChunk); |
| |
| final IRegion startRegion; |
| final List<IRegion> rCode= new ArrayList<>(4); |
| |
| switch ((embeddingNode.getEmbedDescr() & 0xf)) { |
| |
| case EmbeddingAstNode.EMBED_INLINE: |
| startRegion= null; |
| rCode.add(new Region(embeddingNode.getStartOffset(), embeddingNode.getLength())); |
| break; |
| |
| case EmbeddingAstNode.EMBED_CHUNK: { |
| int lineOffset= rChunk.startOffset; |
| int line= lines.getLineOfOffset(rChunk.startOffset); |
| int rCodeStartOffset; |
| |
| // start line |
| int lineEndOffset= lines.getEndOffset(line); |
| this.raChunkStartLineMatcher.region(lineOffset, lineEndOffset); |
| if (!this.raChunkStartLineMatcher.matches()) { |
| throw new IllegalStateException("R chunk does not start with start line."); |
| } |
| { final int start= this.raChunkStartLineMatcher.start(1); |
| final int end= this.raChunkStartLineMatcher.end(1); |
| startRegion= new Region(start, end - start); |
| } |
| rCodeStartOffset= lineEndOffset; |
| |
| if (lineEndOffset < rChunk.endOffset) { |
| do { |
| line++; |
| lineOffset= lineEndOffset; |
| lineEndOffset= lines.getEndOffset(line); |
| |
| if (this.raChunkRefLineMatcher != null) { |
| this.raChunkRefLineMatcher.region(lineOffset, lineEndOffset); |
| if (this.raChunkRefLineMatcher.matches()) { |
| if (rCodeStartOffset < lineOffset) { |
| rCode.add(new Region(rCodeStartOffset, lineOffset - rCodeStartOffset)); |
| } |
| rCodeStartOffset= lineEndOffset; |
| } |
| } |
| } while (lineEndOffset < rChunk.endOffset); |
| |
| if (rChunk.endOffset != lineEndOffset) { |
| throw new IllegalStateException("R chunk does not end at line end."); |
| } |
| |
| this.raChunkEndLineMatcher.region(lineOffset, lineEndOffset); |
| if (this.raChunkEndLineMatcher.matches()) { |
| if (rCodeStartOffset < lineOffset) { |
| rCode.add(new Region(rCodeStartOffset, lineOffset - rCodeStartOffset)); |
| } |
| rCodeStartOffset= lineEndOffset; |
| } |
| else if (rCodeStartOffset < lineOffset) { |
| rCode.add(new Region(rCodeStartOffset, lineEndOffset - rCodeStartOffset)); |
| } |
| } |
| break; } |
| |
| default: |
| throw new UnsupportedOperationException("embedType= " + embeddingNode.getEmbedDescr()); |
| } |
| |
| if (startRegion != null) { |
| rChunk.weaveArgs= this.raParser.scanFCallArgs( |
| this.raInput.init(startRegion.getOffset(), startRegion.getOffset() + startRegion.getLength()), |
| true ); |
| } |
| final SourceComponent[] rCodeNodes= new @NonNull SourceComponent[rCode.size()]; |
| for (int j= 0; j < rCodeNodes.length; j++) { |
| final IRegion region= rCode.get(j); |
| rCodeNodes[j]= this.raParser.scanSourceRange( |
| this.raInput.init(region.getOffset(), region.getOffset() + region.getLength()), |
| rChunk, true ); |
| } |
| rChunk.rSources= ImCollections.newList(rCodeNodes); |
| } |
| } |
| |
| public @Nullable RSourceUnitModelInfo reconcileModel(final TModelContainer adapter, |
| final SourceContent content, final TModelInfo mainModel, |
| final List<? extends EmbeddingForeignReconcileTask<?, ?>> list, |
| final int level, final IProgressMonitor monitor) { |
| if (list == null || list.isEmpty()) { |
| return null; |
| } |
| |
| int chunkCount= 0; |
| final List<RedocsRChunkElement> chunkElements= new ArrayList<>(); |
| final List<SourceComponent> inlineNodes= new ArrayList<>(); |
| |
| for (final EmbeddingForeignReconcileTask<?, ?> item : list) { |
| if (item.getForeignTypeId() != RModel.R_TYPE_ID) { |
| continue; |
| } |
| |
| final EmbeddingAstNode embeddingNode= item.getAstNode(); |
| final RChunkNode rChunk= (RChunkNode) embeddingNode.getForeignNode(); |
| if (rChunk == null) { |
| continue; |
| } |
| |
| final ImList<SourceComponent> rSources= rChunk.getRCodeChildren(); |
| switch ((embeddingNode.getEmbedDescr() & 0xf)) { |
| |
| case EmbeddingAstNode.EMBED_INLINE: |
| if (rSources.size() == 1) { |
| inlineNodes.add(rSources.get(0)); |
| } |
| break; |
| |
| case EmbeddingAstNode.EMBED_CHUNK: { |
| chunkCount++; |
| |
| RElementName name= null; |
| TextRegion nameRegion= null; |
| { final Arg labelArgNode= getLabelArgNode(rChunk.getWeaveArgsChild()); |
| if (labelArgNode != null && labelArgNode.hasValue()) { |
| final RAstNode labelNode= labelArgNode.getValueChild(); |
| final String label; |
| if (labelArgNode.getValueChild().getNodeType() == NodeType.SYMBOL) { |
| label= labelNode.getText(); |
| } |
| else { |
| label= content.getString( |
| labelNode.getStartOffset(), labelNode.getEndOffset() ); |
| } |
| name= RElementName.create(RElementName.MAIN_OTHER, label); |
| nameRegion= labelNode; |
| } |
| } |
| if (name == null) { |
| name= RElementName.create(RElementName.MAIN_OTHER, "#"+Integer.toString(chunkCount)); //$NON-NLS-1$ |
| nameRegion= new BasicTextRegion(embeddingNode.getStartOffset() + 2); |
| } |
| final RedocsRChunkElement element= new RedocsRChunkElement(item.getEmbeddingElement(), |
| rChunk, name, nameRegion ); |
| item.setEmbeddedElement(element); |
| chunkElements.add(element); |
| break; } |
| } |
| } |
| |
| if (chunkElements.isEmpty() && inlineNodes.isEmpty()) { |
| return null; |
| } |
| |
| final RSourceUnitModelInfo modelInfo= getRModelManager().reconcile(adapter.getSourceUnit(), |
| mainModel, chunkElements, inlineNodes, level, monitor ); |
| mainModel.addAttachment(modelInfo); |
| return modelInfo; |
| } |
| |
| private @Nullable Arg getLabelArgNode(final @Nullable Args weaveArgs) { |
| if (weaveArgs == null || !weaveArgs.hasChildren()) { |
| return null; |
| } |
| for (int i= 0; i < weaveArgs.getChildCount(); i++) { |
| final Arg arg= weaveArgs.getChild(i); |
| if ((arg.hasName()) ? |
| (arg.getNameChild().getNodeType() == NodeType.SYMBOL |
| && "label".equals(arg.getNameChild().getText()) ) : //$NON-NLS-1$ |
| (i == 0) ) { |
| return arg; |
| } |
| } |
| return null; |
| } |
| |
| public void reportEmbeddedProblems(final TModelContainer adapter, |
| final SourceContent content, final TModelInfo mainModel, |
| final ProblemRequestor problemRequestor, final int level, |
| final IProgressMonitor monitor) { |
| final RSourceUnitModelInfo rModel= RModel.getRModelInfo(mainModel); |
| if (rModel == null) { |
| return; |
| } |
| final RSourceUnit su= adapter.getSourceUnit(); |
| final RLangSourceElement element= rModel.getSourceElement(); |
| if (element instanceof RCompositeSourceElement) { |
| final List<? extends RLangSourceElement> elements= ((RCompositeSourceElement) element) |
| .getCompositeElements(); |
| for (final RLangSourceElement rChunk : elements) { |
| final AstNode rChunkNode= rChunk.getAdapter(AstNode.class); |
| if (rChunkNode != null) { |
| this.rpReporter.run(su, content, rChunkNode, problemRequestor); |
| } |
| } |
| } |
| } |
| |
| } |