[#1626] Use node model to generate comments with original format.

Signed-off-by: Arne Deutsch <Arne.Deutsch@itemis.de>
This commit is contained in:
Arne Deutsch 2020-12-18 14:26:10 +01:00
parent b34bbe849a
commit 8ab3672c97
3 changed files with 160 additions and 57 deletions

View file

@ -75,14 +75,15 @@ public class GrammarAccessExtensions2Test {
"// | '>' '..'" + NL +
"// | '..'" + NL +
"// | '=>'" + NL +
"// | '>' (=> ('>' '>') | '>') | '<' (=> ('<' '<') | '<' | '=>') | '<>'" + NL +
"// | '>' (=>('>' '>') | '>')" + NL +
"// | '<' (=>('<' '<') | '<' | '=>')" + NL +
"// | '<>'" + NL +
"// | '?:';";
firstRuleIsConvertedTo(grammar, expected);
}
@Test
public void testGrammarFragmentToString2() throws Exception {
String NL = System.lineSeparator();
String grammar =
"grammar org.xtext.example.mydsl.MyDsl hidden (ML_COMMENT)\n" +
"import 'http://www.eclipse.org/emf/2002/Ecore' as ecore\n" +
@ -91,8 +92,7 @@ public class GrammarAccessExtensions2Test {
"terminal ML_COMMENT: '/*'(!'*')->'*/';\n" +
"terminal ID: '^'?('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;";
String expected =
"//terminal ML_COMMENT:" + NL
+ "// '/*' !'*'->'*/';";
"//terminal ML_COMMENT: '/*'(!'*')->'*/';";
secondRuleIsConvertedTo(grammar, expected);
}

View file

@ -8,9 +8,7 @@
*******************************************************************************/
package org.eclipse.xtext.xtext.generator.grammarAccess
import com.google.common.collect.Maps
import com.google.inject.Binder
import com.google.inject.Guice
import com.google.inject.Inject
import java.util.ArrayList
import java.util.List
@ -29,16 +27,17 @@ import org.eclipse.xtext.Grammar
import org.eclipse.xtext.GrammarUtil
import org.eclipse.xtext.Group
import org.eclipse.xtext.Keyword
import org.eclipse.xtext.Parameter
import org.eclipse.xtext.ParserRule
import org.eclipse.xtext.RuleCall
import org.eclipse.xtext.TypeRef
import org.eclipse.xtext.UnorderedGroup
import org.eclipse.xtext.XtextRuntimeModule
import org.eclipse.xtext.formatting.ILineSeparatorInformation
import org.eclipse.xtext.nodemodel.util.NodeModelUtils
import org.eclipse.xtext.resource.SaveOptions
import org.eclipse.xtext.serializer.ISerializer
import org.eclipse.xtext.xtext.RuleNames
import org.eclipse.xtext.xtext.generator.CodeConfig
import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming
import org.eclipse.xtext.xtext.generator.model.TypeReference
import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil
@ -48,7 +47,8 @@ import static extension java.lang.Character.*
import static extension org.eclipse.xtext.EcoreUtil2.*
import static extension org.eclipse.xtext.GrammarUtil.*
import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.*
import org.eclipse.xtext.Parameter
import com.google.common.base.Strings
import org.eclipse.xtext.xtext.generator.CodeConfig
/**
* This API can be used by other templates to generate code
@ -66,11 +66,9 @@ class GrammarAccessExtensions {
"\t" -> "tab",
"\\" -> "backslash"
};
val Map<String, ISerializer> xtextSerializerByLineDelimiter = Maps.newHashMapWithExpectedSize(2)
@Inject CodeConfig codeConfig
@Inject extension XtextGeneratorNaming
@Inject CodeConfig codeConfig
/**
* Returns a reference to the GrammarAccess implementation for a grammar.
@ -396,8 +394,59 @@ class GrammarAccessExtensions {
}
}
def String grammarFragmentToString(EObject ele, String prefix) {
return serializer.grammarFragmentToString(ele, prefix)
def String grammarFragmentToString(EObject object, String prefix) {
val node = NodeModelUtils.findActualNodeFor(object)
if (node === null) {
if (object instanceof RuleCall)
if (object?.rule?.name !== null)
return process(object.rule.name, prefix)
return ""
} else {
return node.text.process(prefix)
}
}
private def process(String input, String prefix) {
// remove leading and trailing blank lines
var lines = input.split('\\s*(\\r?\\n)')
var first = 0
while (isBlank(lines.get(first)))
first++
var last = lines.length - 1
while (isBlank(lines.get(last)))
last--
lines = lines.subList(first, last + 1)
// just one line, trim it and be done
if (lines.size() == 1)
return prefix + lines.get(0).trim();
// remove common whitespace (e.g. leading blanks) and add prefix to each line
val commonWhitespace = commonLeadingWhitespace(lines)
for (var n=0; n<lines.length; n++)
lines.set(n, prefix + lines.get(n).replaceAll("\t", " ").substring(commonWhitespace.length))
// generate result by joining lines
return lines.join(codeConfig.lineDelimiter)
}
private def isBlank(String line) {
return line.trim.empty
}
private def String commonLeadingWhitespace(List<String> lines) {
if(lines.size() < 2) return "";
// determine common prefix while ignoring blank lines
var current = Strings.repeat(" ", lines.get(0).replaceAll("\t", " ").length())
for (var i = 0; i < lines.length; i++) {
val next = lines.get(i).replaceAll("\t", " ");
if (!isBlank(next))
current = Strings.commonPrefix(current, next);
}
// replace blank lines with prefix to handle empty lines gracefully
for (var i = 0; i < lines.length; i++) {
val next = lines.get(i).replaceAll("\t", " ");
if (isBlank(next))
lines.set(i, current)
}
return current;
}
/**
@ -546,18 +595,6 @@ class GrammarAccessExtensions {
}
}
private def ISerializer getSerializer() {
val delimiter = codeConfig.lineDelimiter
var result = xtextSerializerByLineDelimiter.get(delimiter)
if (result !== null) {
return result
}
val injector = Guice.createInjector(new LineSeparatorModule[delimiter])
result = injector.getInstance(ISerializer)
xtextSerializerByLineDelimiter.put(delimiter, result)
return result;
}
@FinalFieldsConstructor
protected static class LineSeparatorModule extends XtextRuntimeModule {

View file

@ -9,12 +9,10 @@
package org.eclipse.xtext.xtext.generator.grammarAccess;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -45,10 +43,13 @@ import org.eclipse.xtext.TypeRef;
import org.eclipse.xtext.UnorderedGroup;
import org.eclipse.xtext.XtextRuntimeModule;
import org.eclipse.xtext.formatting.ILineSeparatorInformation;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.SaveOptions;
import org.eclipse.xtext.serializer.ISerializer;
import org.eclipse.xtext.service.CompoundModule;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
@ -93,15 +94,13 @@ public class GrammarAccessExtensions {
private static Map<String, String> SPECIAL_CHARS = Collections.<String, String>unmodifiableMap(CollectionLiterals.<String, String>newHashMap(Pair.<String, String>of("\b", "backspace"), Pair.<String, String>of("\f", "formFeed"), Pair.<String, String>of("\n", "lineFeed"), Pair.<String, String>of("\r", "carriageReturn"), Pair.<String, String>of("\t", "tab"), Pair.<String, String>of("\\", "backslash")));
private final Map<String, ISerializer> xtextSerializerByLineDelimiter = Maps.<String, ISerializer>newHashMapWithExpectedSize(2);
@Inject
private CodeConfig codeConfig;
@Inject
@Extension
private XtextGeneratorNaming _xtextGeneratorNaming;
@Inject
private CodeConfig codeConfig;
/**
* Returns a reference to the GrammarAccess implementation for a grammar.
*/
@ -600,8 +599,91 @@ public class GrammarAccessExtensions {
return _switchResult;
}
public String grammarFragmentToString(final EObject ele, final String prefix) {
return GrammarAccessExtensions.grammarFragmentToString(this.getSerializer(), ele, prefix);
public String grammarFragmentToString(final EObject object, final String prefix) {
final ICompositeNode node = NodeModelUtils.findActualNodeFor(object);
if ((node == null)) {
if ((object instanceof RuleCall)) {
AbstractRule _rule = null;
if (((RuleCall)object)!=null) {
_rule=((RuleCall)object).getRule();
}
String _name = null;
if (_rule!=null) {
_name=_rule.getName();
}
boolean _tripleNotEquals = (_name != null);
if (_tripleNotEquals) {
return this.process(((RuleCall)object).getRule().getName(), prefix);
}
}
return "";
} else {
return this.process(node.getText(), prefix);
}
}
private String process(final String input, final String prefix) {
String[] lines = input.split("\\s*(\\r?\\n)");
int first = 0;
while (this.isBlank(lines[first])) {
first++;
}
int _length = lines.length;
int last = (_length - 1);
while (this.isBlank(lines[last])) {
last--;
}
final String[] _converted_lines = (String[])lines;
lines = ((String[])Conversions.unwrapArray(((List<String>)Conversions.doWrapArray(_converted_lines)).subList(first, (last + 1)), String.class));
final String[] _converted_lines_1 = (String[])lines;
int _size = ((List<String>)Conversions.doWrapArray(_converted_lines_1)).size();
boolean _equals = (_size == 1);
if (_equals) {
String _trim = (lines[0]).trim();
return (prefix + _trim);
}
final String[] _converted_lines_2 = (String[])lines;
final String commonWhitespace = this.commonLeadingWhitespace(((List<String>)Conversions.doWrapArray(_converted_lines_2)));
for (int n = 0; (n < lines.length); n++) {
String _substring = (lines[n]).replaceAll("\t", " ").substring(commonWhitespace.length());
String _plus = (prefix + _substring);
lines[n] = _plus;
}
final String[] _converted_lines_3 = (String[])lines;
return IterableExtensions.join(((Iterable<?>)Conversions.doWrapArray(_converted_lines_3)), this.codeConfig.getLineDelimiter());
}
private boolean isBlank(final String line) {
return line.trim().isEmpty();
}
private String commonLeadingWhitespace(final List<String> lines) {
int _size = lines.size();
boolean _lessThan = (_size < 2);
if (_lessThan) {
return "";
}
String current = Strings.repeat(" ", lines.get(0).replaceAll("\t", " ").length());
for (int i = 0; (i < ((Object[])Conversions.unwrapArray(lines, Object.class)).length); i++) {
{
final String next = lines.get(i).replaceAll("\t", " ");
boolean _isBlank = this.isBlank(next);
boolean _not = (!_isBlank);
if (_not) {
current = Strings.commonPrefix(current, next);
}
}
}
for (int i = 0; (i < ((Object[])Conversions.unwrapArray(lines, Object.class)).length); i++) {
{
final String next = lines.get(i).replaceAll("\t", " ");
boolean _isBlank = this.isBlank(next);
if (_isBlank) {
lines.set(i, current);
}
}
}
return current;
}
/**
@ -862,22 +944,6 @@ public class GrammarAccessExtensions {
return _switchResult;
}
private ISerializer getSerializer() {
final String delimiter = this.codeConfig.getLineDelimiter();
ISerializer result = this.xtextSerializerByLineDelimiter.get(delimiter);
if ((result != null)) {
return result;
}
final ILineSeparatorInformation _function = () -> {
return delimiter;
};
GrammarAccessExtensions.LineSeparatorModule _lineSeparatorModule = new GrammarAccessExtensions.LineSeparatorModule(_function);
final Injector injector = Guice.createInjector(_lineSeparatorModule);
result = injector.<ISerializer>getInstance(ISerializer.class);
this.xtextSerializerByLineDelimiter.put(delimiter, result);
return result;
}
public String grammarElementIdentifier(final EObject it) {
if (it instanceof AbstractElement) {
return _grammarElementIdentifier((AbstractElement)it);