All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.xtext.parser.impl.PartialParsingHelper Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2008-2010 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.impl;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Alternatives;
import org.eclipse.xtext.CompoundElement;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.XtextPackage;
import org.eclipse.xtext.nodemodel.BidiIterator;
import org.eclipse.xtext.nodemodel.BidiTreeIterator;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.impl.AbstractNode;
import org.eclipse.xtext.nodemodel.impl.CompositeNode;
import org.eclipse.xtext.nodemodel.impl.NodeModelBuilder;
import org.eclipse.xtext.nodemodel.impl.SyntheticCompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.parser.IParser;
import org.eclipse.xtext.parser.ParseException;
import org.eclipse.xtext.parser.ParseResult;
import org.eclipse.xtext.parser.antlr.IPartialParsingHelper;
import org.eclipse.xtext.parser.antlr.IReferableElementsUnloader;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.util.XtextSwitch;

import com.google.inject.Inject;

/**
 * @author Jan K?hnlein - Initial contribution and API
 * @author Sebastian Zarnekow
 */
public class PartialParsingHelper implements IPartialParsingHelper {

	private static final Logger log = Logger.getLogger(PartialParsingHelper.class);

	@Inject
	private IReferableElementsUnloader unloader;
	
	@Inject
	private NodeModelBuilder nodeModelBuilder = new NodeModelBuilder();
	
	@Inject(optional=true)
	private TokenRegionProvider tokenRegionProvider;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public IParseResult reparse(IParser parser, IParseResult previousParseResult, ReplaceRegion changedRegion) {
		if (parser == null)
			throw new NullPointerException("parser may not be null");
		if (previousParseResult == null) {
			throw new NullPointerException("previousParseResult and previousParseResult.rootNode may not be null");
		}
		ICompositeNode oldRootNode = previousParseResult.getRootNode();
		if (changedRegion.getEndOffset() > oldRootNode.getTotalLength()) {
			log.error("Invalid " + changedRegion + " originalLength=" + oldRootNode.getTotalLength());
			return fullyReparse(parser, previousParseResult, changedRegion);
		}
		if (changedRegion.getOffset() >= oldRootNode.getTotalLength() && changedRegion.getText().trim().length() == 0) {
			return fullyReparse(parser, previousParseResult, changedRegion);
		}
		ReplaceRegion replaceRegion = tokenRegionProvider.getTokenReplaceRegion(insertChangeIntoReplaceRegion(oldRootNode, changedRegion), changedRegion);
		if (isNullEdit(oldRootNode, replaceRegion)) {
			return previousParseResult;
		}
		PartialParsingPointers parsingPointers = calculatePartialParsingPointers(previousParseResult, replaceRegion.getOffset(), replaceRegion.getLength());
		List validReplaceRootNodes = parsingPointers.getValidReplaceRootNodes();
		ICompositeNode oldCompositeNode = null;
		String reparseRegion = "";
		for (int i = validReplaceRootNodes.size() - 1; i >= 0; --i) {
			oldCompositeNode = validReplaceRootNodes.get(i);
			if (!(oldCompositeNode instanceof SyntheticCompositeNode) && !isRangePartOfExceedingLookAhead((CompositeNode) oldCompositeNode, replaceRegion)) {
				boolean replaceAtEnd = oldCompositeNode.getTotalEndOffset() == replaceRegion.getEndOffset();
				reparseRegion = insertChangeIntoReplaceRegion(oldCompositeNode, replaceRegion);
				if (!"".equals(reparseRegion)) {
					if (!replaceAtEnd || !Character.isWhitespace(reparseRegion.charAt(reparseRegion.length() - 1))) {
						if (log.isDebugEnabled()) {
							log.debug("replace region: [" + oldCompositeNode.getTotalOffset() + " / length: " + oldCompositeNode.getTotalLength() + " of [" + oldRootNode.getTotalOffset() + " / lenght: " + oldRootNode.getTotalLength()+ "]");
						}
						break;	
					}
				}
			}
		}
		if (oldCompositeNode == null || reparseRegion.equals("") || oldCompositeNode == oldRootNode) {
			return fullyReparse(parser, previousParseResult, replaceRegion);
		}
		EObject entryRuleOrRuleCall = parsingPointers.findEntryRuleOrRuleCall(oldCompositeNode);
		IParseResult newParseResult = null;
		try {
			if (entryRuleOrRuleCall instanceof RuleCall)
				newParseResult = parser.parse((RuleCall)entryRuleOrRuleCall, new StringReader(reparseRegion), oldCompositeNode.getLookAhead());
			else
				newParseResult = parser.parse((ParserRule)entryRuleOrRuleCall, new StringReader(reparseRegion));
		} catch (ParseException exc) {
		}
		if (newParseResult == null || newParseResult.hasSyntaxErrors()) {
			// TODO: Should we reparse if the complete input was parsed?
			// on error fully reparse
			return fullyReparse(parser, previousParseResult, replaceRegion);
		}
		if (oldRootNode.equals(oldCompositeNode)) {
			unloadSemanticObject(previousParseResult.getRootASTElement());
			return newParseResult;
		}
		EObject oldSemanticParentElement = oldCompositeNode.getParent().getSemanticElement();
		EObject oldSemanticElement = null;
		if (oldCompositeNode.hasDirectSemanticElement()) {
			oldSemanticElement = oldCompositeNode.getSemanticElement();
		} else {
			List nodesEnclosingRegion = parsingPointers.getNodesEnclosingRegion();
			for (int i = nodesEnclosingRegion.size() - 1; i >= 0; --i) {
				ICompositeNode enclosingNode = nodesEnclosingRegion.get(i);
				if (enclosingNode == oldCompositeNode) {
					break;
				}
				if (enclosingNode.hasDirectSemanticElement())
					oldSemanticElement = enclosingNode.getSemanticElement();
			}
			if (oldSemanticElement == null)
				return fullyReparse(parser, previousParseResult, replaceRegion);
		}
		if (oldSemanticElement == oldSemanticParentElement) {
			throw new IllegalStateException("oldParent == oldElement");
		}
		if (oldSemanticParentElement != null) {
			EStructuralFeature feature = oldSemanticElement.eContainingFeature();
			if (feature == null)
				return fullyReparse(parser, previousParseResult, replaceRegion);
			if (feature.isMany()) {
				List featureValueList = (List) oldSemanticParentElement.eGet(feature);
				int index = featureValueList.indexOf(oldSemanticElement);
				unloadSemanticObject(oldSemanticElement);
				featureValueList.set(index, newParseResult.getRootASTElement());
			} else {
				unloadSemanticObject(oldSemanticElement);
				oldSemanticParentElement.eSet(feature, newParseResult.getRootASTElement());
			}
			((ParseResult) newParseResult).setRootASTElement(previousParseResult.getRootASTElement());
		} else {
			unloadSemanticObject(oldSemanticElement);
		}
		if (oldCompositeNode != oldRootNode) {
			nodeModelBuilder.replaceAndTransferLookAhead(oldCompositeNode, newParseResult.getRootNode());
			((ParseResult) newParseResult).setRootNode(oldRootNode);
			StringBuilder builder = new StringBuilder(oldRootNode.getText());
			replaceRegion.applyTo(builder);
			nodeModelBuilder.setCompleteContent(oldRootNode, builder.toString());
		} 
		return newParseResult;
	}
	
