/*******************************************************************************
* Copyright (c) 2007, 2008 Edgar Espina.
* 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.deved.antlride.internal.core.codeassit;
import java.util.ArrayList;
import java.util.Collection;
import org.deved.antlride.core.AntlrCore;
import org.deved.antlride.core.build.AntlrSourceParserRepository;
import org.deved.antlride.core.model.ElementKind;
import org.deved.antlride.core.model.ICallExpression;
import org.deved.antlride.core.model.IGrammar;
import org.deved.antlride.core.model.IModelElement;
import org.deved.antlride.core.model.IOption;
import org.deved.antlride.core.model.IOptions;
import org.deved.antlride.core.model.IReference;
import org.deved.antlride.core.model.IRule;
import org.deved.antlride.core.model.IScope;
import org.deved.antlride.core.model.IStatement;
import org.deved.antlride.core.model.ITargetAction;
import org.deved.antlride.core.model.RuleType;
import org.deved.antlride.core.model.ast.AntlrModelElementLocator;
import org.deved.antlride.core.model.ast.ModelElementQuery;
import org.deved.antlride.core.util.AntlrTextHelper;
import org.deved.antlride.internal.core.model.dltk.FakeField;
import org.deved.antlride.internal.core.model.dltk.FakeMethod;
import org.eclipse.dltk.codeassist.ScriptCompletionEngine;
import org.eclipse.dltk.compiler.env.IModuleSource;
import org.eclipse.dltk.core.CompletionProposal;
import org.eclipse.dltk.core.IField;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
public class AntlrCompletionEngine extends ScriptCompletionEngine {
private ISourceModule currentModule;
private static final int OPTION_RELEVANCE = 424241;
private static final int RULE_RELEVANCE = 424242;
private static final int VARIABLE_REFERENCE_RELEVANCE = 424251;
public AntlrCompletionEngine() {
}
public void complete(
IModuleSource envSourceModule,
int position, int i) {
this.requestor.beginReporting();
this.actualCompletionPosition = position;
try {
currentModule = (ISourceModule) envSourceModule;
IGrammar grammar = AntlrSourceParserRepository
.getGrammar(currentModule);
char[] source = currentModule.getSourceAsCharArray();
AntlrModelElementLocator locator = new AntlrModelElementLocator(
grammar);
IModelElement element = locator.getElementAt(position - 1);
if (element == null)
return;
if (element.getElementKind() == ElementKind.REFERENCE
|| element.getElementKind() == ElementKind.CALL_PARAMETER) {
element = element.getParent();
}
// complete findRule
if (element instanceof IStatement
|| element.getElementKind() == ElementKind.CALL_PARAMETERS) {
completeRule(element, position, source);
} else {
ElementKind kind = element.getElementKind();
if (kind == ElementKind.GRAMMAR_OPTIONS
|| kind.isChildOf(ElementKind.GRAMMAR_OPTIONS)
|| kind == ElementKind.RULE_OPTIONS
|| kind.isChildOf(ElementKind.RULE_OPTIONS)) {
IOptions options = element.getAdapter(IOptions.class);
completeOptions(options, position, source);
}
}
} catch (Throwable t) {
if(AntlrCore.DEBUG)
t.printStackTrace();
} finally {
this.requestor.endReporting();
}
}
private void completeOptions(IOptions options, int position, char[] source) {
// source range information
String prefix = AntlrTextHelper.findWord(source, position - 1, false);
String word = AntlrTextHelper.findWord(source, position - 1, true);
int start = position - prefix.length();
int end = start + word.length();
setSourceRange(start, end);
String[] missingOptions = getOptions(options);
for (String string : missingOptions) {
FakeField field = new FakeField(currentModule, string);
reportOption(field);
}
}
private String[] getOptions(IOptions options) {
String[] result = null;
switch (options.getElementKind()) {
case GRAMMAR_OPTIONS:
result = IOptions.EOptions.getMissingGrammarOptions(options);
break;
case RULE_OPTIONS:
case BLOCK_OPTIONS:
result = IOptions.EOptions.getMissingBlockOptions(options);
break;
}
return result;
}
private boolean match(char[] source, int position, char ch) {
try {
return source[position] == ch;
} catch (Exception ex) {
}
return false;
}
private void completeRule(IModelElement element, int position, char[] source) {
ElementKind kind = ElementKind.RULE;
// source range information
String prefix = AntlrTextHelper.findWord(source, position - 1, false);
String word = AntlrTextHelper.findWord(source, position - 1, true);
String refName = null;
int start = position - prefix.length();
int end = start + word.length();
int pos = position - prefix.length() - 1;
setSourceRange(start, end);
// completation kind
if (element.getElementKind() == ElementKind.BLOCK
|| element.getElementKind() == ElementKind.ALTERNATIVE) {
kind = ElementKind.CALL;
}
if (match(source, pos, '$')) {
kind = ElementKind.VARIABLE;
} else if (match(source, pos, '.')) {
refName = AntlrTextHelper.findWord(source, pos - 1, false);
kind = ElementKind.RULE_REFERENCE;
} else if (match(source, pos, ':') && match(source, pos - 1, ':')) {
refName = AntlrTextHelper.findWord(source, pos - 2, false);
kind = ElementKind.SCOPE_REFERENCE;
}
// complete
switch (kind) {
case VARIABLE:
completeVariableReference(element, prefix);
break;
case CALL_PARAMETERS:
case RULE_REFERENCE: {
IReference reference = findReference(element, refName);
completeRuleProperties(reference, prefix);
}
break;
case SCOPE_REFERENCE: {
IReference reference = findReference(element, refName);
completeScopeAttributes(reference, prefix);
}
break;
default:
completeRuleCall((IStatement) element, source, position, prefix);
break;
}
}
private IReference findReference(IModelElement statement,
String referenceName) {
IReference reference = null;
ITargetAction targetAction = statement.getAdapter(ITargetAction.class);
if (targetAction != null) {
IReference[] references = targetAction.getReferences();
for (int i = 0; i < references.length; i++) {
if (references[i].getElementName().equals(referenceName)) {
reference = references[i];
break;
}
}
}
return reference;
}
private void completeRuleProperties(IReference reference, String prefix) {
if (reference == null)
return;
ITargetAction targetAction = reference.getAdapter(ITargetAction.class);
ICallExpression callExpression = reference
.getAdapter(ICallExpression.class);
IRule rule = reference.getAdapter(IRule.class);// enclosing rule
IRule referenedRule = null;
if (callExpression != null) {
IGrammar grammar = callExpression.getAdapter(IGrammar.class);
referenedRule = grammar.findRule(callExpression.getRuleName()
.getText());
} else {
if (rule != null
&& reference.getElementName().equals(rule.getElementName())) {
referenedRule = rule;// same as enclosing rule
}
}
if (referenedRule != null) {
Collection<String> properties = getProperties(referenedRule,
targetAction, rule.getElementName());
for (String property : properties) {
reportVariableDeclaration(new FakeField(currentModule, property));
}
}
}
private Collection<String> getProperties(IRule rule,
ITargetAction targetAction, String referenceName) {
boolean after = false;
boolean sameRule = referenceName.equals(rule.getElementName());
IGrammar grammar = (IGrammar) rule.getParent();
String output = "";
Collection<String> properties = new ArrayList<String>(5);
properties.add("text");
// @after
IModelElement parent = targetAction.getParent();
if (parent.getElementKind() == ElementKind.RULE_ACTION) {
after = parent.getElementName().equals("after");
}
// output=?
if (grammar.hasOptions()) {
IOption option = grammar.findOption(IOptions.OUTPUT);
if (option != null && option.getValue() != null) {
output = option.getValue().getText();
}
}
// rule type
RuleType ruleType = rule.getRuleType();
switch (ruleType) {
case LEXER:
properties.add("type");
properties.add("line");
properties.add("pos");
properties.add("index");
properties.add("channel");
break;
case PARSER:
case TREE_PARSER:
properties.add("start");
if (output.equals("AST")) {
if (after || !sameRule)
properties.add("tree");
} else if (output.equals("template")) {
if (after || !sameRule)
properties.add("st");
}
if (ruleType != RuleType.TREE_PARSER) {
if (after || !sameRule)
properties.add("stop");
}
break;
}
return properties;
}
private void completeScopeAttributes(IReference reference, String prefix) {
if (reference != null) {
IScope scope = reference.getAdapter(IScope.class);
if (scope != null) {
IModelElement[] elements = ModelElementQuery.collectScopeAttributes(scope, prefix);
for (IModelElement e : elements) {
reportVariableDeclaration(new FakeField(currentModule, e));
}
}
}
}
private void completeVariableReference(IModelElement element, String prefix) {
IRule rule = element.getAdapter(IRule.class);
ITargetAction targetAction = element.getAdapter(ITargetAction.class);
IModelElement[] elements = ModelElementQuery.collectLocalReferences(rule, actualCompletionPosition - 1, prefix);
for (IModelElement e : elements) {
if (e.getElementKind() == ElementKind.CALL) {
if (e.getParent().getElementKind() == ElementKind.ASSIGN_OPERATOR) {
continue;
}
}
IField method = new FakeField(currentModule, e);
reportVariableDeclaration(method);
}
if (targetAction != null) {
Collection<String> properties = getProperties(rule,
targetAction, rule.getElementName());
for (String property : properties) {
if (property.startsWith(prefix))
reportVariableDeclaration(new FakeField(currentModule,
property));
}
}
if (rule.getElementName().startsWith(prefix) && !rule.hasScopes())
reportVariableDeclaration(new FakeField(currentModule, rule));
}
private void completeRuleCall(IStatement statement, char[] source,
int position, String prefix) {
if (statement == null
|| statement.getElementKind() == ElementKind.TARGET_ACTION)
// ignore rule auto completation in target action
return;
IRule rule = statement.getAdapter(IRule.class);
if (position < rule.getBody().sourceStart()) {
// :
return;
}
IGrammar grammar = rule.getAdapter(IGrammar.class);
IModelElement[] elements = ModelElementQuery.collectRules(grammar, prefix);
for (IModelElement e : elements) {
IMethod method = new FakeMethod(currentModule, e);
reportRuleReference(method, RULE_RELEVANCE);
}
}
private void reportOption(IField field) {
String elementName = field.getElementName();
// accept result
noProposal = false;
if (!requestor.isIgnored(CompletionProposal.LABEL_REF)) {
CompletionProposal proposal = createProposal(
CompletionProposal.LABEL_REF, actualCompletionPosition);
proposal.setModelElement(field);
proposal.setName(elementName);
proposal.setCompletion(elementName);
// proposal.setFlags(Flags.AccDefault);
proposal.setReplaceRange(this.startPosition - this.offset,
this.endPosition - this.offset);
proposal.setRelevance(OPTION_RELEVANCE);
this.requestor.accept(proposal);
}
}
private void reportVariableDeclaration(IField field) {
String elementName = field.getElementName();
// accept result
noProposal = false;
if (!requestor.isIgnored(CompletionProposal.LOCAL_VARIABLE_REF)) {
CompletionProposal proposal = createProposal(
CompletionProposal.LOCAL_VARIABLE_REF,
actualCompletionPosition);
proposal.setModelElement(field);
proposal.setName(elementName);
proposal.setCompletion(elementName);
// proposal.setFlags(Flags.AccDefault);
proposal.setReplaceRange(this.startPosition - this.offset,
this.endPosition - this.offset);
proposal.setRelevance(VARIABLE_REFERENCE_RELEVANCE);
this.requestor.accept(proposal);
}
}
private void reportRuleReference(IMethod method, int relevance) {
String elementName = method.getElementName();
if (elementName.indexOf('.') != -1) {
elementName = elementName.substring(elementName.indexOf('.') + 1);
}
// accept result
noProposal = false;
if (!requestor.isIgnored(CompletionProposal.METHOD_DECLARATION)) {
CompletionProposal proposal = createProposal(
CompletionProposal.METHOD_DECLARATION,
actualCompletionPosition);
String[] params = null;
try {
params = method.getParameterNames();
} catch (ModelException e) {
// ANTLR3Plugin.error(e);
}
if (params != null && params.length > 0) {
proposal.setParameterNames(params);
}
proposal.setModelElement(method);
proposal.setName(elementName);
proposal.setCompletion(elementName);
proposal.setReplaceRange(this.startPosition - this.offset,
this.endPosition - this.offset);
proposal.setRelevance(relevance);
this.requestor.accept(proposal);
}
}
@Override
protected int getEndOfEmptyToken() {
return 0;
}
@Override
protected String processMethodName(IMethod method, String token) {
return method.getElementName();
}
@Override
protected String processTypeName(IType method, String token) {
return null;
}
}