blob: 9e206b2db014b72cabb6168cf0cd3a42dac2f646 [file] [log] [blame]
/*=============================================================================#
# 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);
}
}
}
}
}