/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* 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
*
* Contributors:
* Puppet Labs
*/
package com.puppetlabs.xtext.serializer.acceptor;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.ACTION;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.COMMENT;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.DATATYPE;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.ENUM;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.KEYWORD;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.RULECALL;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.TERMINAL;
import static com.puppetlabs.xtext.dommodel.IDomNode.NodeType.WHITESPACE;
import java.util.List;
import com.puppetlabs.xtext.dommodel.DomModelUtils;
import com.puppetlabs.xtext.dommodel.IDomNode;
import com.puppetlabs.xtext.dommodel.IDomNode.NodeClassifier;
import com.puppetlabs.xtext.dommodel.IDomNode.NodeType;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentConfiguration;
import com.puppetlabs.xtext.dommodel.impl.BaseDomNode;
import com.puppetlabs.xtext.dommodel.impl.CompositeDomNode;
import com.puppetlabs.xtext.dommodel.impl.LeafDomNode;
import org.eclipse.xtext.formatting.ILineSeparatorInformation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.parsetree.reconstr.IHiddenTokenHelper;
import org.eclipse.xtext.serializer.acceptor.ISequenceAcceptor;
import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* TODO: (list of things to discuss)
* - Calls that pass a data object and an index
* - Initialization (the Serializer calls init depending on implementation class).
* Suggest adding init(EObject context, EObject semantic) to ISequenceAcceptor.
*
*/
public class DomModelSequenceAdapter implements ISequenceAcceptor {
protected ISerializationDiagnostic.Acceptor errorAcceptor;
private CompositeDomNode current;
private List<CompositeDomNode> stack;
protected IHiddenTokenHelper hiddenTokenHelper;
Function<AbstractRule, String> ruleToName = new Function<AbstractRule, String>() {
@Override
public String apply(AbstractRule from) {
return from.getName();
}
};
/**
* Required to encode certain facts about comments in a generic way
*/
ILineSeparatorInformation lineSeparatorInformation;
ICommentConfiguration<?> commentConfiguration;
@Inject
public DomModelSequenceAdapter(IHiddenTokenHelper hiddenTokenHelper, ICommentConfiguration<?> commentConfiguration,
ILineSeparatorInformation lineSeparatorInformation, ISerializationDiagnostic.Acceptor errorAcceptor) {
current = new CompositeDomNode();
stack = Lists.newArrayList();
this.errorAcceptor = errorAcceptor;
this.hiddenTokenHelper = hiddenTokenHelper;
this.lineSeparatorInformation = lineSeparatorInformation;
this.commentConfiguration = commentConfiguration;
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedCrossRefDatatype(org.eclipse.xtext.RuleCall,
* java.lang.String, org.eclipse.emf.ecore.EObject, int, org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public void acceptAssignedCrossRefDatatype(RuleCall datatypeRC, String token, EObject value, int index,
ICompositeNode node) {
addCompositeNodeToCurrent(GrammarUtil.containingCrossReference(datatypeRC), token, node, DATATYPE, //
NodeClassifier.CROSSREF, NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedCrossRefEnum(org.eclipse.xtext.RuleCall, java.lang.String,
* org.eclipse.emf.ecore.EObject, int, org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public void acceptAssignedCrossRefEnum(RuleCall enumRC, String token, EObject value, int index, ICompositeNode node) {
addCompositeNodeToCurrent(GrammarUtil.containingCrossReference(enumRC), token, node, ENUM, //
NodeClassifier.CROSSREF, NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedCrossRefKeyword(org.eclipse.xtext.Keyword, java.lang.String,
* org.eclipse.emf.ecore.EObject, int, org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptAssignedCrossRefKeyword(Keyword kw, String token, EObject value, int index, ILeafNode node) {
addLeafNodeToCurrent(GrammarUtil.containingCrossReference(kw), token, node, KEYWORD, //
NodeClassifier.CROSSREF, NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedCrossRefTerminal(org.eclipse.xtext.RuleCall,
* java.lang.String, org.eclipse.emf.ecore.EObject, int, org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptAssignedCrossRefTerminal(RuleCall terminalRC, String token, EObject value, int index,
ILeafNode node) {
addLeafNodeToCurrent(GrammarUtil.containingCrossReference(terminalRC), token, node, TERMINAL, //
NodeClassifier.CROSSREF, NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedDatatype(org.eclipse.xtext.RuleCall, java.lang.String,
* java.lang.Object, int, org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public void acceptAssignedDatatype(RuleCall datatypeRC, String token, Object value, int index, ICompositeNode node) {
addCompositeNodeToCurrent(datatypeRC, token, node, DATATYPE, //
NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedEnum(org.eclipse.xtext.RuleCall, java.lang.String,
* java.lang.Object, int, org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public void acceptAssignedEnum(RuleCall enumRC, String token, Object value, int index, ICompositeNode node) {
addCompositeNodeToCurrent(enumRC, token, node, ENUM, //
NodeClassifier.ASSIGNED);
}
@Override
public void acceptAssignedKeyword(Keyword keyword, String token, Object value, int index, ILeafNode node) {
addLeafNodeToCurrent(keyword, token, node, KEYWORD, //
NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#acceptAssignedTerminal(org.eclipse.xtext.RuleCall, java.lang.String,
* java.lang.Object, int, org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptAssignedTerminal(RuleCall terminalRC, String token, Object value, int index, ILeafNode node) {
addLeafNodeToCurrent(terminalRC, token, node, TERMINAL, //
NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISequenceAcceptor#acceptComment(org.eclipse.xtext.AbstractRule, java.lang.String,
* org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptComment(AbstractRule rule, String token, ILeafNode node) {
// It is of value to know if a comment extends over multiple lines or if it ends with a line separator
//
final String lineSep = lineSeparatorInformation.getLineSeparator();
List<Object> commentClassification = Lists.newArrayList();
// generic, textual classification
if(token.endsWith(lineSep))
commentClassification.add(NodeClassifier.LINESEPARATOR_TERMINATED);
final int lineSepIx = token.indexOf(lineSep);
if(lineSepIx != -1 && lineSepIx != token.lastIndexOf(lineSep))
commentClassification.add(NodeClassifier.MULTIPLE_LINES);
// semantic comment classification
commentClassification.add(commentConfiguration.classify(rule));
addLeafNodeToCurrent(rule, token, node, COMMENT, commentClassification.toArray());
}
/**
* An unassigned rule call is not meaningful to keep in the DOM tree. It is basically information
* about the rule path taken to something concrete like a terminal.
* This implementation simply does nothing.
*
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#acceptUnassignedAction(org.eclipse.xtext.Action)
*/
@Override
public void acceptUnassignedAction(Action action) {
BaseDomNode n = addLeafNodeToCurrent(action, "", null, ACTION, //
NodeClassifier.INSTANTIATION);
n.setSemanticElement(action.getType().getClassifier());
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#acceptUnassignedDatatype(org.eclipse.xtext.RuleCall, java.lang.String,
* org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public void acceptUnassignedDatatype(RuleCall datatypeRC, String token, ICompositeNode node) {
addCompositeNodeToCurrent(datatypeRC, token, node, DATATYPE, //
NodeClassifier.UNASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#acceptUnassignedEnum(org.eclipse.xtext.RuleCall, java.lang.String,
* org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public void acceptUnassignedEnum(RuleCall enumRC, String token, ICompositeNode node) {
addCompositeNodeToCurrent(enumRC, token, node, ENUM, //
NodeClassifier.UNASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#acceptUnassignedKeyword(org.eclipse.xtext.Keyword, java.lang.String,
* org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptUnassignedKeyword(Keyword keyword, String token, ILeafNode node) {
addLeafNodeToCurrent(keyword, token, node, KEYWORD, //
NodeClassifier.ASSIGNED);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#acceptUnassignedTerminal(org.eclipse.xtext.RuleCall, java.lang.String,
* org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptUnassignedTerminal(RuleCall terminalRC, String token, ILeafNode node) {
addLeafNodeToCurrent(terminalRC, token, node, TERMINAL, //
NodeClassifier.UNASSIGNED);
}
/**
* TODO: When the ILeafNode is null, it is not possible to know if this is HIDDEN or not.
* Currently, HIDDEN is set if node is null, else this is controlled by node.isHidden().
*
* @see org.eclipse.xtext.serializer.acceptor.ISequenceAcceptor#acceptWhitespace(org.eclipse.xtext.AbstractRule, java.lang.String,
* org.eclipse.xtext.nodemodel.ILeafNode)
*/
@Override
public void acceptWhitespace(AbstractRule rule, String token, ILeafNode node) {
BaseDomNode n = addLeafNodeToCurrent(rule, token, node, WHITESPACE, //
NodeClassifier.HIDDEN);
// TODO: Not a very robust way of checking if the whitespace is implied
// Will probably need to change when also creating a dom model from the node model
//
n.setClassifiers(token == IDomNode.IMPLIED_EMPTY_WHITESPACE, NodeClassifier.IMPLIED);
}
protected BaseDomNode addCompositeNodeToCurrent(EObject rule, String token, ICompositeNode node, NodeType nodeType,
Object... classifiers) {
BaseDomNode result = new LeafDomNode();
result.setText(token);
result.setNode(node);
result.setGrammarElement(rule);
result.setNodeType(nodeType);
result.setClassifiers(true, classifiers);
addNodeToCurrent(result);
return result;
}
protected BaseDomNode addLeafNodeToCurrent(EObject rule, String token, ILeafNode node, NodeType nodeType,
Object... classifiers) {
BaseDomNode result = new LeafDomNode();
result.setText(token);
result.setNode(node);
result.setGrammarElement(rule);
result.setNodeType(nodeType);
result.setClassifiers(true, classifiers);
if(node != null && node.isHidden()) {
result.setClassifiers(true, NodeClassifier.HIDDEN);
}
addNodeToCurrent(result);
return result;
}
protected void addNodeToCurrent(BaseDomNode node) {
current.addChild(node);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#enterAssignedAction(org.eclipse.xtext.Action,
* org.eclipse.emf.ecore.EObject, org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public boolean enterAssignedAction(Action action, EObject semanticChild, ICompositeNode node) {
push();
current.setGrammarElement(action);
current.setSemanticElement(semanticChild);
current.setNode(node);
current.setNodeType(ACTION);
current.setClassifiers(true, NodeClassifier.ASSIGNED);
return true;
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#enterAssignedParserRuleCall(org.eclipse.xtext.RuleCall,
* org.eclipse.emf.ecore.EObject, org.eclipse.xtext.nodemodel.ICompositeNode)
*/
@Override
public boolean enterAssignedParserRuleCall(RuleCall rc, EObject semanticChild, ICompositeNode node) {
push();
current.setGrammarElement(rc);
current.setSemanticElement(semanticChild);
current.setNode(node);
current.setNodeType(RULECALL);
current.setClassifiers(true, NodeClassifier.ASSIGNED);
return true;
}
/**
* This information is not terribly useful in the DOM as it says that the grammar has performed
* a rule call and is now in a particular rule. Eventually the rule will result is something.
* This implementation simply does nothing.
*
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#enterUnassignedParserRuleCall(org.eclipse.xtext.RuleCall)
*/
@Override
public void enterUnassignedParserRuleCall(RuleCall rc) {
// push();
// current.setGrammarElement(rc);
// current.setSemanticElement(null);
// current.setNode(null);
// current.setNodeType(RULECALL);
// current.setClassifiers(true, NodeClassifier.UNASSIGNED);
// // For debug printout of entered rule with hidden
// AbstractRule r = rc.getRule();
// if(r instanceof ParserRule) {
// ParserRule pr = ((ParserRule) r);
// if(pr.isDefinesHiddenTokens()) {
// StringBuilder builder = new StringBuilder();
// builder.append("HIDDEN(");
// Joiner joiner = Joiner.on(", ");
// if(pr.getHiddenTokens().size() != 0)
// joiner.appendTo(builder, Iterables.transform(pr.getHiddenTokens(), ruleToName));
// builder.append(")");
// System.out.println(builder.toString());
// }
// }
// push();
// current.setGrammarElement(rc);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#finish()
*/
@Override
public void finish() {
if(stack.size() == 0) {
// TODO: Discuss this issue
// ensure there is a whitespace node at the very end (to enable defining a
// formatting rule for trailing WS)
IDomNode lastLeaf = DomModelUtils.lastLeaf(current);
if(lastLeaf != null) {
if(!DomModelUtils.isWhitespace(lastLeaf))
acceptWhitespace(hiddenTokenHelper.getWhitespaceRuleFor(null, ""), //
IDomNode.IMPLIED_EMPTY_WHITESPACE, null);
}
current.doLayout();
}
}
protected CompositeDomNode getCurrent() {
return current;
}
/**
* Returns the constructed Dom model. The returned IDomNode is always a composite node
*
* @return the constructed dom model root
*/
public IDomNode getDomModel() {
return current;
}
/**
* @param context
* @param semanticObject
*/
public void init(EObject context, EObject semanticObject) {
current.setGrammarElement(context);
current.setSemanticElement(semanticObject);
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#leaveAssignedAction(org.eclipse.xtext.Action,
* org.eclipse.emf.ecore.EObject)
*/
@Override
public void leaveAssignedAction(Action action, EObject semanticChild) {
pop();
}
/**
* @see org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor#leaveAssignedParserRuleCall(org.eclipse.xtext.RuleCall,
* org.eclipse.emf.ecore.EObject)
*/
@Override
public void leaveAssignedParserRuleCall(RuleCall rc, EObject semanticChild) {
pop();
}
/**
* Since the information about entry to a RuleCall is not recorded, neither is leaving it.
* This implementation does nothing.
* TODO: Method name is misspelled in the interface - report issue
*
* @see org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor#leaveUnssignedParserRuleCall(org.eclipse.xtext.RuleCall)
*/
@Override
public void leaveUnssignedParserRuleCall(RuleCall rc) {
// Skip these
// pop();
}
/**
* Current is added as a child to the top of the stack, and the top of the stack is made current.
* The stack is popped.
*
* @throws IndexOutOfBoundsException
* - if the calls to push/pop are not balanced and the stack is empty.
*/
protected void pop() {
try {
CompositeDomNode top = stack.remove(stack.size() - 1);
current = top;
}
catch(ArrayIndexOutOfBoundsException e) {
throw e;
}
}
/**
* Adds current to the stack, and creates a new empty CompositeDomNode as the current node.
*/
protected void push() {
stack.add(current);
CompositeDomNode newCurrent = new CompositeDomNode();
current.addChild(newCurrent);
current = newCurrent;
}
}