	private boolean isRangePartOfExceedingLookAhead(CompositeNode node, ReplaceRegion replaceRegion) {
		TreeIterator iterator = node.basicIterator();
		int lookAhead = node.getLookAhead();
		if (lookAhead == 0) {
			return false;
		}
		while(iterator.hasNext()) {
			AbstractNode child = iterator.next();
			if (child instanceof CompositeNode) {
				if (child.getTotalOffset() < replaceRegion.getEndOffset())
					lookAhead = Math.max(((CompositeNode) child).getLookAhead(), lookAhead);
			} else if (!((ILeafNode) child).isHidden()) {
				lookAhead--;
				if (lookAhead == 0) {
					if (child.getTotalOffset() >= replaceRegion.getEndOffset())
						return false;
				}
			}
		}
		return lookAhead > 0;
	}
	
	private boolean isNullEdit(INode oldRootNode, ReplaceRegion replaceRegion) {
		if (replaceRegion.getLength() == replaceRegion.getText().length()) {
			String replacedText = oldRootNode.getText().substring(replaceRegion.getOffset(), replaceRegion.getEndOffset());
			if (replaceRegion.getText().equals(replacedText)) {
				return true;
			}
		}
		return false;
	}

	protected IParseResult fullyReparse(IParser parser, IParseResult previousParseResult, ReplaceRegion replaceRegion) {
		unloadSemanticObject(previousParseResult.getRootASTElement());
		String reparseRegion = insertChangeIntoReplaceRegion(previousParseResult.getRootNode(), replaceRegion);
		return parser.parse(new StringReader(reparseRegion));
	}

