mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-16 00:38:56 +00:00
partial parsing revisited
EContentAdapter for offset und line
This commit is contained in:
parent
014959cf01
commit
53838b74af
22 changed files with 523 additions and 327 deletions
|
@ -0,0 +1,47 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Jan Köhnlein - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class MultiMap<K,V> extends HashMap<K, List<V>>{
|
||||
|
||||
private static final long serialVersionUID = 28768781263L;
|
||||
|
||||
public void put(K key, V value) {
|
||||
List<V> list = super.get(key);
|
||||
if(list == null) {
|
||||
list=new ArrayList<V>();
|
||||
put(key, list);
|
||||
}
|
||||
list.add(value);
|
||||
}
|
||||
|
||||
public boolean remove(K key, V value) {
|
||||
List<V> list = super.get(key);
|
||||
if(list == null) return false;
|
||||
boolean result = list.remove(value);
|
||||
if(list.isEmpty()) {
|
||||
remove(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<V> get(K key) {
|
||||
List<V> list = super.get(key);
|
||||
return (list == null) ? Collections.<V>emptyList() : list;
|
||||
}
|
||||
}
|
|
@ -6,8 +6,8 @@
|
|||
<eClassifiers xsi:type="ecore:EClass" name="CompositeNode" eSuperTypes="#//AbstractNode">
|
||||
<eStructuralFeatures xsi:type="ecore:EReference" name="children" upperBound="-1"
|
||||
eType="#//AbstractNode" containment="true" eOpposite="#//AbstractNode/parent"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EAttribute" name="lookahead" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EAttribute" name="lookaheadConsumed" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EReference" name="lookaheadLeafNodes" upperBound="-1"
|
||||
eType="#//LeafNode"/>
|
||||
</eClassifiers>
|
||||
<eClassifiers xsi:type="ecore:EClass" name="AbstractNode" abstract="true">
|
||||
<eOperations name="length" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
|
||||
|
@ -15,16 +15,6 @@
|
|||
<details key="body" value="if (this instanceof CompositeNodeImpl) { return ParsetreeUtil.length((CompositeNodeImpl) this);} else if (this instanceof LeafNodeImpl) { return ParsetreeUtil.length((LeafNodeImpl) this);} else {return ParsetreeUtil.length((AbstractNodeImpl) this);}"/>
|
||||
</eAnnotations>
|
||||
</eOperations>
|
||||
<eOperations name="offset" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
|
||||
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
|
||||
<details key="body" value="if (this instanceof CompositeNodeImpl) { return ParsetreeUtil.offset((CompositeNodeImpl) this);} else if (this instanceof LeafNodeImpl) { return ParsetreeUtil.offset((LeafNodeImpl) this);} else {return ParsetreeUtil.offset((AbstractNodeImpl) this);}"/>
|
||||
</eAnnotations>
|
||||
</eOperations>
|
||||
<eOperations name="line" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
|
||||
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
|
||||
<details key="body" value="if (this instanceof CompositeNodeImpl) { return ParsetreeUtil.line((CompositeNodeImpl) this);} else if (this instanceof LeafNodeImpl) { return ParsetreeUtil.line((LeafNodeImpl) this);} else {return ParsetreeUtil.line((AbstractNodeImpl) this);}"/>
|
||||
</eAnnotations>
|
||||
</eOperations>
|
||||
<eOperations name="serialize" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString">
|
||||
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
|
||||
<details key="body" value="if (this instanceof CompositeNodeImpl) { return ParsetreeUtil.serialize((CompositeNodeImpl) this);} else if (this instanceof LeafNodeImpl) { return ParsetreeUtil.serialize((LeafNodeImpl) this);} else {return ParsetreeUtil.serialize((AbstractNodeImpl) this);}"/>
|
||||
|
@ -46,12 +36,19 @@
|
|||
<details key="body" value="if (this instanceof CompositeNodeImpl) { return ParsetreeUtil.allSyntaxErrors((CompositeNodeImpl) this);} else if (this instanceof LeafNodeImpl) { return ParsetreeUtil.allSyntaxErrors((LeafNodeImpl) this);} else {return ParsetreeUtil.allSyntaxErrors((AbstractNodeImpl) this);}"/>
|
||||
</eAnnotations>
|
||||
</eOperations>
|
||||
<eOperations name="endLine" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
|
||||
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
|
||||
<details key="body" value="if (this instanceof CompositeNodeImpl) { return ParsetreeUtil.endLine((CompositeNodeImpl) this);} else if (this instanceof LeafNodeImpl) { return ParsetreeUtil.endLine((LeafNodeImpl) this);} else {return ParsetreeUtil.endLine((AbstractNodeImpl) this);}"/>
|
||||
</eAnnotations>
|
||||
</eOperations>
|
||||
<eStructuralFeatures xsi:type="ecore:EReference" name="parent" eType="#//CompositeNode"
|
||||
eOpposite="#//CompositeNode/children"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EReference" name="grammarElement" eType="ecore:EClass http://www.eclipse.org/emf/2002/Ecore#//EObject"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EReference" name="element" eType="ecore:EClass http://www.eclipse.org/emf/2002/Ecore#//EObject"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EReference" name="syntaxError" eType="#//SyntaxError"
|
||||
containment="true" eOpposite="#//SyntaxError/node"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EAttribute" name="offset" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
|
||||
<eStructuralFeatures xsi:type="ecore:EAttribute" name="line" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
|
||||
</eClassifiers>
|
||||
<eClassifiers xsi:type="ecore:EClass" name="LeafNode" eSuperTypes="#//AbstractNode">
|
||||
<eStructuralFeatures xsi:type="ecore:EAttribute" name="text" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.eclipse.xtext.parsetree.NodeAdapter;
|
|||
import org.eclipse.xtext.parsetree.NodeAdapterFactory;
|
||||
import org.eclipse.xtext.parsetree.ParsetreeFactory;
|
||||
import org.eclipse.xtext.parsetree.SyntaxError;
|
||||
import org.eclipse.xtext.util.MultiMap;
|
||||
import org.eclipse.xtext.util.Strings;
|
||||
|
||||
public abstract class AbstractAntlrParser extends Parser {
|
||||
|
@ -53,6 +54,9 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
|
||||
protected int lastConsumedIndex = -1;
|
||||
|
||||
private MultiMap<Token, CompositeNode> deferredLookaheadMap = new MultiMap<Token, CompositeNode>();
|
||||
private Map<Token, LeafNode> token2NodeMap = new HashMap<Token, LeafNode>();
|
||||
|
||||
protected AbstractAntlrParser(TokenStream input) {
|
||||
super(input);
|
||||
}
|
||||
|
@ -97,8 +101,7 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
leafNode.setFeature(feature);
|
||||
parentNode.getChildren().add(leafNode);
|
||||
lastConsumedIndex = token.getTokenIndex();
|
||||
((XtextTokenStream) input).consumeLookahead();
|
||||
// ((XtextTokenStream) input).decrementLookahead();
|
||||
tokenConsumed(token, leafNode);
|
||||
return leafNode;
|
||||
}
|
||||
return null;
|
||||
|
@ -130,7 +133,8 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
antlrTypeToLexerName.put(Integer.parseInt(tokenTypeId), token.substring(prefix.length()));
|
||||
line = br.readLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.error(e);
|
||||
throw new WrappedException(e);
|
||||
}
|
||||
|
@ -184,7 +188,8 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
EList<LeafNode> leafNodes = currentNode.getLeafNodes();
|
||||
if (leafNodes.isEmpty()) {
|
||||
appendError(currentNode);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
appendError(leafNodes.get(leafNodes.size() - 1));
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +273,8 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
try {
|
||||
Method method = this.getClass().getMethod(antlrEntryRuleName);
|
||||
current = (EObject) method.invoke(this);
|
||||
} catch (InvocationTargetException ite) {
|
||||
}
|
||||
catch (InvocationTargetException ite) {
|
||||
Throwable targetException = ite.getTargetException();
|
||||
if (targetException instanceof RecognitionException) {
|
||||
throw (RecognitionException) targetException;
|
||||
|
@ -277,14 +283,17 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
throw new WrappedException((Exception) targetException);
|
||||
}
|
||||
throw new RuntimeException(targetException);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new WrappedException(e);
|
||||
}
|
||||
appendTrailingHiddenTokens(currentNode);
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
appendAllTokens();
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
result = new ParseResult(current, currentNode);
|
||||
}
|
||||
}
|
||||
|
@ -296,15 +305,28 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
if (!entryRuleName.startsWith("entryRule")) {
|
||||
if (!entryRuleName.startsWith("rule")) {
|
||||
antlrEntryRuleName = "entryRule" + entryRuleName;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
antlrEntryRuleName = "entry" + Strings.toFirstUpper(entryRuleName);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
antlrEntryRuleName = entryRuleName;
|
||||
}
|
||||
return antlrEntryRuleName;
|
||||
}
|
||||
|
||||
private void tokenConsumed(Token token, LeafNode leafNode) {
|
||||
List<CompositeNode> nodesDecidingOnToken = deferredLookaheadMap.get(token);
|
||||
for (CompositeNode nodeDecidingOnToken : nodesDecidingOnToken) {
|
||||
nodeDecidingOnToken.getLookaheadLeafNodes().add(leafNode);
|
||||
}
|
||||
deferredLookaheadMap.remove(token);
|
||||
token2NodeMap.put(token, leafNode);
|
||||
((XtextTokenStream) input).consumeLookahead();
|
||||
// ((XtextTokenStream) input).decrementLookahead();
|
||||
}
|
||||
|
||||
/**
|
||||
* The current lookahead is the number of tokens that have been matched by
|
||||
* the parent rule to decide that the current rule has to be called.
|
||||
|
@ -313,24 +335,39 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
*/
|
||||
protected void setCurrentLookahead() {
|
||||
XtextTokenStream xtextTokenStream = (XtextTokenStream) input;
|
||||
currentNode.setLookahead(xtextTokenStream.getCurrentLookahead());
|
||||
currentNode.setLookaheadConsumed(xtextTokenStream.getLookaheadConsumedByParent());
|
||||
List<Token> lookaheadTokens = xtextTokenStream.getLookaheadTokens();
|
||||
for (Token lookaheadToken : lookaheadTokens) {
|
||||
LeafNode leafNode = token2NodeMap.get(lookaheadToken);
|
||||
if (leafNode == null) {
|
||||
deferredLookaheadMap.put(lookaheadToken, currentNode);
|
||||
}
|
||||
else {
|
||||
currentNode.getLookaheadLeafNodes().add(leafNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current lookahead to zero. See {@link
|
||||
* AbstractAntlrParser#setCurrentLookahead()}
|
||||
* Sets the current lookahead to zero. See
|
||||
* {@link AbstractAntlrParser#setCurrentLookahead()}
|
||||
*/
|
||||
protected void resetLookahead() {
|
||||
XtextTokenStream xtextTokenStream = (XtextTokenStream) input;
|
||||
xtextTokenStream.resetLookahead();
|
||||
token2NodeMap.clear();
|
||||
}
|
||||
|
||||
protected void moveLookaheadInfo(CompositeNode source, CompositeNode target) {
|
||||
target.setLookahead(source.getLookahead());
|
||||
target.setLookaheadConsumed(source.getLookaheadConsumed());
|
||||
source.setLookahead(0);
|
||||
source.setLookaheadConsumed(0);
|
||||
EList<LeafNode> sourceLookaheadLeafNodes = source.getLookaheadLeafNodes();
|
||||
target.getLookaheadLeafNodes().addAll(sourceLookaheadLeafNodes);
|
||||
sourceLookaheadLeafNodes.clear();
|
||||
|
||||
for (Token deferredLookaheadToken : deferredLookaheadMap.keySet()) {
|
||||
List<CompositeNode> nodesDecidingOnToken = deferredLookaheadMap.get(deferredLookaheadToken);
|
||||
while(nodesDecidingOnToken.indexOf(source) != -1) {
|
||||
nodesDecidingOnToken.set(nodesDecidingOnToken.indexOf(source), target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,11 +376,12 @@ public abstract class AbstractAntlrParser extends Parser {
|
|||
* {@link AbstractAntlrParser#setCurrentLookahead()}
|
||||
*
|
||||
* @see org.antlr.runtime.BaseRecognizer#match(org.antlr.runtime.IntStream,
|
||||
* int, org.antlr.runtime.BitSet)
|
||||
* int, org.antlr.runtime.BitSet)
|
||||
*/
|
||||
@Override
|
||||
public void match(IntStream input, int ttype, BitSet follow) throws RecognitionException {
|
||||
super.match(input, ttype, follow);
|
||||
((XtextTokenStream) input).removeLastLookaheadToken();
|
||||
((XtextTokenStream) input).decrementLookahead();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.xtext.parser.antlr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.antlr.runtime.CommonTokenStream;
|
||||
import org.antlr.runtime.Token;
|
||||
import org.antlr.runtime.TokenSource;
|
||||
|
||||
/**
|
||||
|
@ -21,6 +25,8 @@ public class XtextTokenStream extends CommonTokenStream {
|
|||
private int currentLookahead;
|
||||
private int lookaheadConsumedByParent;
|
||||
|
||||
private List<Token> lookaheadTokens = new ArrayList<Token>();
|
||||
|
||||
public XtextTokenStream() {
|
||||
super();
|
||||
}
|
||||
|
@ -41,6 +47,10 @@ public class XtextTokenStream extends CommonTokenStream {
|
|||
@Override
|
||||
public int LA(int i) {
|
||||
currentLookahead = Math.max(currentLookahead, i);
|
||||
Token lookaheadToken = LT(i);
|
||||
if(!lookaheadTokens.contains(lookaheadToken)) {
|
||||
lookaheadTokens.add(lookaheadToken);
|
||||
}
|
||||
return super.LA(i);
|
||||
}
|
||||
|
||||
|
@ -51,6 +61,17 @@ public class XtextTokenStream extends CommonTokenStream {
|
|||
return currentLookahead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lookaheadTokens
|
||||
*/
|
||||
public List<Token> getLookaheadTokens() {
|
||||
return lookaheadTokens;
|
||||
}
|
||||
|
||||
public void removeLastLookaheadToken() {
|
||||
lookaheadTokens.remove(lookaheadTokens.size()-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lookaheadConsumedByParent
|
||||
*/
|
||||
|
@ -61,6 +82,7 @@ public class XtextTokenStream extends CommonTokenStream {
|
|||
public void resetLookahead() {
|
||||
currentLookahead = 0;
|
||||
lookaheadConsumedByParent = 0;
|
||||
lookaheadTokens.clear();
|
||||
}
|
||||
|
||||
public void decrementLookahead() {
|
||||
|
@ -70,4 +92,5 @@ public class XtextTokenStream extends CommonTokenStream {
|
|||
public void consumeLookahead() {
|
||||
++lookaheadConsumedByParent;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ public class EnclosingCompositeNodeFinder {
|
|||
|
||||
private int currentOffset = 0;
|
||||
private int startTokenParentIndex = -1;
|
||||
private List<NodeWithCachedOffset> nodesEnclosingRegion;
|
||||
private List<CompositeNode> nodesEnclosingRegion;
|
||||
private int offset;
|
||||
private int length;
|
||||
|
||||
public EnclosingCompositeNodeFinder(CompositeNode parentNode, int offset, int length) {
|
||||
nodesEnclosingRegion = new ArrayList<NodeWithCachedOffset>();
|
||||
nodesEnclosingRegion = new ArrayList<CompositeNode>();
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
findRegion(parentNode);
|
||||
|
@ -38,13 +38,13 @@ public class EnclosingCompositeNodeFinder {
|
|||
/**
|
||||
* @return the nodesEnclosingRegion
|
||||
*/
|
||||
public List<NodeWithCachedOffset> getNodesEnclosingRegion() {
|
||||
public List<CompositeNode> getNodesEnclosingRegion() {
|
||||
return nodesEnclosingRegion;
|
||||
}
|
||||
|
||||
private boolean findRegion(CompositeNode parentNode) {
|
||||
boolean isLookingForStartToken = startTokenParentIndex == -1;
|
||||
nodesEnclosingRegion.add(new NodeWithCachedOffset(currentOffset, parentNode));
|
||||
nodesEnclosingRegion.add(parentNode);
|
||||
int currentParentIndex = nodesEnclosingRegion.size() - 1;
|
||||
EList<AbstractNode> children = parentNode.getChildren();
|
||||
for (AbstractNode child : children) {
|
||||
|
|
|
@ -28,7 +28,7 @@ public class NodeWithCachedOffset {
|
|||
/**
|
||||
* @return the offset
|
||||
*/
|
||||
public int getChachedOffset() {
|
||||
public int getCachedOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,21 +34,21 @@ public class PartialParsingPointers {
|
|||
private int length;
|
||||
private int offset;
|
||||
private List<CompositeNode> validReplaceRootNodes;
|
||||
private List<NodeWithCachedOffset> nodesEnclosingChangeRegion;
|
||||
private List<CompositeNode> nodesEnclosingChangeRegion;
|
||||
|
||||
public PartialParsingPointers(CompositeNode rootNode, int offset, int length,
|
||||
List<CompositeNode> validReplaceRootNodes, List<NodeWithCachedOffset> nodesEnclosingChangeRegion) {
|
||||
List<CompositeNode> validReplaceRootNodes, List<CompositeNode> nodesEnclosingRegion) {
|
||||
if (validReplaceRootNodes == null || validReplaceRootNodes.isEmpty()) {
|
||||
throw new IllegalArgumentException("validReplaceRootNodes cannot be empty");
|
||||
}
|
||||
if (nodesEnclosingChangeRegion == null || nodesEnclosingChangeRegion.isEmpty()) {
|
||||
if (nodesEnclosingRegion == null || nodesEnclosingRegion.isEmpty()) {
|
||||
throw new IllegalArgumentException("nodesEnclosingChangeRegion cannot be empty");
|
||||
}
|
||||
this.rootNode = rootNode;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.validReplaceRootNodes = validReplaceRootNodes;
|
||||
this.nodesEnclosingChangeRegion = nodesEnclosingChangeRegion;
|
||||
this.nodesEnclosingChangeRegion = nodesEnclosingRegion;
|
||||
}
|
||||
|
||||
public String findReparseRegion() {
|
||||
|
@ -92,12 +92,12 @@ public class PartialParsingPointers {
|
|||
public EObject findASTReplaceElement(CompositeNode replaceRootNode) {
|
||||
boolean foundReplaceNode = false;
|
||||
for (int i = 0; i < nodesEnclosingChangeRegion.size(); ++i) {
|
||||
CompositeNode nodeEnclosingRegion = (CompositeNode) nodesEnclosingChangeRegion.get(i).getNode();
|
||||
CompositeNode nodeEnclosingRegion = nodesEnclosingChangeRegion.get(i);
|
||||
if (nodeEnclosingRegion == replaceRootNode) {
|
||||
foundReplaceNode = true;
|
||||
}
|
||||
if (foundReplaceNode) {
|
||||
EObject currentASTElement = nodesEnclosingChangeRegion.get(i).getNode().getElement();
|
||||
EObject currentASTElement = nodesEnclosingChangeRegion.get(i).getElement();
|
||||
if (currentASTElement != null) {
|
||||
return currentASTElement;
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ public class PartialParsingPointers {
|
|||
// In this case, there must not be any composite nodes with
|
||||
// multiple composite children on the way down.
|
||||
return NodeUtil.getASTElementForRootNode((CompositeNode) nodesEnclosingChangeRegion.get(
|
||||
nodesEnclosingChangeRegion.size() - 1).getNode());
|
||||
nodesEnclosingChangeRegion.size() - 1));
|
||||
}
|
||||
|
||||
public String findASTContainmentFeatureName() {
|
||||
|
|
|
@ -101,7 +101,7 @@ public class PartialParsingUtil {
|
|||
public static String insertChangeIntoReplaceRegion(CompositeNode replaceRootNode, int originalOffset,
|
||||
int originalLength, String changedRegion) {
|
||||
String originalRegion = replaceRootNode.serialize();
|
||||
int changeOffset = originalOffset - replaceRootNode.offset();
|
||||
int changeOffset = originalOffset - replaceRootNode.getOffset();
|
||||
StringBuffer reparseRegion = new StringBuffer();
|
||||
reparseRegion.append(originalRegion.substring(0, changeOffset));
|
||||
reparseRegion.append(changedRegion);
|
||||
|
@ -110,7 +110,7 @@ public class PartialParsingUtil {
|
|||
}
|
||||
|
||||
public static PartialParsingPointers calculatePartialParsingPointers(CompositeNode rootNode, int offset, int length) {
|
||||
List<NodeWithCachedOffset> nodesEnclosingRegion = collectNodesEnclosingChangeRegion(rootNode, offset, length);
|
||||
List<CompositeNode> nodesEnclosingRegion = collectNodesEnclosingChangeRegion(rootNode, offset, length);
|
||||
List<CompositeNode> validReplaceRootNodes = internalFindValidReplaceRootNodeForChangeRegion(
|
||||
nodesEnclosingRegion, offset, length);
|
||||
if (validReplaceRootNodes.isEmpty()) {
|
||||
|
@ -122,11 +122,30 @@ public class PartialParsingUtil {
|
|||
/**
|
||||
* Collects a list of all nodes containing the change region
|
||||
*/
|
||||
private static List<NodeWithCachedOffset> collectNodesEnclosingChangeRegion(CompositeNode parent, int offset,
|
||||
int length) {
|
||||
EnclosingCompositeNodeFinder enclosingCompositeNodeFinder = new EnclosingCompositeNodeFinder(parent, offset,
|
||||
length);
|
||||
return enclosingCompositeNodeFinder.getNodesEnclosingRegion();
|
||||
private static List<CompositeNode> collectNodesEnclosingChangeRegion(CompositeNode parent, int offset, int length) {
|
||||
List<CompositeNode> nodesEnclosingRegion = new ArrayList<CompositeNode>();
|
||||
if (nodeEnclosesRegion(parent, offset, length)) {
|
||||
collectNodesEnclosingChangeRegion(parent, offset, length, nodesEnclosingRegion);
|
||||
}
|
||||
return nodesEnclosingRegion;
|
||||
}
|
||||
|
||||
private static void collectNodesEnclosingChangeRegion(CompositeNode parent, int offset, int length,
|
||||
List<CompositeNode> nodesEnclosingRegion) {
|
||||
nodesEnclosingRegion.add(parent);
|
||||
for (AbstractNode child : parent.getChildren()) {
|
||||
if (child instanceof CompositeNode) {
|
||||
CompositeNode childCompositeNode = (CompositeNode) child;
|
||||
if (nodeEnclosesRegion(childCompositeNode, offset, length)) {
|
||||
collectNodesEnclosingChangeRegion(childCompositeNode, offset, length, nodesEnclosingRegion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean nodeEnclosesRegion(CompositeNode node, int offset, int length) {
|
||||
return node.getOffset() <= offset && node.getOffset() + node.length() >= offset + length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,167 +160,72 @@ public class PartialParsingUtil {
|
|||
* @return
|
||||
*/
|
||||
private static List<CompositeNode> internalFindValidReplaceRootNodeForChangeRegion(
|
||||
List<NodeWithCachedOffset> nodesEnclosingRegion, int offset, int length) {
|
||||
List<NodeWithCachedOffset> lookaheadNodes = new ArrayList<NodeWithCachedOffset>();
|
||||
int numConsumedLookaheadTokens = 0;
|
||||
List<CompositeNode> nodesEnclosingRegion, int offset, int length) {
|
||||
List<LeafNode> lookaheadNodes = new ArrayList<LeafNode>();
|
||||
List<CompositeNode> validReplaceRootNodes = new ArrayList<CompositeNode>();
|
||||
|
||||
for (NodeWithCachedOffset node : nodesEnclosingRegion) {
|
||||
List<NodeWithCachedOffset> parentsLookaheadNodes = getParentsLookaheadNodes(node);
|
||||
boolean lookaheadChanged = false;
|
||||
for (CompositeNode node : nodesEnclosingRegion) {
|
||||
// EList<LeafNode> parentsLookaheadNodes = ((CompositeNode)
|
||||
// node.getNode()).getLookaheadLeafNodes();
|
||||
EList<LeafNode> parentsLookaheadNodes = node.getLookaheadLeafNodes();
|
||||
if (!parentsLookaheadNodes.isEmpty()) {
|
||||
mergeLookaheadNodes(lookaheadNodes, parentsLookaheadNodes);
|
||||
}
|
||||
if (((CompositeNode) node.getNode()).getLookaheadConsumed() > 0) {
|
||||
// parent has consumed lookahead tokens.
|
||||
numConsumedLookaheadTokens += ((CompositeNode) node.getNode()).getLookaheadConsumed();
|
||||
if (numConsumedLookaheadTokens == lookaheadNodes.size()) {
|
||||
// parent has consumed all current lookahead tokens
|
||||
NodeWithCachedOffset leafNode = lookaheadNodes.get(numConsumedLookaheadTokens - 1);
|
||||
if (nodeIsBeforeRegion(leafNode, offset)) {
|
||||
// last lookahead token is before changed region
|
||||
//NodeUtil.dumpCompositeNodeInfo("Possible entry node: "
|
||||
// , node);
|
||||
validReplaceRootNodes.add((CompositeNode) node.getNode());
|
||||
int index = lookaheadNodes.indexOf(parentsLookaheadNodes.get(0));
|
||||
if (index > 0) {
|
||||
// remove lookahead nodes common with grandpa
|
||||
while (lookaheadNodes.size() > index) {
|
||||
lookaheadNodes.remove(index);
|
||||
}
|
||||
}
|
||||
lookaheadNodes.addAll(parentsLookaheadNodes);
|
||||
lookaheadChanged = true;
|
||||
}
|
||||
if (lookaheadChanged) {
|
||||
LeafNode lastLookaheadLeafNode = lookaheadNodes.get(lookaheadNodes.size() - 1);
|
||||
if (nodeIsBeforeRegion(lastLookaheadLeafNode, offset)) {
|
||||
// last lookahead token is before changed region
|
||||
// and parent has consumed all current lookahead tokens
|
||||
// NodeUtil.dumpCompositeNodeInfo("Possible entry node: "
|
||||
// , node);
|
||||
validReplaceRootNodes.add(node);
|
||||
lookaheadChanged = false;
|
||||
}
|
||||
}
|
||||
// List<NodeWithCachedOffset> lookaheadNodes = new
|
||||
// ArrayList<NodeWithCachedOffset>();
|
||||
// int numConsumedLookaheadTokens = 0;
|
||||
// List<CompositeNode> validReplaceRootNodes = new
|
||||
// ArrayList<CompositeNode>();
|
||||
//
|
||||
// for (NodeWithCachedOffset node : nodesEnclosingRegion) {
|
||||
// List<NodeWithCachedOffset> parentsLookaheadNodes =
|
||||
// getParentsLookaheadNodes(node);
|
||||
// if (!parentsLookaheadNodes.isEmpty()) {
|
||||
// mergeLookaheadNodes(lookaheadNodes, parentsLookaheadNodes);
|
||||
// }
|
||||
// if (((CompositeNode) node.getNode()).getLookaheadConsumed() > 0)
|
||||
// {
|
||||
// // parent has consumed lookahead tokens.
|
||||
// numConsumedLookaheadTokens += ((CompositeNode)
|
||||
// node.getNode()).getLookaheadConsumed();
|
||||
// if (numConsumedLookaheadTokens == lookaheadNodes.size()) {
|
||||
// // parent has consumed all current lookahead tokens
|
||||
// NodeWithCachedOffset leafNode =
|
||||
// lookaheadNodes.get(numConsumedLookaheadTokens - 1);
|
||||
// if (nodeIsBeforeRegion(leafNode, offset)) {
|
||||
// // last lookahead token is before changed region
|
||||
// //NodeUtil.dumpCompositeNodeInfo("Possible entry node: "
|
||||
// // , node);
|
||||
// validReplaceRootNodes.add((CompositeNode) node.getNode());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
return validReplaceRootNodes;
|
||||
}
|
||||
|
||||
private static void mergeLookaheadNodes(List<NodeWithCachedOffset> lookaheadNodes,
|
||||
List<NodeWithCachedOffset> parentsLookaheadNodes) {
|
||||
NodeWithCachedOffset firstParentLookaheadNode = parentsLookaheadNodes.get(0);
|
||||
int index;
|
||||
for(index=0; index < lookaheadNodes.size(); ++index) {
|
||||
if(lookaheadNodes.get(index).getNode().equals(firstParentLookaheadNode.getNode())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index < lookaheadNodes.size()) {
|
||||
// remove lookahead nodes common with grandpa
|
||||
while (lookaheadNodes.size() > index) {
|
||||
lookaheadNodes.remove(index);
|
||||
}
|
||||
}
|
||||
lookaheadNodes.addAll(parentsLookaheadNodes);
|
||||
private static boolean nodeIsBeforeRegion(LeafNode node, int offset) {
|
||||
return node.getOffset() + node.length() <= offset;
|
||||
}
|
||||
|
||||
private static boolean nodeIsBeforeRegion(NodeWithCachedOffset leafNode, int offset) {
|
||||
return leafNode.getChachedOffset() + leafNode.getNode().length() <= offset;
|
||||
}
|
||||
|
||||
private static List<NodeWithCachedOffset> getParentsLookaheadNodes(NodeWithCachedOffset child) {
|
||||
int lookaheadFromParent = ((CompositeNode) child.getNode()).getLookahead();
|
||||
if (lookaheadFromParent != 0) {
|
||||
List<NodeWithCachedOffset> lookaheadNodes = new ArrayList<NodeWithCachedOffset>();
|
||||
int consumedByParent = ((CompositeNode) child.getNode()).getLookaheadConsumed();
|
||||
if (consumedByParent > 0) {
|
||||
// some lookahead nodes are consumed by parent
|
||||
NodeWithCachedOffset tempLeaf = child;
|
||||
for (int i = 0; i < consumedByParent; ++i) {
|
||||
tempLeaf = previousUnhiddenLeaf(tempLeaf.getNode(), tempLeaf.getChachedOffset());
|
||||
lookaheadNodes.add(tempLeaf);
|
||||
}
|
||||
}
|
||||
if (lookaheadFromParent - consumedByParent > 0) {
|
||||
// remaining lookahead nodes consumed by child
|
||||
NodeWithCachedOffset tempLeaf = child;
|
||||
for (int i = 0; i < lookaheadFromParent - consumedByParent; ++i) {
|
||||
tempLeaf = nextUnhiddenLeaf(tempLeaf.getNode(), tempLeaf.getChachedOffset());
|
||||
lookaheadNodes.add(tempLeaf);
|
||||
}
|
||||
}
|
||||
return lookaheadNodes;
|
||||
}
|
||||
else {
|
||||
return Collections.<NodeWithCachedOffset> emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static NodeWithCachedOffset nextUnhiddenLeaf(AbstractNode node, int currentOffset) {
|
||||
if (node instanceof CompositeNode) {
|
||||
NodeWithCachedOffset firstLeaf = firstLeaf((CompositeNode) node, currentOffset);
|
||||
if (firstLeaf != null) {
|
||||
return firstLeaf;
|
||||
}
|
||||
}
|
||||
CompositeNode parent = node.getParent();
|
||||
EList<AbstractNode> siblings = parent.getChildren();
|
||||
int childIndex = siblings.indexOf(node);
|
||||
while (++childIndex < siblings.size()) {
|
||||
AbstractNode sibling = siblings.get(childIndex);
|
||||
if (sibling instanceof LeafNode) {
|
||||
if (!((LeafNode) sibling).isHidden()) {
|
||||
return new NodeWithCachedOffset(currentOffset, sibling);
|
||||
}
|
||||
currentOffset += sibling.length();
|
||||
}
|
||||
else if (sibling instanceof CompositeNode) {
|
||||
return nextUnhiddenLeaf(sibling, currentOffset);
|
||||
}
|
||||
}
|
||||
return nextUnhiddenLeaf(parent, currentOffset);
|
||||
}
|
||||
|
||||
private static NodeWithCachedOffset firstLeaf(CompositeNode node, int currentOffset) {
|
||||
EList<AbstractNode> children = node.getChildren();
|
||||
for (AbstractNode child : children) {
|
||||
if (child instanceof LeafNode) {
|
||||
if (!((LeafNode) child).isHidden()) {
|
||||
return new NodeWithCachedOffset(currentOffset, child);
|
||||
}
|
||||
currentOffset += child.length();
|
||||
}
|
||||
else if (child instanceof CompositeNode) {
|
||||
NodeWithCachedOffset leafFromChild = firstLeaf((CompositeNode) child, currentOffset);
|
||||
if (leafFromChild != null) {
|
||||
return leafFromChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static NodeWithCachedOffset previousUnhiddenLeaf(AbstractNode node, int currentOffset) {
|
||||
CompositeNode parent = node.getParent();
|
||||
EList<AbstractNode> siblings = parent.getChildren();
|
||||
int childIndex = siblings.indexOf(node);
|
||||
while (--childIndex >= 0) {
|
||||
AbstractNode sibling = siblings.get(childIndex);
|
||||
if (sibling instanceof LeafNode) {
|
||||
currentOffset -= sibling.length();
|
||||
if(!((LeafNode) sibling).isHidden()) {
|
||||
return new NodeWithCachedOffset(currentOffset, sibling);
|
||||
}
|
||||
}
|
||||
else {
|
||||
NodeWithCachedOffset leafFromComposite = lastUnhiddenLeaf((CompositeNode) sibling, currentOffset);
|
||||
if (leafFromComposite != null) {
|
||||
return leafFromComposite;
|
||||
}
|
||||
}
|
||||
}
|
||||
return previousUnhiddenLeaf(parent, currentOffset);
|
||||
}
|
||||
|
||||
private static NodeWithCachedOffset lastUnhiddenLeaf(CompositeNode node, int currentOffset) {
|
||||
EList<AbstractNode> children = node.getChildren();
|
||||
for (int i = children.size() - 1; i >= 0; --i) {
|
||||
AbstractNode child = children.get(i);
|
||||
if (child instanceof LeafNode) {
|
||||
currentOffset -= child.length();
|
||||
if(!((LeafNode) child).isHidden()) {
|
||||
return new NodeWithCachedOffset(currentOffset, child);
|
||||
}
|
||||
}
|
||||
else if (child instanceof CompositeNode) {
|
||||
NodeWithCachedOffset leafFromChild = lastUnhiddenLeaf((CompositeNode) child, currentOffset);
|
||||
if (leafFromChild != null) {
|
||||
return leafFromChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.parsetree;
|
||||
|
||||
import org.eclipse.emf.common.notify.Notification;
|
||||
import org.eclipse.emf.common.util.EList;
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.util.EContentAdapter;
|
||||
|
||||
/**
|
||||
* @author Jan Köhnlein - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class NodeContentAdapter extends EContentAdapter {
|
||||
|
||||
@Override
|
||||
public void notifyChanged(Notification notification) {
|
||||
super.notifyChanged(notification);
|
||||
Object notifier = notification.getNotifier();
|
||||
if (notifier instanceof CompositeNode) {
|
||||
CompositeNode parent = (CompositeNode) notifier;
|
||||
Object feature = notification.getFeature();
|
||||
if (ParsetreePackage.Literals.COMPOSITE_NODE__CHILDREN.equals(feature)) {
|
||||
AbstractNode child = (AbstractNode) notification.getNewValue();
|
||||
int eventType = notification.getEventType();
|
||||
int position = notification.getPosition();
|
||||
switch (eventType) {
|
||||
case Notification.ADD:
|
||||
if (position == 0) {
|
||||
updateOffsetAndLine(child, new NodeInfo(parent.getOffset(), parent.getLine()));
|
||||
}
|
||||
else {
|
||||
AbstractNode predecessor = parent.getChildren().get(position - 1);
|
||||
updateOffsetAndLine(child, new NodeInfo((predecessor.getOffset() + predecessor.length()), predecessor.endLine()));
|
||||
}
|
||||
break;
|
||||
case Notification.REMOVE:
|
||||
if (position == 0) {
|
||||
updateOffsetAndLine(parent, new NodeInfo(parent.getOffset(), parent.getLine()));
|
||||
}
|
||||
else {
|
||||
AbstractNode successor = parent.getChildren().get(position);
|
||||
updateOffsetAndLine(successor, new NodeInfo(child.getOffset(), child.getLine()));
|
||||
}
|
||||
break;
|
||||
case Notification.ADD_MANY:
|
||||
case Notification.MOVE:
|
||||
case Notification.REMOVE_MANY:
|
||||
updateOffsetAndLine(parent, new NodeInfo(parent.getOffset(), parent.getLine()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTarget(EObject target) {
|
||||
if (target instanceof AbstractNode) {
|
||||
AbstractNode targetNode = (AbstractNode) target;
|
||||
CompositeNode parent = targetNode.getParent();
|
||||
if (parent != null) {
|
||||
EList<AbstractNode> siblings = parent.getChildren();
|
||||
int index = siblings.indexOf(target);
|
||||
if (index == 0) {
|
||||
updateOffsetAndLine(targetNode, new NodeInfo(parent.getOffset(), parent.getLine()));
|
||||
}
|
||||
else {
|
||||
AbstractNode predecessor = siblings.get(index - 1);
|
||||
updateOffsetAndLine(targetNode, new NodeInfo((predecessor.getOffset() + predecessor.length()), predecessor.endLine()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
updateOffsetAndLine(targetNode, new NodeInfo(0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class NodeInfo {
|
||||
int offset;
|
||||
int line;
|
||||
|
||||
public NodeInfo(int offset, int line) {
|
||||
this.offset = offset;
|
||||
this.line = line;
|
||||
}
|
||||
}
|
||||
|
||||
protected NodeInfo updateOffsetAndLineInContents(AbstractNode node, NodeInfo info) {
|
||||
node.setOffset(info.offset);
|
||||
node.setLine(info.line);
|
||||
if (node instanceof LeafNode) {
|
||||
info.offset += node.length();
|
||||
info.line = node.endLine();
|
||||
}
|
||||
if (node instanceof CompositeNode) {
|
||||
for (AbstractNode child : ((CompositeNode) node).getChildren()) {
|
||||
info = updateOffsetAndLineInContents(child, info);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
protected AbstractNode updateOffsetAndLine(AbstractNode node, NodeInfo info) {
|
||||
updateOffsetAndLineInContents(node, info);
|
||||
CompositeNode parent = node.getParent();
|
||||
if (parent != null) {
|
||||
EList<AbstractNode> siblings = parent.getChildren();
|
||||
int index = siblings.indexOf(node);
|
||||
for (int i = index + 1; i < siblings.size(); ++i) {
|
||||
info = updateOffsetAndLineInContents(siblings.get(i), info);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
|
@ -101,8 +101,13 @@ public class NodeUtil {
|
|||
name = grammarElement.getClass().getSimpleName();
|
||||
}
|
||||
String astElementAsString = (node.getElement() == null) ? "null" : node.getElement().eClass().getName();
|
||||
System.out.println(indent + node.getLookahead() + " " + node.getLookaheadConsumed() + " " + name + " : " + node.serialize()
|
||||
+ " -> " + astElementAsString);
|
||||
System.out.print(indent + name + " : " + node.serialize()
|
||||
+ " -> " + astElementAsString + " la={ ");
|
||||
for (LeafNode lookaheadNode : node.getLookaheadLeafNodes()) {
|
||||
System.out.print("\""+ lookaheadNode.getText() +"\" ");
|
||||
}
|
||||
System.out.print(" (" + node.getOffset() + ", " + node.length() + ")");
|
||||
System.out.println("}");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,20 +41,6 @@ public class ParsetreeUtil {
|
|||
+ abstractParserNode.eClass().getName());
|
||||
}
|
||||
|
||||
public static int offset(AbstractNodeImpl abstractParserNode) {
|
||||
checkArgument(abstractParserNode);
|
||||
AbstractNode rootContainer = (AbstractNode) EcoreUtil.getRootContainer(abstractParserNode);
|
||||
if (rootContainer == abstractParserNode) {
|
||||
return 0;
|
||||
}
|
||||
EList<LeafNode> leafNodes = rootContainer.getLeafNodes(abstractParserNode);
|
||||
int offset = 0;
|
||||
for (LeafNode leafNode : leafNodes) {
|
||||
offset += leafNode.length();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static void checkArgument(AbstractNodeImpl abstractParserNode) {
|
||||
int classifierID = abstractParserNode.eClass().getClassifierID();
|
||||
if (classifierID != ParsetreePackage.COMPOSITE_NODE && classifierID != ParsetreePackage.LEAF_NODE) {
|
||||
|
@ -79,6 +65,18 @@ public class ParsetreeUtil {
|
|||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
public static int endLine(AbstractNodeImpl _this) {
|
||||
int line = _this.getLine();
|
||||
String text = _this.serialize();
|
||||
char[] charArray = text.toCharArray();
|
||||
for (char c : charArray) {
|
||||
// TODO handle os specific newlines
|
||||
if (c == '\n' || c == '\r')
|
||||
line++;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
public static String serialize(AbstractNodeImpl _this) {
|
||||
checkArgument(_this);
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.eclipse.xtext.parsetree.AbstractNode;
|
|||
import org.eclipse.xtext.parsetree.CompositeNode;
|
||||
import org.eclipse.xtext.parsetree.IParseTreeConstructor;
|
||||
import org.eclipse.xtext.parsetree.NodeAdapter;
|
||||
import org.eclipse.xtext.parsetree.NodeContentAdapter;
|
||||
import org.eclipse.xtext.service.Inject;
|
||||
|
||||
/**
|
||||
|
@ -58,6 +59,13 @@ public class XtextResource extends ResourceImpl {
|
|||
int documentGrowth = length - rootNode.length();
|
||||
int originalLength = length - documentGrowth;
|
||||
parseResult = parser.reparse(rootNode, offset, originalLength, change);
|
||||
if (parseResult != null && parseResult.getRootNode() != rootNode) {
|
||||
addNodeContentAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
private void addNodeContentAdapter() {
|
||||
parseResult.getRootNode().eAdapters().add(new NodeContentAdapter());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -69,6 +77,7 @@ public class XtextResource extends ResourceImpl {
|
|||
if (rootElement != null) {
|
||||
getContents().add(rootElement);
|
||||
}
|
||||
addNodeContentAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,8 @@ public class ParseErrorHandlingTest extends AbstractGeneratorTest {
|
|||
EList<SyntaxError> errors = NodeUtil.getRootNode(root).allSyntaxErrors();
|
||||
assertEquals(1,errors.size());
|
||||
assertEquals("%", ((LeafNode)errors.get(0).getNode()).getText());
|
||||
assertEquals(1, errors.get(0).getNode().line());
|
||||
assertEquals(15, errors.get(0).getNode().offset());
|
||||
assertEquals(1, errors.get(0).getNode().getLine());
|
||||
assertEquals(15, errors.get(0).getNode().getOffset());
|
||||
assertEquals(1, errors.get(0).getNode().length());
|
||||
assertEquals(1, errors.size());
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ public class ParseErrorHandlingTest extends AbstractGeneratorTest {
|
|||
EObject root = getModel("import 'holla' foo returns x::y::Z : name=ID;");
|
||||
EList<SyntaxError> errors = NodeUtil.getRootNode(root).allSyntaxErrors();
|
||||
assertEquals("::", ((LeafNode)errors.get(0).getNode()).getText());
|
||||
assertEquals(1, errors.get(0).getNode().line());
|
||||
assertEquals(31, errors.get(0).getNode().offset());
|
||||
assertEquals(1, errors.get(0).getNode().getLine());
|
||||
assertEquals(31, errors.get(0).getNode().getOffset());
|
||||
assertEquals(2, errors.get(0).getNode().length());
|
||||
assertEquals(1, errors.size());
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.eclipse.xtext.util.EmfStructureComparator;
|
|||
*/
|
||||
public abstract class AbstractPartialParserTest extends AbstractGeneratorTest {
|
||||
|
||||
protected static final boolean DEBUG = false;
|
||||
protected static final boolean DEBUG = true;
|
||||
protected EmfStructureComparator comparator;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.parser;
|
||||
|
||||
import static org.eclipse.xtext.parsetree.NodeUtil.dumpCompositeNodes;
|
||||
import static org.eclipse.xtext.parsetree.NodeUtil.getCompositeChildren;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.xtext.parsetree.CompositeNode;
|
||||
import org.eclipse.xtext.testlanguages.LookaheadLanguageStandaloneSetup;
|
||||
import org.eclipse.xtext.tests.AbstractGeneratorTest;
|
||||
|
||||
/**
|
||||
* @author Jan Köhnlein - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class LookaheadTest extends AbstractGeneratorTest {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.xtext.tests.AbstractGeneratorTest#setUp()
|
||||
*/
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
with(LookaheadLanguageStandaloneSetup.class);
|
||||
}
|
||||
|
||||
public void testLookahead() throws Exception {
|
||||
CompositeNode rootNode = getRootNode("bar a foo bar c b d foo bar b c");
|
||||
dumpCompositeNodes("", rootNode);
|
||||
assertEquals(0, rootNode.getLookahead());
|
||||
List<CompositeNode> alts = getCompositeChildren(rootNode);
|
||||
assertEquals(1, alts.get(0).getLookahead());
|
||||
assertEquals(1, alts.get(1).getLookahead());
|
||||
assertEquals(1, alts.get(2).getLookahead());
|
||||
assertEquals(1, getCompositeChildren(alts.get(0)).get(0).getLookahead());
|
||||
CompositeNode lookahead0 = getCompositeChildren(alts.get(1)).get(0);
|
||||
assertEquals(3, lookahead0.getLookahead());
|
||||
assertEquals(0, getCompositeChildren(lookahead0).get(0).getLookahead());
|
||||
CompositeNode lookahead3 = getCompositeChildren(alts.get(2)).get(0);
|
||||
assertEquals(3, lookahead3.getLookahead());
|
||||
assertEquals(0, getCompositeChildren(lookahead3).get(0).getLookahead());
|
||||
}
|
||||
|
||||
}
|
|
@ -25,9 +25,9 @@ public class PartialParserReplaceTest extends AbstractPartialParserTest {
|
|||
public void testExpression() throws Exception {
|
||||
with(SimpleExpressionsStandaloneSetup.class);
|
||||
String model = "(a+b+c)*(c/d)";
|
||||
replaceAndReparse(model, 2, 2, "+hugo+egon", "a+hugo+egon+c");
|
||||
replaceAndReparse(model, 2, 2, "+hugo+egon", "(a+hugo+egon+c)");
|
||||
replaceAndReparse(model, 8, 5, "egon", "egon");
|
||||
replaceAndReparse(model, 1, 2, "", "b+c");
|
||||
replaceAndReparse(model, 1, 2, "", "(b+c)");
|
||||
replaceAndReparse(model, 6, 3, "*", "(a+b+c*c/d)");
|
||||
replaceAndReparse(model, 3, 1, "(x+y+z)", "(x+y+z)");
|
||||
|
||||
|
@ -40,7 +40,7 @@ public class PartialParserReplaceTest extends AbstractPartialParserTest {
|
|||
public void testLookahead() throws Exception {
|
||||
with(LookaheadLanguageStandaloneSetup.class);
|
||||
String model = "foo bar b c";
|
||||
replaceAndReparse(model, 10, 1, "d", " d");
|
||||
replaceAndReparse(model, 10, 1, "d", "foo bar b d");
|
||||
replaceAndReparse(model, 8, 1, "b", "foo bar b c");
|
||||
replaceAndReparse(model, 0, model.length(), "", "");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
|
||||
* Copyright (c) NUM_ELEMENTS8 itemis AG (http://www.itemis.eu) and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
|
@ -18,15 +18,15 @@ import org.eclipse.xtext.testlanguages.SimpleExpressionsStandaloneSetup;
|
|||
*/
|
||||
public class PartialParsingPerformanceTest extends AbstractPartialParserTest {
|
||||
|
||||
public void testPerformance() throws Exception {
|
||||
int magicnumber = 10;
|
||||
private static final int NUM_ELEMENTS = 10;
|
||||
public void testExpression() throws Exception {
|
||||
with(SimpleExpressionsStandaloneSetup.class);
|
||||
StringBuffer modelBuffer = new StringBuffer();
|
||||
for(int i=0; i<magicnumber; ++i) {
|
||||
for(int i=0; i<NUM_ELEMENTS; ++i) {
|
||||
modelBuffer.append("(a+(b*");
|
||||
}
|
||||
modelBuffer.append("c");
|
||||
for(int i=0; i<magicnumber; ++i) {
|
||||
for(int i=0; i<NUM_ELEMENTS; ++i) {
|
||||
modelBuffer.append(")+d)");
|
||||
}
|
||||
String model = modelBuffer.toString();
|
||||
|
@ -35,4 +35,29 @@ public class PartialParsingPerformanceTest extends AbstractPartialParserTest {
|
|||
assertTrue(reparse.getParseErrors() == null || reparse.getParseErrors().isEmpty());
|
||||
}
|
||||
|
||||
// public void testReference() throws Exception {
|
||||
// with(ReferenceGrammarStandaloneSetup.class);
|
||||
// StringBuffer modelBuffer = new StringBuffer();
|
||||
// modelBuffer.append("spielplatz 17 {\n");
|
||||
// for(int i=0; i<NUM_ELEMENTS; ++i) {
|
||||
// modelBuffer.append(" kind ( Herbert");
|
||||
// modelBuffer.append(i);
|
||||
// modelBuffer.append(" 11 )\n");
|
||||
// }
|
||||
// for(int i=0; i<NUM_ELEMENTS; ++i) {
|
||||
// modelBuffer.append(" erwachsener ( Hugo");
|
||||
// modelBuffer.append(i);
|
||||
// modelBuffer.append(" 111 )\n");
|
||||
// }
|
||||
// modelBuffer.append(" erwachsener ( Sven 112 )\n");
|
||||
// for(int i=0; i<NUM_ELEMENTS; ++i) {
|
||||
// modelBuffer.append(" spielzeug ( Schaufel GRÜN )\n");
|
||||
// }
|
||||
// modelBuffer.append("}\n");
|
||||
// String model = modelBuffer.toString();
|
||||
// CompositeNode rootNode = getRootNode(model);
|
||||
// IParseResult reparse = PartialParsingUtil.reparse(getParser(), rootNode, model.indexOf("Sven"), 4, "Peter");
|
||||
// assertTrue(reparse.getParseErrors() == null || reparse.getParseErrors().isEmpty());
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.eclipse.xtext.parser.impl.PartialParsingPointers;
|
|||
import org.eclipse.xtext.parser.impl.PartialParsingUtil;
|
||||
import org.eclipse.xtext.parsetree.CompositeNode;
|
||||
import org.eclipse.xtext.testlanguages.LookaheadLanguageStandaloneSetup;
|
||||
import org.eclipse.xtext.testlanguages.ReferenceGrammarStandaloneSetup;
|
||||
import org.eclipse.xtext.testlanguages.SimpleExpressionsStandaloneSetup;
|
||||
|
||||
/**
|
||||
|
@ -29,25 +30,27 @@ public class PartialParsingPointerTest extends AbstractPartialParserTest {
|
|||
public void testExpression() throws Exception {
|
||||
with(SimpleExpressionsStandaloneSetup.class);
|
||||
String model = "(a+b+c)*(c/d)";
|
||||
PartialParsingPointers parsingPointers = calculatePartialParsingPointers(model, 1, 1);
|
||||
checkParseRegionPointers(parsingPointers, "a+b+c", "ActionImpl", "Addition", "Op", "Op", "values");
|
||||
PartialParsingPointers parsingPointers;
|
||||
|
||||
parsingPointers = calculatePartialParsingPointers(model, 1, 1);
|
||||
checkParseRegionPointers(parsingPointers, "(a+b+c)", "Parens", "Parens", "Op", "Op", "values");
|
||||
|
||||
parsingPointers = calculatePartialParsingPointers(model, 3, 1);
|
||||
checkParseRegionPointers(parsingPointers, "b", "Multiplication", "Multiplication", "Atom", "Op", "values");
|
||||
|
||||
parsingPointers = calculatePartialParsingPointers(model, 5, 2);
|
||||
checkParseRegionPointers(parsingPointers, model, "Sequence", "Sequence", "Op", null, null);
|
||||
checkParseRegionPointers(parsingPointers, "(a+b+c)", "Parens", "Parens", "Op", "Op", "values");
|
||||
|
||||
parsingPointers = calculatePartialParsingPointers(model, 6, 1);
|
||||
checkParseRegionPointers(parsingPointers, model, "Sequence", "Sequence", "Op", null, null);
|
||||
checkParseRegionPointers(parsingPointers, "(a+b+c)", "Parens", "Parens", "Op", "Op", "values");
|
||||
|
||||
parsingPointers = calculatePartialParsingPointers(model, 8, 2);
|
||||
checkParseRegionPointers(parsingPointers, "(c/d)", "Term", "Term", "Op", "Op", "values");
|
||||
|
||||
parsingPointers = calculatePartialParsingPointers(model, 9, 2);
|
||||
checkParseRegionPointers(parsingPointers, "c/d", "Addition", "Addition", "Op", "Op", "values");
|
||||
checkParseRegionPointers(parsingPointers, "(c/d)", "Parens", "Parens", "Op", "Op", "values");
|
||||
|
||||
model="a b";
|
||||
model = "a b";
|
||||
parsingPointers = calculatePartialParsingPointers(model, 1, 1);
|
||||
checkParseRegionPointers(parsingPointers, "a b", "ActionImpl", "Sequence", "Sequence", null, null);
|
||||
}
|
||||
|
@ -56,17 +59,47 @@ public class PartialParsingPointerTest extends AbstractPartialParserTest {
|
|||
with(LookaheadLanguageStandaloneSetup.class);
|
||||
String model = "bar a foo bar c b d foo bar b c";
|
||||
for (int i = 0; i < model.length(); ++i) {
|
||||
System.out.println(i);
|
||||
PartialParsingPointers parsingPointers = calculatePartialParsingPointers(model, i, 1);
|
||||
if (i < 29) {
|
||||
if (i < 3) {
|
||||
checkParseRegionPointers(parsingPointers, model, "Entry", "Entry", "Entry", null, null);
|
||||
}
|
||||
else if (i < 5) {
|
||||
checkParseRegionPointers(parsingPointers, "bar a", "LookAhead0", "LookAhead0", "LookAhead0", "Entry",
|
||||
"contents");
|
||||
}
|
||||
else if (i < 9) {
|
||||
checkParseRegionPointers(parsingPointers, model, "Entry", "Entry", "Entry", null, null);
|
||||
}
|
||||
else if (i < 15) {
|
||||
checkParseRegionPointers(parsingPointers, " foo bar c b d", "Alts", "Alts", "LookAhead1", "Entry",
|
||||
"contents");
|
||||
}
|
||||
else if (i < 19) {
|
||||
checkParseRegionPointers(parsingPointers, " foo bar c b d", "LookAhead1", "LookAhead1", "LookAhead1",
|
||||
"Entry", "contents");
|
||||
}
|
||||
else if (i < 23) {
|
||||
checkParseRegionPointers(parsingPointers, model, "Entry", "Entry", "Entry", null, null);
|
||||
}
|
||||
else if (i < 29) {
|
||||
checkParseRegionPointers(parsingPointers, " foo bar b c", "Alts", "Alts", "LookAhead3", "Entry",
|
||||
"contents");
|
||||
}
|
||||
else {
|
||||
checkParseRegionPointers(parsingPointers, " c", "LookAhead4", "LookAhead4", "LookAhead4", "LookAhead3",
|
||||
"z");
|
||||
checkParseRegionPointers(parsingPointers, " foo bar b c", "LookAhead3", "LookAhead3", "LookAhead3", "Entry",
|
||||
"contents");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testReference() throws Exception {
|
||||
with(ReferenceGrammarStandaloneSetup.class);
|
||||
String model = "spielplatz 17 { kind ( Dennis 6 ) kind ( Sven 7 ) }";
|
||||
PartialParsingPointers parsingPointers = calculatePartialParsingPointers(model, model.indexOf("Sven"), 4);
|
||||
checkParseRegionPointers(parsingPointers, " kind ( Sven 7 )", "Kind", "Kind", "Kind", "Spielplatz", "kinder");
|
||||
}
|
||||
|
||||
private PartialParsingPointers calculatePartialParsingPointers(String model, int changeRegionStart,
|
||||
int changeRegionSize) throws Exception {
|
||||
CompositeNode rootNode = getRootNode(model);
|
||||
|
@ -113,11 +146,14 @@ public class PartialParsingPointerTest extends AbstractPartialParserTest {
|
|||
assertEquals(expectedAstParentElementClassName, astParentElementClassName);
|
||||
String containmentFeatureName = parsingPointers.findASTContainmentFeatureName();
|
||||
assertEquals(expectedAstParentFeatureName, containmentFeatureName);
|
||||
if(astParentElement != null) {
|
||||
EStructuralFeature containmentFeature = astParentElement.eClass().getEStructuralFeature(containmentFeatureName);
|
||||
if(containmentFeature.isMany()) {
|
||||
assertTrue(((List<EObject>) astParentElement.eGet(containmentFeature)).contains(parsingPointers.findASTReplaceElement()));
|
||||
} else {
|
||||
if (astParentElement != null) {
|
||||
EStructuralFeature containmentFeature = astParentElement.eClass().getEStructuralFeature(
|
||||
containmentFeatureName);
|
||||
if (containmentFeature.isMany()) {
|
||||
assertTrue(((List<EObject>) astParentElement.eGet(containmentFeature)).contains(parsingPointers
|
||||
.findASTReplaceElement()));
|
||||
}
|
||||
else {
|
||||
assertTrue(astParentElement.eGet(containmentFeature).equals(parsingPointers.findASTReplaceElement()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,23 +21,23 @@ public class LengthOffsetLineTest extends AbstractGeneratorTest {
|
|||
CompositeNode node = (CompositeNode) getRootNode(model);
|
||||
EList<LeafNode> leafNodes = node.getLeafNodes();
|
||||
Iterator<LeafNode> iter = leafNodes.iterator();
|
||||
assertEquals(0,iter.next().offset());
|
||||
assertEquals(7,iter.next().offset());
|
||||
assertEquals(8,iter.next().offset());
|
||||
assertEquals(11,iter.next().offset());
|
||||
assertEquals(12,iter.next().offset());
|
||||
assertEquals(13,iter.next().offset());
|
||||
assertEquals(20,iter.next().offset());
|
||||
assertEquals(21,iter.next().offset());
|
||||
assertEquals(24,iter.next().offset());
|
||||
assertEquals(0,iter.next().getOffset());
|
||||
assertEquals(7,iter.next().getOffset());
|
||||
assertEquals(8,iter.next().getOffset());
|
||||
assertEquals(11,iter.next().getOffset());
|
||||
assertEquals(12,iter.next().getOffset());
|
||||
assertEquals(13,iter.next().getOffset());
|
||||
assertEquals(20,iter.next().getOffset());
|
||||
assertEquals(21,iter.next().getOffset());
|
||||
assertEquals(24,iter.next().getOffset());
|
||||
}
|
||||
|
||||
public void testOffset2() throws Exception {
|
||||
String model = "element foo;\nelement bar;";
|
||||
CompositeNode node = (CompositeNode) getRootNode(model);
|
||||
Iterator<AbstractNode> iter = node.getChildren().iterator();
|
||||
assertEquals(0,iter.next().offset());
|
||||
assertEquals(12,iter.next().offset());
|
||||
assertEquals(0,iter.next().getOffset());
|
||||
assertEquals(12,iter.next().getOffset());
|
||||
assertFalse(iter.hasNext());
|
||||
}
|
||||
|
||||
|
@ -46,15 +46,15 @@ public class LengthOffsetLineTest extends AbstractGeneratorTest {
|
|||
CompositeNode node = (CompositeNode) getRootNode(model);
|
||||
EList<LeafNode> leafNodes = node.getLeafNodes();
|
||||
Iterator<LeafNode> iter = leafNodes.iterator();
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(2,iter.next().line());
|
||||
assertEquals(2,iter.next().line());
|
||||
assertEquals(2,iter.next().line());
|
||||
assertEquals(2,iter.next().line());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
assertEquals(2,iter.next().getLine());
|
||||
assertEquals(2,iter.next().getLine());
|
||||
assertEquals(2,iter.next().getLine());
|
||||
assertEquals(2,iter.next().getLine());
|
||||
assertFalse(iter.hasNext());
|
||||
}
|
||||
|
||||
|
@ -62,12 +62,12 @@ public class LengthOffsetLineTest extends AbstractGeneratorTest {
|
|||
String model = "element foo;\nelement bar;\nelement bar;\nelement bar;";
|
||||
CompositeNode node = (CompositeNode) getRootNode(model);
|
||||
Iterator<AbstractNode> iter = node.getChildren().iterator();
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
//Note: because preceding whitespace is added to the following node,
|
||||
// the '\n' is always added to the following composite node
|
||||
assertEquals(1,iter.next().line());
|
||||
assertEquals(2,iter.next().line());
|
||||
assertEquals(3,iter.next().line());
|
||||
assertEquals(1,iter.next().getLine());
|
||||
assertEquals(2,iter.next().getLine());
|
||||
assertEquals(3,iter.next().getLine());
|
||||
assertFalse(iter.hasNext());
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ public class LengthOffsetLineTest extends AbstractGeneratorTest {
|
|||
assertEquals(5,nodes.size());
|
||||
int offset = 0;
|
||||
for (LeafNode leafNode : nodes) {
|
||||
assertEquals(offset,leafNode.offset());
|
||||
assertEquals(offset,leafNode.getOffset());
|
||||
offset += leafNode.length();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.parsetree;
|
||||
|
||||
import org.eclipse.xtext.testlanguages.ReferenceGrammarStandaloneSetup;
|
||||
import org.eclipse.xtext.tests.AbstractGeneratorTest;
|
||||
|
||||
/**
|
||||
* @author Jan Köhnlein - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class NodeContentAdapterTest extends AbstractGeneratorTest{
|
||||
|
||||
public void testNodeContentAdapter() throws Exception {
|
||||
with(ReferenceGrammarStandaloneSetup.class);
|
||||
CompositeNode rootNode = getRootNode("spielplatz 112 'Jajaja' { kind ( Dennis 7) }");
|
||||
NodeUtil.dumpCompositeNodes("", rootNode);
|
||||
}
|
||||
}
|
|
@ -9,8 +9,6 @@
|
|||
package org.eclipse.xtext.parsetree.reconstr;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.xtext.RuleCall;
|
||||
import org.eclipse.xtext.parsetree.CompositeNode;
|
||||
import org.eclipse.xtext.parsetree.IParseTreeConstructor;
|
||||
import org.eclipse.xtext.parsetree.NodeUtil;
|
||||
import org.eclipse.xtext.tests.AbstractGeneratorTest;
|
||||
|
|
|
@ -30,9 +30,9 @@ public class LeafNodeBug_234132 extends AbstractGeneratorTest {
|
|||
System.out.println("Model length=" + model.length());
|
||||
for (LeafNode leafNode : leafNodes) {
|
||||
String text = leafNode.getText();
|
||||
System.out.println("Leaf node" + leafNode.toString() + " offset=" + leafNode.offset() + " length=" + leafNode.length() + " text=" + ((text != null)? text : ""));
|
||||
assertTrue(leafNode.length() + leafNode.offset() <= model.length());
|
||||
assertEquals(model.substring(leafNode.offset(), leafNode.offset() + leafNode.length()), leafNode.getText());
|
||||
System.out.println("Leaf node" + leafNode.toString() + " offset=" + leafNode.getOffset() + " length=" + leafNode.length() + " text=" + ((text != null)? text : ""));
|
||||
assertTrue(leafNode.length() + leafNode.getOffset() <= model.length());
|
||||
assertEquals(model.substring(leafNode.getOffset(), leafNode.getOffset() + leafNode.length()), leafNode.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue