package org.xpect.xtext.lib.util;
import java.util.Collections;
import java.util.List;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractMetamodelDeclaration;
import org.eclipse.xtext.AbstractNegatedToken;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Alternatives;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.NegatedToken;
import org.eclipse.xtext.ReferencedMetamodel;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.UntilToken;
import org.eclipse.xtext.common.types.TypesPackage;
import com.google.common.collect.Lists;
public class GrammarAnalyzer {
// copy of org.eclipse.xtext.xbase.XbasePackage.eNS_URI
private final String XbasePackageNS_URI = "http://www.eclipse.org/xtext/xbase/Xbase";
// be aware, that syntactically one TerminalRule can declare multiple
// CommentRules.
public static class CommentRule {
private final String start;
public CommentRule(String start) {
this.start = start;
}
public String getStart() {
return start;
}
}
public static class MLCommentRule extends CommentRule {
private final String end;
public MLCommentRule(String start, String end) {
super(start);
this.end = end;
}
public String getEnd() {
return end;
}
@Override
public String toString() {
return getClass().getSimpleName() + ": '" + getStart() + "' -> '" + getEnd() + "'";
}
}
public static class SLCommentRule extends CommentRule {
public SLCommentRule(String start) {
super(start);
}
@Override
public String toString() {
return getClass().getSimpleName() + ": '" + getStart() + "' -> '\n'";
}
}
public enum XtextLanguageKind {
GENERAL, JAVA, XBASE
}
private List<CommentRule> commentRules;
private final Grammar grammar;
public GrammarAnalyzer(Grammar grammar) {
this.grammar = grammar;
}
protected List<String> collectChars(AbstractElement ele) {
ele = resolve(ele);
if (GrammarUtil.isOptionalCardinality(ele))
return Collections.emptyList();
if (ele instanceof Keyword) {
String value = ((Keyword) ele).getValue();
if (!"\r".equals(value)) // it suffices to handle \n
return Collections.singletonList(value);
}
if (ele instanceof Group) {
return collectChars((Group) ele, 0, ((Group) ele).getElements().size());
}
if (ele instanceof Alternatives) {
List<String> current = Lists.newArrayList();
for (AbstractElement e : ((Alternatives) ele).getElements())
current.addAll(collectChars(e));
return current;
}
return Collections.emptyList();
}
protected List<String> collectChars(Group ele, int from, int to) {
List<String> last = Lists.newArrayList("");
List<String> current = Lists.newArrayList();
for (int i = from; i < to; i++) {
for (String l : last)
for (String append : collectChars(ele.getElements().get(i)))
current.add(l + append);
last = current;
current = Lists.newArrayList();
}
return last;
}
protected void collectCommentRules(AbstractElement ele, List<CommentRule> rules) {
ele = resolve(ele);
if (ele instanceof Group) {
Group group = (Group) ele;
AbstractNegatedToken negated = null;
for (AbstractElement child : group.getElements())
if (child instanceof AbstractNegatedToken) {
if (negated == null)
negated = (AbstractNegatedToken) child;
else
return;
}
if (negated instanceof NegatedToken) {
int index = group.getElements().indexOf(negated);
if (index > 0 && index < group.getElements().size() - 1) {
List<String> starts = collectChars(group, 0, index);
List<String> ends = collectChars(group, index + 1, group.getElements().size());
if (ends.isEmpty()) {
for (String start : starts)
rules.add(new SLCommentRule(start));
} else {
for (String start : starts)
for (String end : ends)
if (end.contains("\n"))
rules.add(new SLCommentRule(start));
else
rules.add(new MLCommentRule(start, end));
}
}
} else if (negated instanceof UntilToken) {
int index = group.getElements().indexOf(negated);
if (index == group.getElements().size() - 1) {
List<String> starts = collectChars(group, 0, index);
List<String> ends = collectChars(((UntilToken) negated).getTerminal());
for (String start : starts) {
for (String end : ends)
rules.add(new MLCommentRule(start, end));
}
}
}
} else if (ele instanceof Alternatives) {
for (AbstractElement e : ((Alternatives) ele).getElements())
collectCommentRules(e, rules);
}
}
public List<CommentRule> getCommentRules() {
if (this.commentRules == null) {
this.commentRules = Lists.newArrayList();
Grammar grammarWithHiddenTokens = grammar;
while (!grammarWithHiddenTokens.isDefinesHiddenTokens() && !grammarWithHiddenTokens.getUsedGrammars().isEmpty())
grammarWithHiddenTokens = grammarWithHiddenTokens.getUsedGrammars().get(0);
if (grammarWithHiddenTokens.isDefinesHiddenTokens())
for (AbstractRule rule : grammarWithHiddenTokens.getHiddenTokens())
if (rule instanceof TerminalRule)
collectCommentRules(rule.getAlternatives(), this.commentRules);
}
return this.commentRules;
}
public XtextLanguageKind getLanguageKind() {
List<Grammar> grammars = Lists.newArrayList(grammar);
grammars.addAll(GrammarUtil.allUsedGrammars(grammar));
boolean xbase = false;
boolean java = false;
for (Grammar g : grammars)
for (AbstractMetamodelDeclaration decl : g.getMetamodelDeclarations())
if (decl instanceof ReferencedMetamodel) {
if (decl.getEPackage().getNsURI().equals(XbasePackageNS_URI))
xbase = true;
else if (decl.getEPackage().getNsURI().equals(TypesPackage.eNS_URI))
java = true;
}
if (xbase)
return XtextLanguageKind.XBASE;
if (java)
return XtextLanguageKind.JAVA;
return XtextLanguageKind.GENERAL;
}
protected AbstractElement resolve(AbstractElement ele) {
if (ele instanceof RuleCall) {
AbstractRule rule = ((RuleCall) ele).getRule();
if (rule instanceof TerminalRule && ((TerminalRule) rule).isFragment())
return rule.getAlternatives();
}
return ele;
}
}