	public void unloadNode(INode node) {
		if (node != null) {
			EObject semantic = node.getSemanticElement();
			unloadSemanticObject(semantic);
		}
	}

	public void unloadSemanticObject(EObject object) {
		if (unloader != null && object != null)
			unloader.unloadRoot(object);
	}

	public String insertChangeIntoReplaceRegion(ICompositeNode rootNode, ReplaceRegion region) {
		final StringBuilder builder = new StringBuilder(rootNode.getText());
		region.shiftBy(0-rootNode.getTotalOffset()).applyTo(builder);
		return builder.toString();
	}

	public PartialParsingPointers calculatePartialParsingPointers(IParseResult previousParseResult, final int offset,
			int replacedTextLength) {
		int myOffset = offset;
		int myReplacedTextLength = replacedTextLength;
		ICompositeNode oldRootNode = previousParseResult.getRootNode();
		if (myOffset == oldRootNode.getTotalLength() && myOffset != 0) {
			// newText is appended, so look for the last original character instead
			--myOffset;
			myReplacedTextLength = 1;
		}
		// include any existing parse errors
		Range range = new Range(myOffset, myReplacedTextLength + myOffset);
		if (previousParseResult.hasSyntaxErrors())
			range.mergeAllSyntaxErrors(oldRootNode);

		myOffset = range.getOffset();
		List nodesEnclosingRegion = collectNodesEnclosingChangeRegion(oldRootNode, range);
		List validReplaceRootNodes = internalFindValidReplaceRootNodeForChangeRegion(nodesEnclosingRegion, range);

		filterInvalidRootNodes(oldRootNode, validReplaceRootNodes);

		if (validReplaceRootNodes.isEmpty()) {
			validReplaceRootNodes = Collections.singletonList(oldRootNode);
		}
		return new PartialParsingPointers(oldRootNode, myOffset, myReplacedTextLength, validReplaceRootNodes, nodesEnclosingRegion);
	}

	protected void filterInvalidRootNodes(ICompositeNode oldRootNode, List validReplaceRootNodes) {
		ListIterator iter = validReplaceRootNodes.listIterator(validReplaceRootNodes.size());
		while (iter.hasPrevious()) {
			ICompositeNode candidate = iter.previous();
			if (isInvalidRootNode(oldRootNode, candidate))
				iter.remove();
			else
				return;
		}
	}

	protected boolean isInvalidRootNode(ICompositeNode rootNode, ICompositeNode candidate) {
		int endOffset = candidate.getTotalEndOffset();
		if (candidate instanceof SyntheticCompositeNode)
			return true;
		if (candidate.getGrammarElement() instanceof RuleCall) {
			AbstractRule rule = ((RuleCall) candidate.getGrammarElement()).getRule();
			if (!(rule instanceof ParserRule) || GrammarUtil.isDatatypeRule((ParserRule) rule))
				return true;
			else if (isInvalidDueToPredicates((AbstractElement) candidate.getGrammarElement()))
				return true;
		}
		if (candidate.getGrammarElement() instanceof Action) {
			return true;
		}
		if (endOffset == rootNode.getTotalEndOffset()) {
			INode lastChild = getLastChild(candidate);
			if (lastChild instanceof ICompositeNode) {
				INode lastLeaf = getLastLeaf(candidate);
				if (isInvalidLastChildNode(candidate, lastLeaf)) {
					return true;
				}
			}
			if (isInvalidLastChildNode(candidate, lastChild)) {
				return true;
			}
		}
		return false;
	}


	/**
	 * @since 2.3
	 */
	protected boolean isInvalidDueToPredicates(AbstractElement element) {
//		if(element.isPredicated()) 
//			return true;
//		else if(element instanceof RuleCall) {
//			AbstractRule rule = ((RuleCall) element).getRule();
//			if(rule.getAlternatives() instanceof Group) {
//				boolean result = isInvalidDueToPredicates(((Group) rule.getAlternatives()).getElements().get(0));
//				return result;
//			}
//		}
		return false;
	}
	
	protected boolean isInvalidLastChildNode(ICompositeNode candidate, INode lastChild) {
		if (lastChild != null && lastChild.getSyntaxErrorMessage() != null) {
			EObject lastChildGrammarElement = lastChild.getGrammarElement();
			if (lastChildGrammarElement == null)
				return true;
			AbstractElement candidateElement = getCandidateElement(candidate.getGrammarElement());
			if (candidateElement != null) {
				if (isCalledBy(lastChildGrammarElement, candidateElement)) {
					while(candidate != null) {
						if (candidateElement != null && hasMandatoryFollowElements(candidateElement))
							return true;
						candidate = candidate.getParent();
						if (candidate != null)
							candidateElement = getCandidateElement(candidate.getGrammarElement());
					}
				}
				return true;
			}
		}
		return false;
	}

	private boolean isCalledBy(final EObject child, AbstractElement parent) {
		return new XtextSwitch() {
			private final Set rules = new HashSet(4);

			@Override
			public Boolean caseCompoundElement(CompoundElement object) {
				for (AbstractElement elem : object.getElements()) {
					if (doSwitch(elem))
						return true;
				}
				return false;
			}

			@Override
			public Boolean caseAbstractElement(AbstractElement object) {
				return object == child;
			}

			@Override
			public Boolean caseRuleCall(RuleCall object) {
				return object == child || doSwitch(object.getRule());
			}

			@Override
			public Boolean caseAbstractRule(AbstractRule object) {
				return object == child;
			}

			@Override
			public Boolean caseParserRule(ParserRule object) {
				return object == child || (rules.add(object) && doSwitch(object.getAlternatives()));
			}
		}.doSwitch(parent);
	}

	private boolean hasMandatoryFollowElements(AbstractElement lastParsedElement) {
		if (lastParsedElement.eContainer() instanceof AbstractElement) {
			AbstractElement directParent = (AbstractElement) lastParsedElement.eContainer();
			if (directParent instanceof Group) {
				Group group = (Group) directParent;
				int idx = group.getElements().indexOf(lastParsedElement) + 1;
				for (int i = idx; i < group.getElements().size(); i++) {
					if (isMandatory(group.getElements().get(i)))
						return true;
				}
			}
			return hasMandatoryFollowElements(directParent);
		}
		return false;
	}

	private boolean isMandatory(AbstractElement element) {
		return new XtextSwitch() {
			private final Set rules = new HashSet(4);

			@Override
			public Boolean caseAction(Action object) {
				return false;
			}

			@Override
			public Boolean caseCompoundElement(CompoundElement object) {
				if (GrammarUtil.isOptionalCardinality(object))
					return false;
				for (AbstractElement child : object.getElements()) {
					if (doSwitch(child)) {
						return true;
					}
				}
				return false;
			}

			@Override
			public Boolean caseAlternatives(Alternatives object) {
				if (GrammarUtil.isOptionalCardinality(object))
					return false;
				for (AbstractElement child : object.getElements()) {
					if (!doSwitch(child)) {
						return false;
					}
				}
				return true;
			}

			@Override
			public Boolean caseAbstractElement(AbstractElement object) {
				return !GrammarUtil.isOptionalCardinality(object);
			}

			@Override
			public Boolean caseRuleCall(RuleCall object) {
				return !GrammarUtil.isOptionalCardinality(object) || doSwitch(object.getRule());
			}

			@Override
			public Boolean caseAbstractRule(AbstractRule object) {
				return true;
			}

			@Override
			public Boolean caseParserRule(ParserRule object) {
				return rules.add(object) && doSwitch(object.getAlternatives());
			}
		}.doSwitch(element);
	}

	private AbstractElement getCandidateElement(EObject grammarElement) {
		if (grammarElement instanceof AbstractElement)
			return (AbstractElement) grammarElement;
		return null;
	}

	private INode getLastChild(ICompositeNode parent) {
		BidiTreeIterator iterator = parent.getAsTreeIterable().iterator();
		while(iterator.hasPrevious()) {
			INode previous = iterator.previous();
			if (previous instanceof ILeafNode) {
				return previous;
			} else if (previous instanceof ICompositeNode) {
				if (!((ICompositeNode) previous).hasChildren())
					return previous;
			}
		}
		return parent;
	}
	
	private INode getLastLeaf(ICompositeNode parent) {
		BidiTreeIterator iterator = parent.getAsTreeIterable().iterator();
		while(iterator.hasPrevious()) {
			INode previous = iterator.previous();
			if (previous instanceof ILeafNode) {
				return previous;
			} 
		}
		return null;
	}

	/**
	 * Collects a list of all nodes containing the change region
	 */
	private List collectNodesEnclosingChangeRegion(ICompositeNode parent, Range range) {
		List nodesEnclosingRegion = new ArrayList();
		if (nodeEnclosesRegion(parent, range)) {
			collectNodesEnclosingChangeRegion(parent, range, nodesEnclosingRegion);
		}
		return nodesEnclosingRegion;
	}

	private void collectNodesEnclosingChangeRegion(ICompositeNode parent, Range range,
			List nodesEnclosingRegion) {
		nodesEnclosingRegion.add(parent);
		BidiIterator iterator = parent.getChildren().iterator();
		while(iterator.hasPrevious()) {
			INode prev = iterator.previous();
			if (prev instanceof ICompositeNode) {
				if (nodeEnclosesRegion((ICompositeNode) prev, range)) {
					collectNodesEnclosingChangeRegion((ICompositeNode) prev, range, nodesEnclosingRegion);
					break;
				}
			}
		}
	}

	protected boolean nodeEnclosesRegion(ICompositeNode node, Range range) {
		boolean result = node.getTotalOffset() <= range.getOffset() && node.getTotalEndOffset() >= range.getEndOffset();
		return result;
	}

	/**
	 * Investigates the composite nodes containing the changed region and collects a list of nodes which could possibly
	 * replaced by a partial parse. Such a node has a parent that consumes all his current lookahead tokens and all of
	 * these tokens are located before the changed region.
	 */
	private List internalFindValidReplaceRootNodeForChangeRegion(
			List nodesEnclosingRegion, Range range) {
		List result = new ArrayList();
		boolean mustSkipNext = false;
		ICompositeNode previous = null;
		/*
		 * set to 'true' as soon as the lookahead of an enclosing
		 * exceeds the given range
		 */
		boolean done = false;  
		for (int i = 0; i < nodesEnclosingRegion.size() && !done; i++) {
			ICompositeNode node = nodesEnclosingRegion.get(i);
			if (node.getGrammarElement() != null) {
				if (!mustSkipNext) {
					boolean process = true;
					if (previous != null && !node.hasNextSibling()) {
						if (previous.getLookAhead() == node.getLookAhead() && previous.getLookAhead() == 0) {
							process = false;
						}
					}
					EObject semanticElement = NodeModelUtils.findActualSemanticObjectFor(node);
					if (semanticElement != null) {
						ICompositeNode actualNode = NodeModelUtils.findActualNodeFor(semanticElement);
						if (actualNode != null && (actualNode.getTotalOffset() < node.getTotalOffset() || actualNode.getTotalEndOffset() > node.getTotalEndOffset()))
							process = false;
					}
					if (process) {
						int remainingLookAhead = node.getLookAhead();
						if (remainingLookAhead != 0) {
							Iterator iterator = node.getLeafNodes().iterator();
							while(iterator.hasNext() && remainingLookAhead > 0) {
								ILeafNode leaf = iterator.next();
								if (!leaf.isHidden()) {
									if (remainingLookAhead > 0)
										remainingLookAhead--;
									if (remainingLookAhead == 0) {
										if (leaf.getTotalEndOffset() <= range.getOffset()) {
											result.add(node);
											previous = node;
											if (isActionNode(node)) {
												mustSkipNext = true;
											}
											break;
										} else {
											// lookahead ends left of the range, don't dive into child nodes
											done = true;
										}
									}
								}
							}
							if (remainingLookAhead != 0) {
								done = true;
							}
						} else {
							result.add(node);
							previous = node;
							if (isActionNode(node)) {
								mustSkipNext = true;
							}
						}
					}
				} else { // !mustSkipNext
					mustSkipNext = isActionNode(node);
				}
			}
		}
		return result;
	}

	protected boolean isActionNode(ICompositeNode node) {
		return node.getGrammarElement() != null && node.getGrammarElement().eClass() == XtextPackage.Literals.ACTION;
	}
	
	public void setUnloader(IReferableElementsUnloader unloader) {
		this.unloader = unloader;
	}

	public IReferableElementsUnloader getUnloader() {
		return unloader;
	}
	
	/**
	 * @since 2.3
	 */
	public void setTokenRegionProvider(TokenRegionProvider tokenRegionProvider) {
		this.tokenRegionProvider = tokenRegionProvider;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy