Merge pull request #328 from eclipse/msp_templateNodes

Fixed inconsistencies between TemplateNode and Xtend templates
This commit is contained in:
Miro Spönemann 2017-05-02 09:46:15 +02:00 committed by GitHub
commit ecc8da5c4f
12 changed files with 581 additions and 158 deletions

View file

@ -51,15 +51,17 @@ class GeneratorNodeTest {
var node = root.trace.append('Hallo').appendNewLine
node.indent
node.append('noindent').appendNewLine
node.append(new IndentNode(' ', true, true))
node.append('noindent').appendNewLine
val processor = new GeneratorNodeProcessor
Assert.assertEquals('''
Hallo
noindent
noindent
'''.toString, processor.process(node).toString)
}
@Test def void testTemplateProcessing() {
val root = loc(0)
val node = root.trace
@ -89,20 +91,20 @@ class GeneratorNodeTest {
}'''.toString, result.traceRegion.toString)
}
def StringConcatenationClient someCodeGen(int n) '''
private def StringConcatenationClient someCodeGen(int n) '''
«FOR i : 0..<n»
before «loc(10+i).trace.append('Hello')» after
«someCodeGen(n-1)»
«ENDFOR»
'''
def String someCodeGen_noTrace(int n) '''
private def String someCodeGen_noTrace(int n) '''
«FOR i : 0..<n»
before «'Hello'» after
«someCodeGen_noTrace(n-1)»
«ENDFOR»
'''
def loc(int idx) {
private def loc(int idx) {
new LocationData(idx, 100-idx, 0, 0, new SourceRelativeURI('foo/mymodel.dsl'))
}
@ -146,4 +148,49 @@ class GeneratorNodeTest {
'''.toString, processor.process(node).toString)
}
@Test def void testIndentVariants() {
val node = new CompositeGeneratorNode
node.doIndent(false, false)
node.doIndent(true, false)
node.doIndent(false, true)
node.doIndent(true, true)
val processor = new GeneratorNodeProcessor
Assert.assertEquals('''
// indentImmediately: false, indentEmptyLines: false
a
bc
d
// indentImmediately: true, indentEmptyLines: false
a
b c
d
// indentImmediately: false, indentEmptyLines: true
a
bc
d
// indentImmediately: true, indentEmptyLines: true
a
b c
d
'''.toString, processor.process(node).toString)
}
private def void doIndent(CompositeGeneratorNode parent, boolean indentImmediately, boolean indentEmptyLines) {
parent.append('// indentImmediately: ').append(indentImmediately)
parent.append(', indentEmptyLines: ').append(indentEmptyLines).appendNewLine
parent.append(new IndentNode(' ', indentImmediately, indentEmptyLines).append('a').appendNewLine.appendNewLine.append('b'))
parent.append(new IndentNode(' ', indentImmediately, indentEmptyLines).append('c')).appendNewLine
parent.append(new IndentNode(' ', indentImmediately, indentEmptyLines).appendNewLine)
parent.append('d').append(new IndentNode(' ', indentImmediately, indentEmptyLines).appendNewLine)
}
}

View file

@ -60,13 +60,6 @@ class TemplateNodeTest {
''')
}
@Test def void testSeparatorLoop() {
val strings = #['a', 'b', 'c']
assertEquals('''
«FOR s : strings SEPARATOR ', '»"«s»"«ENDFOR»
''')
}
private def other() '''
foo «"dfdf" + 23» bar
'''
@ -77,7 +70,56 @@ class TemplateNodeTest {
«other()»
'''
def void assertEquals(StringConcatenationClient c) {
@Test def void testSeparatorLoop() {
val strings = #['a', 'b', 'c']
assertEquals('''
«FOR s : strings SEPARATOR ', '»"«s»"«ENDFOR»
''')
}
@Test def void testIndentedIf() {
val condition = true
val string = 'foo'
assertEquals('''
Very wise:
«IF condition»
who «string» do
«ENDIF»
''')
}
@Test def void testIndentedFor() {
val list = #['foo', 'bar']
assertEquals('''
Very wise:
«FOR s : list» «s»«ENDFOR»
''')
}
@Test def void testIndentedTemplate() {
val StringConcatenationClient template = '''
sometimes foo
and sometimes bar
'''
assertEquals('''
Very wise:
«template»
''')
}
@Test def void testIfNotEmpty() {
val StringConcatenationClient template = '''
«''»
foo
'''
assertEquals('''
Very wise:
«template»
''')
}
protected def void assertEquals(StringConcatenationClient c) {
val ext = new GeneratorNodeExtensions()
val processor = new GeneratorNodeProcessor()

View file

@ -14,6 +14,7 @@ import org.eclipse.xtext.generator.trace.SourceRelativeURI;
import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode;
import org.eclipse.xtext.generator.trace.node.GeneratorNodeExtensions;
import org.eclipse.xtext.generator.trace.node.GeneratorNodeProcessor;
import org.eclipse.xtext.generator.trace.node.IndentNode;
import org.eclipse.xtext.xbase.lib.ExclusiveRange;
import org.eclipse.xtext.xbase.lib.Extension;
import org.junit.Assert;
@ -73,12 +74,17 @@ public class GeneratorNodeTest {
CompositeGeneratorNode node = this.exts.appendNewLine(this.exts.append(this.exts.trace(root), "Hallo"));
this.exts.indent(node);
this.exts.appendNewLine(this.exts.append(node, "noindent"));
IndentNode _indentNode = new IndentNode(" ", true, true);
this.exts.append(node, _indentNode);
this.exts.appendNewLine(this.exts.append(node, "noindent"));
final GeneratorNodeProcessor processor = new GeneratorNodeProcessor();
StringConcatenation _builder = new StringConcatenation();
_builder.append("Hallo");
_builder.newLine();
_builder.append("noindent");
_builder.newLine();
_builder.append("noindent");
_builder.newLine();
Assert.assertEquals(_builder.toString(), processor.process(node).toString());
}
@ -146,7 +152,7 @@ public class GeneratorNodeTest {
Assert.assertEquals(_builder.toString(), result.getTraceRegion().toString());
}
public StringConcatenationClient someCodeGen(final int n) {
private StringConcatenationClient someCodeGen(final int n) {
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
@ -169,7 +175,7 @@ public class GeneratorNodeTest {
return _client;
}
public String someCodeGen_noTrace(final int n) {
private String someCodeGen_noTrace(final int n) {
StringConcatenation _builder = new StringConcatenation();
{
ExclusiveRange _doubleDotLessThan = new ExclusiveRange(0, n, true);
@ -187,7 +193,7 @@ public class GeneratorNodeTest {
return _builder.toString();
}
public LocationData loc(final int idx) {
private LocationData loc(final int idx) {
SourceRelativeURI _sourceRelativeURI = new SourceRelativeURI("foo/mymodel.dsl");
return new LocationData(idx, (100 - idx), 0, 0, _sourceRelativeURI);
}
@ -266,4 +272,77 @@ public class GeneratorNodeTest {
_builder_3.newLine();
Assert.assertEquals(_builder_3.toString(), processor.process(node).toString());
}
@Test
public void testIndentVariants() {
final CompositeGeneratorNode node = new CompositeGeneratorNode();
this.doIndent(node, false, false);
this.doIndent(node, true, false);
this.doIndent(node, false, true);
this.doIndent(node, true, true);
final GeneratorNodeProcessor processor = new GeneratorNodeProcessor();
StringConcatenation _builder = new StringConcatenation();
_builder.append("// indentImmediately: false, indentEmptyLines: false");
_builder.newLine();
_builder.append(" ");
_builder.append("a");
_builder.newLine();
_builder.newLine();
_builder.append(" ");
_builder.append("bc");
_builder.newLine();
_builder.newLine();
_builder.append("d");
_builder.newLine();
_builder.append("// indentImmediately: true, indentEmptyLines: false");
_builder.newLine();
_builder.append(" ");
_builder.append("a");
_builder.newLine();
_builder.newLine();
_builder.append(" ");
_builder.append("b c");
_builder.newLine();
_builder.newLine();
_builder.append("d ");
_builder.newLine();
_builder.append("// indentImmediately: false, indentEmptyLines: true");
_builder.newLine();
_builder.append(" ");
_builder.append("a");
_builder.newLine();
_builder.append(" ");
_builder.newLine();
_builder.append(" ");
_builder.append("bc");
_builder.newLine();
_builder.append(" ");
_builder.newLine();
_builder.append("d");
_builder.newLine();
_builder.append("// indentImmediately: true, indentEmptyLines: true");
_builder.newLine();
_builder.append(" ");
_builder.append("a");
_builder.newLine();
_builder.append(" ");
_builder.newLine();
_builder.append(" ");
_builder.append("b c");
_builder.newLine();
_builder.append(" ");
_builder.newLine();
_builder.append("d ");
_builder.newLine();
Assert.assertEquals(_builder.toString(), processor.process(node).toString());
}
private void doIndent(final CompositeGeneratorNode parent, final boolean indentImmediately, final boolean indentEmptyLines) {
this.exts.append(this.exts.append(parent, "// indentImmediately: "), Boolean.valueOf(indentImmediately));
this.exts.appendNewLine(this.exts.append(this.exts.append(parent, ", indentEmptyLines: "), Boolean.valueOf(indentEmptyLines)));
this.exts.append(parent, this.exts.append(this.exts.appendNewLine(this.exts.appendNewLine(this.exts.append(new IndentNode(" ", indentImmediately, indentEmptyLines), "a"))), "b"));
this.exts.appendNewLine(this.exts.append(parent, this.exts.append(new IndentNode(" ", indentImmediately, indentEmptyLines), "c")));
this.exts.append(parent, this.exts.appendNewLine(new IndentNode(" ", indentImmediately, indentEmptyLines)));
this.exts.append(this.exts.append(parent, "d"), this.exts.appendNewLine(new IndentNode(" ", indentImmediately, indentEmptyLines)));
}
}

View file

@ -125,6 +125,28 @@ public class TemplateNodeTest {
this.assertEquals(_client);
}
private CharSequence other() {
StringConcatenation _builder = new StringConcatenation();
_builder.append("foo ");
_builder.append(("dfdf" + Integer.valueOf(23)));
_builder.append(" bar");
_builder.newLineIfNotEmpty();
return _builder;
}
private String multiLineString() {
StringConcatenation _builder = new StringConcatenation();
_builder.append("test ");
_builder.newLine();
_builder.append("bar");
_builder.newLine();
_builder.append("\t");
CharSequence _other = this.other();
_builder.append(_other, "\t");
_builder.newLineIfNotEmpty();
return _builder.toString();
}
@Test
public void testSeparatorLoop() {
final List<String> strings = Collections.<String>unmodifiableList(CollectionLiterals.<String>newArrayList("a", "b", "c"));
@ -150,29 +172,101 @@ public class TemplateNodeTest {
this.assertEquals(_client);
}
private CharSequence other() {
StringConcatenation _builder = new StringConcatenation();
_builder.append("foo ");
_builder.append(("dfdf" + Integer.valueOf(23)));
_builder.append(" bar");
_builder.newLineIfNotEmpty();
return _builder;
@Test
public void testIndentedIf() {
final boolean condition = true;
final String string = "foo";
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("Very wise:");
_builder.newLine();
{
if (condition) {
_builder.append("\t");
_builder.append("who ");
_builder.append(string, "\t");
_builder.append(" do");
_builder.newLineIfNotEmpty();
}
}
}
};
this.assertEquals(_client);
}
private String multiLineString() {
StringConcatenation _builder = new StringConcatenation();
_builder.append("test ");
_builder.newLine();
_builder.append("bar");
_builder.newLine();
_builder.append("\t");
CharSequence _other = this.other();
_builder.append(_other, "\t");
_builder.newLineIfNotEmpty();
return _builder.toString();
@Test
public void testIndentedFor() {
final List<String> list = Collections.<String>unmodifiableList(CollectionLiterals.<String>newArrayList("foo", "bar"));
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("Very wise:");
_builder.newLine();
_builder.append("\t");
{
for(final String s : list) {
_builder.append("\t");
_builder.append(s, "\t");
}
}
_builder.newLineIfNotEmpty();
}
};
this.assertEquals(_client);
}
public void assertEquals(final StringConcatenationClient c) {
@Test
public void testIndentedTemplate() {
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("sometimes foo");
_builder.newLine();
_builder.newLine();
_builder.append("and sometimes bar");
_builder.newLine();
}
};
final StringConcatenationClient template = _client;
StringConcatenationClient _client_1 = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("Very wise:");
_builder.newLine();
_builder.append("\t");
_builder.append(template, "\t");
_builder.newLineIfNotEmpty();
}
};
this.assertEquals(_client_1);
}
@Test
public void testIfNotEmpty() {
StringConcatenationClient _client = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.newLineIfNotEmpty();
_builder.append("foo");
_builder.newLine();
}
};
final StringConcatenationClient template = _client;
StringConcatenationClient _client_1 = new StringConcatenationClient() {
@Override
protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
_builder.append("Very wise:");
_builder.newLine();
_builder.append("\t");
_builder.append(template, "\t");
_builder.newLineIfNotEmpty();
}
};
this.assertEquals(_client_1);
}
protected void assertEquals(final StringConcatenationClient c) {
final GeneratorNodeExtensions ext = new GeneratorNodeExtensions();
final GeneratorNodeProcessor processor = new GeneratorNodeProcessor();
final CompositeGeneratorNode root = new CompositeGeneratorNode();

View file

@ -41,7 +41,7 @@ class GeneratorNodeProcessor {
@Accessors protected static class Context {
List<StringBuilder> lines
Deque<String> currentIndents
Deque<IndentNode> currentIndents
boolean pendingIndent
AbstractTraceRegion currentRegion = null
@ -52,7 +52,7 @@ class GeneratorNodeProcessor {
def int contentLength() {
val contentLength = lines.fold(0) [ $0 + $1.length ]
if (pendingIndent) {
return contentLength + currentIndents.fold(0) [ $0 + $1.length ]
return contentLength + currentIndents.fold(0) [ $0 + $1.indentationString.length ]
} else {
return contentLength
}
@ -66,7 +66,7 @@ class GeneratorNodeProcessor {
def Result process(IGeneratorNode root) {
val ctx = new Context => [
lines = newArrayList(new StringBuilder)
currentIndents = new ArrayDeque<String>()
currentIndents = new ArrayDeque
pendingIndent = true
]
doProcess(root, ctx)
@ -74,74 +74,104 @@ class GeneratorNodeProcessor {
}
/**
* Indent nodes apply indentation between newline and content of its children.
* An indent node prepends indentation to each line of its children.
*/
protected def dispatch void doProcess(IndentNode node, Context ctx) {
// do nothing if the indent node is empty
if (node.children.empty) {
return
}
try {
ctx.currentIndents.push(node.indentationString)
ctx.pendingIndent = true
doProcessChildren(node, ctx)
} finally {
ctx.currentIndents.pop
if (node._hasContent(ctx)) {
if (node.indentImmediately && !ctx.pendingIndent) {
ctx.currentLine.append(node.indentationString)
}
try {
ctx.currentIndents.push(node)
doProcessChildren(node, ctx)
} finally {
ctx.currentIndents.pop
}
}
}
protected def dispatch void doProcess(NewLineNode node, Context ctx) {
val trimmedLine = ctx.currentLine.toString.trim
if (node.ifNotEmpty && trimmedLine.empty) {
if (node.ifNotEmpty && !ctx.currentLine.hasNonWhitespace) {
ctx.lines.set(ctx.currentLineNumber, new StringBuilder)
return
} else {
if (ctx.pendingIndent)
handlePendingIndent(ctx, true)
ctx.currentLine.append(node.lineDelimiter)
ctx.lines.add(new StringBuilder)
}
ctx.currentLine.append(node.lineDelimiter)
ctx.lines.add(new StringBuilder)
ctx.pendingIndent = true
}
protected def dispatch void doProcess(TextNode node, Context ctx) {
val txt = node.text.toString
if (txt.empty) {
return
if (node._hasContent(ctx)) {
if (ctx.pendingIndent)
handlePendingIndent(ctx, false)
ctx.currentLine.append(node.text)
}
if (ctx.pendingIndent) {
val indentString = new StringBuilder
for (indentationString : ctx.currentIndents) {
indentString.append(indentationString)
}
}
protected def void handlePendingIndent(Context ctx, boolean endOfLine) {
val indentString = new StringBuilder
for (indentNode : ctx.currentIndents) {
if (indentNode.indentEmptyLines || !endOfLine)
indentString.append(indentNode.indentationString)
}
if (indentString.length > 0) {
ctx.currentLine.insert(0, indentString)
ctx.pendingIndent = false
}
ctx.currentLine.append(node.text)
ctx.pendingIndent = false
}
protected def dispatch void doProcess(TraceNode node, Context ctx) {
if (node._hasContent(ctx)) {
val beforeRegion = ctx.currentRegion
val newRegion = new CompletableTraceRegion(false, node.sourceLocation, beforeRegion)
val offset = ctx.contentLength
val startLineNumber = ctx.currentLineNumber
try {
ctx.currentRegion = newRegion
doProcessChildren(node, ctx)
} finally {
if (beforeRegion !== null)
ctx.currentRegion = beforeRegion
newRegion.complete(offset, ctx.contentLength - offset, startLineNumber, ctx.currentLineNumber)
}
}
}
protected def dispatch void doProcess(CompositeGeneratorNode node, Context ctx) {
doProcessChildren(node, ctx)
}
protected def dispatch void doProcess(TraceNode node, Context ctx) {
val beforeRegion = ctx.currentRegion
val newRegion = new CompletableTraceRegion(false, node.sourceLocation, beforeRegion)
val offset = ctx.contentLength
val startLineNumber = ctx.currentLineNumber
try {
ctx.currentRegion = newRegion
doProcessChildren(node, ctx)
} finally {
if (beforeRegion !== null)
ctx.currentRegion = beforeRegion
newRegion.complete(offset, ctx.contentLength - offset, startLineNumber, ctx.currentLineNumber)
}
}
protected def void doProcessChildren(CompositeGeneratorNode node, Context ctx) {
for (child : node.children) {
doProcess(child, ctx)
}
}
protected def dispatch boolean hasContent(CompositeGeneratorNode node, Context ctx) {
node.children.exists[hasContent(ctx)]
}
protected def dispatch boolean hasContent(NewLineNode node, Context ctx) {
!(node.ifNotEmpty && ctx.currentLine.length == 0)
}
protected def dispatch boolean hasContent(TextNode node, Context ctx) {
!node.text.nullOrEmpty
}
protected static def boolean hasNonWhitespace(CharSequence s) {
for (var i = 0; i < s.length; i++) {
if (!Character.isWhitespace(s.charAt(i)))
return true
}
return false
}
protected static def boolean isNullOrEmpty(CharSequence s) {
s === null || s.length == 0
}
/**
* Used to avoid multi-pass processing, when constructing a trace region tree.
*

View file

@ -10,8 +10,7 @@ package org.eclipse.xtext.generator.trace.node
import org.eclipse.xtend.lib.annotations.Accessors
/**
* An indent node prepends the indentation string to each line that is generated through its children
* (including the first line).
* An indent node prepends the indentation string to each line that is generated through its children.
*
* @author Sven Efftinge - Initial contribution and API
*/
@ -20,8 +19,27 @@ class IndentNode extends CompositeGeneratorNode {
String indentationString
/**
* When this is set to {@code true}, the indentation is always inserted in the first line, otherwise it is
* inserted only if the first line has no text preceding this node.
*/
boolean indentImmediately
/**
* When this is set to {@code true}, all lines are indented, otherwise only lines with text content are indented.
*/
boolean indentEmptyLines
new(String indentationString) {
this(indentationString, true, false)
}
new(String indentationString, boolean indentImmediately, boolean indentEmptyLines) {
if (indentationString === null)
throw new NullPointerException
this.indentationString = indentationString
this.indentImmediately = indentImmediately
this.indentEmptyLines = indentEmptyLines
}
}

View file

@ -20,13 +20,18 @@ class NewLineNode implements IGeneratorNode {
String lineDelimiter
/**
* When this is set to {@code true}, the preceding line is removed if it contains only whitespace.
*/
boolean ifNotEmpty
new(String lineDelimiter) {
this.lineDelimiter = lineDelimiter
this(lineDelimiter, false)
}
new(String lineDelimiter, boolean ifNotEmpty) {
if (lineDelimiter === null)
throw new NullPointerException
this.lineDelimiter = lineDelimiter
this.ifNotEmpty = ifNotEmpty
}

View file

@ -33,14 +33,8 @@ class TemplateNode extends CompositeGeneratorNode implements TargetStringConcate
override append(Object object, String indentation) {
if (indentation.length > 0) {
val before = currentParent
// The first line of an indented template is prepended with an explicit indentation string.
// We need to revert this because this case is already handled by the GeneratorNodeProcessor.
val lastChild = before.children.last
if (lastChild instanceof TextNode && (lastChild as TextNode).text == indentation) {
before.children.remove(before.children.size - 1)
}
try {
currentParent = new IndentNode(indentation)
currentParent = new IndentNode(indentation, false, true)
before.children += currentParent
append(object)
} finally {
@ -94,7 +88,7 @@ class TemplateNode extends CompositeGeneratorNode implements TargetStringConcate
for (var i = currentParent.children.size - 1; i >= 0; i--) {
val node = currentParent.children.get(i)
if (node instanceof TextNode) {
if (node.text.toString.trim.length === 0) {
if (!node.text.hasContent) {
currentParent.children.remove(i)
}
}
@ -102,6 +96,14 @@ class TemplateNode extends CompositeGeneratorNode implements TargetStringConcate
append(object, indentation)
}
protected static def boolean hasContent(CharSequence s) {
for (var i = 0; i < s.length; i++) {
if (!Character.isWhitespace(s.charAt(i)))
return true
}
return false
}
override newLine() {
this.nodeFactory.appendNewLine(currentParent)
}

View file

@ -30,6 +30,7 @@ import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.ITextRegionWithLineInformation;
import org.eclipse.xtext.util.TextRegionWithLineInformation;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.Functions.Function2;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ObjectExtensions;
@ -127,7 +128,7 @@ public class GeneratorNodeProcessor {
protected static class Context {
private List<StringBuilder> lines;
private Deque<String> currentIndents;
private Deque<IndentNode> currentIndents;
private boolean pendingIndent;
@ -144,11 +145,11 @@ public class GeneratorNodeProcessor {
};
final Integer contentLength = IterableExtensions.<StringBuilder, Integer>fold(this.lines, Integer.valueOf(0), _function);
if (this.pendingIndent) {
final Function2<Integer, String, Integer> _function_1 = (Integer $0, String $1) -> {
int _length = $1.length();
final Function2<Integer, IndentNode, Integer> _function_1 = (Integer $0, IndentNode $1) -> {
int _length = $1.getIndentationString().length();
return Integer.valueOf((($0).intValue() + _length));
};
Integer _fold = IterableExtensions.<String, Integer>fold(this.currentIndents, Integer.valueOf(0), _function_1);
Integer _fold = IterableExtensions.<IndentNode, Integer>fold(this.currentIndents, Integer.valueOf(0), _function_1);
return ((contentLength).intValue() + (_fold).intValue());
} else {
return (contentLength).intValue();
@ -170,11 +171,11 @@ public class GeneratorNodeProcessor {
}
@Pure
public Deque<String> getCurrentIndents() {
public Deque<IndentNode> getCurrentIndents() {
return this.currentIndents;
}
public void setCurrentIndents(final Deque<String> currentIndents) {
public void setCurrentIndents(final Deque<IndentNode> currentIndents) {
this.currentIndents = currentIndents;
}
@ -274,7 +275,7 @@ public class GeneratorNodeProcessor {
final Procedure1<GeneratorNodeProcessor.Context> _function = (GeneratorNodeProcessor.Context it) -> {
StringBuilder _stringBuilder = new StringBuilder();
it.lines = CollectionLiterals.<StringBuilder>newArrayList(_stringBuilder);
ArrayDeque<String> _arrayDeque = new ArrayDeque<String>();
ArrayDeque<IndentNode> _arrayDeque = new ArrayDeque<IndentNode>();
it.currentIndents = _arrayDeque;
it.pendingIndent = true;
};
@ -285,76 +286,90 @@ public class GeneratorNodeProcessor {
}
/**
* Indent nodes apply indentation between newline and content of its children.
* An indent node prepends indentation to each line of its children.
*/
protected void _doProcess(final IndentNode node, final GeneratorNodeProcessor.Context ctx) {
boolean _isEmpty = node.getChildren().isEmpty();
if (_isEmpty) {
return;
}
try {
ctx.currentIndents.push(node.getIndentationString());
ctx.pendingIndent = true;
this.doProcessChildren(node, ctx);
} finally {
ctx.currentIndents.pop();
boolean __hasContent = this._hasContent(node, ctx);
if (__hasContent) {
if ((node.isIndentImmediately() && (!ctx.pendingIndent))) {
ctx.currentLine().append(node.getIndentationString());
}
try {
ctx.currentIndents.push(node);
this.doProcessChildren(node, ctx);
} finally {
ctx.currentIndents.pop();
}
}
}
protected void _doProcess(final NewLineNode node, final GeneratorNodeProcessor.Context ctx) {
final String trimmedLine = ctx.currentLine().toString().trim();
if ((node.isIfNotEmpty() && trimmedLine.isEmpty())) {
if ((node.isIfNotEmpty() && (!GeneratorNodeProcessor.hasNonWhitespace(ctx.currentLine())))) {
int _currentLineNumber = ctx.currentLineNumber();
StringBuilder _stringBuilder = new StringBuilder();
ctx.lines.set(_currentLineNumber, _stringBuilder);
return;
} else {
if (ctx.pendingIndent) {
this.handlePendingIndent(ctx, true);
}
ctx.currentLine().append(node.getLineDelimiter());
StringBuilder _stringBuilder_1 = new StringBuilder();
ctx.lines.add(_stringBuilder_1);
}
ctx.currentLine().append(node.getLineDelimiter());
StringBuilder _stringBuilder_1 = new StringBuilder();
ctx.lines.add(_stringBuilder_1);
ctx.pendingIndent = true;
}
protected void _doProcess(final TextNode node, final GeneratorNodeProcessor.Context ctx) {
final String txt = node.getText().toString();
boolean _isEmpty = txt.isEmpty();
if (_isEmpty) {
return;
}
if (ctx.pendingIndent) {
final StringBuilder indentString = new StringBuilder();
for (final String indentationString : ctx.currentIndents) {
indentString.append(indentationString);
boolean __hasContent = this._hasContent(node, ctx);
if (__hasContent) {
if (ctx.pendingIndent) {
this.handlePendingIndent(ctx, false);
}
ctx.currentLine().append(node.getText());
}
}
protected void handlePendingIndent(final GeneratorNodeProcessor.Context ctx, final boolean endOfLine) {
final StringBuilder indentString = new StringBuilder();
for (final IndentNode indentNode : ctx.currentIndents) {
if ((indentNode.isIndentEmptyLines() || (!endOfLine))) {
indentString.append(indentNode.getIndentationString());
}
}
int _length = indentString.length();
boolean _greaterThan = (_length > 0);
if (_greaterThan) {
ctx.currentLine().insert(0, indentString);
}
ctx.pendingIndent = false;
}
protected void _doProcess(final TraceNode node, final GeneratorNodeProcessor.Context ctx) {
boolean __hasContent = this._hasContent(node, ctx);
if (__hasContent) {
final AbstractTraceRegion beforeRegion = ctx.currentRegion;
ILocationData _sourceLocation = node.getSourceLocation();
final GeneratorNodeProcessor.CompletableTraceRegion newRegion = new GeneratorNodeProcessor.CompletableTraceRegion(false, _sourceLocation, beforeRegion);
final int offset = ctx.contentLength();
final int startLineNumber = ctx.currentLineNumber();
try {
ctx.currentRegion = newRegion;
this.doProcessChildren(node, ctx);
} finally {
if ((beforeRegion != null)) {
ctx.currentRegion = beforeRegion;
}
int _contentLength = ctx.contentLength();
int _minus = (_contentLength - offset);
newRegion.complete(offset, _minus, startLineNumber, ctx.currentLineNumber());
}
ctx.currentLine().insert(0, indentString);
ctx.pendingIndent = false;
}
ctx.currentLine().append(node.getText());
}
protected void _doProcess(final CompositeGeneratorNode node, final GeneratorNodeProcessor.Context ctx) {
this.doProcessChildren(node, ctx);
}
protected void _doProcess(final TraceNode node, final GeneratorNodeProcessor.Context ctx) {
final AbstractTraceRegion beforeRegion = ctx.currentRegion;
ILocationData _sourceLocation = node.getSourceLocation();
final GeneratorNodeProcessor.CompletableTraceRegion newRegion = new GeneratorNodeProcessor.CompletableTraceRegion(false, _sourceLocation, beforeRegion);
final int offset = ctx.contentLength();
final int startLineNumber = ctx.currentLineNumber();
try {
ctx.currentRegion = newRegion;
this.doProcessChildren(node, ctx);
} finally {
if ((beforeRegion != null)) {
ctx.currentRegion = beforeRegion;
}
int _contentLength = ctx.contentLength();
int _minus = (_contentLength - offset);
newRegion.complete(offset, _minus, startLineNumber, ctx.currentLineNumber());
}
}
protected void doProcessChildren(final CompositeGeneratorNode node, final GeneratorNodeProcessor.Context ctx) {
List<IGeneratorNode> _children = node.getChildren();
for (final IGeneratorNode child : _children) {
@ -362,6 +377,37 @@ public class GeneratorNodeProcessor {
}
}
protected boolean _hasContent(final CompositeGeneratorNode node, final GeneratorNodeProcessor.Context ctx) {
final Function1<IGeneratorNode, Boolean> _function = (IGeneratorNode it) -> {
return Boolean.valueOf(this.hasContent(it, ctx));
};
return IterableExtensions.<IGeneratorNode>exists(node.getChildren(), _function);
}
protected boolean _hasContent(final NewLineNode node, final GeneratorNodeProcessor.Context ctx) {
return (!(node.isIfNotEmpty() && (ctx.currentLine().length() == 0)));
}
protected boolean _hasContent(final TextNode node, final GeneratorNodeProcessor.Context ctx) {
boolean _isNullOrEmpty = GeneratorNodeProcessor.isNullOrEmpty(node.getText());
return (!_isNullOrEmpty);
}
protected static boolean hasNonWhitespace(final CharSequence s) {
for (int i = 0; (i < s.length()); i++) {
boolean _isWhitespace = Character.isWhitespace(s.charAt(i));
boolean _not = (!_isWhitespace);
if (_not) {
return true;
}
}
return false;
}
protected static boolean isNullOrEmpty(final CharSequence s) {
return ((s == null) || (s.length() == 0));
}
protected void doProcess(final IGeneratorNode node, final GeneratorNodeProcessor.Context ctx) {
if (node instanceof IndentNode) {
_doProcess((IndentNode)node, ctx);
@ -383,4 +429,17 @@ public class GeneratorNodeProcessor {
Arrays.<Object>asList(node, ctx).toString());
}
}
protected boolean hasContent(final IGeneratorNode node, final GeneratorNodeProcessor.Context ctx) {
if (node instanceof CompositeGeneratorNode) {
return _hasContent((CompositeGeneratorNode)node, ctx);
} else if (node instanceof NewLineNode) {
return _hasContent((NewLineNode)node, ctx);
} else if (node instanceof TextNode) {
return _hasContent((TextNode)node, ctx);
} else {
throw new IllegalArgumentException("Unhandled parameter types: " +
Arrays.<Object>asList(node, ctx).toString());
}
}
}

View file

@ -12,8 +12,7 @@ import org.eclipse.xtext.generator.trace.node.CompositeGeneratorNode;
import org.eclipse.xtext.xbase.lib.Pure;
/**
* An indent node prepends the indentation string to each line that is generated through its children
* (including the first line).
* An indent node prepends the indentation string to each line that is generated through its children.
*
* @author Sven Efftinge - Initial contribution and API
*/
@ -22,8 +21,28 @@ import org.eclipse.xtext.xbase.lib.Pure;
public class IndentNode extends CompositeGeneratorNode {
private String indentationString;
/**
* When this is set to {@code true}, the indentation is always inserted in the first line, otherwise it is
* inserted only if the first line has no text preceding this node.
*/
private boolean indentImmediately;
/**
* When this is set to {@code true}, all lines are indented, otherwise only lines with text content are indented.
*/
private boolean indentEmptyLines;
public IndentNode(final String indentationString) {
this(indentationString, true, false);
}
public IndentNode(final String indentationString, final boolean indentImmediately, final boolean indentEmptyLines) {
if ((indentationString == null)) {
throw new NullPointerException();
}
this.indentationString = indentationString;
this.indentImmediately = indentImmediately;
this.indentEmptyLines = indentEmptyLines;
}
@Pure
@ -34,4 +53,22 @@ public class IndentNode extends CompositeGeneratorNode {
public void setIndentationString(final String indentationString) {
this.indentationString = indentationString;
}
@Pure
public boolean isIndentImmediately() {
return this.indentImmediately;
}
public void setIndentImmediately(final boolean indentImmediately) {
this.indentImmediately = indentImmediately;
}
@Pure
public boolean isIndentEmptyLines() {
return this.indentEmptyLines;
}
public void setIndentEmptyLines(final boolean indentEmptyLines) {
this.indentEmptyLines = indentEmptyLines;
}
}

View file

@ -23,13 +23,19 @@ import org.eclipse.xtext.xbase.lib.Pure;
public class NewLineNode implements IGeneratorNode {
private String lineDelimiter;
/**
* When this is set to {@code true}, the preceding line is removed if it contains only whitespace.
*/
private boolean ifNotEmpty;
public NewLineNode(final String lineDelimiter) {
this.lineDelimiter = lineDelimiter;
this(lineDelimiter, false);
}
public NewLineNode(final String lineDelimiter, final boolean ifNotEmpty) {
if ((lineDelimiter == null)) {
throw new NullPointerException();
}
this.lineDelimiter = lineDelimiter;
this.ifNotEmpty = ifNotEmpty;
}

View file

@ -7,7 +7,6 @@
*/
package org.eclipse.xtext.generator.trace.node;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import java.util.Collections;
@ -50,14 +49,8 @@ public class TemplateNode extends CompositeGeneratorNode implements StringConcat
boolean _greaterThan = (_length > 0);
if (_greaterThan) {
final CompositeGeneratorNode before = this.currentParent;
final IGeneratorNode lastChild = IterableExtensions.<IGeneratorNode>last(before.getChildren());
if (((lastChild instanceof TextNode) && Objects.equal(((TextNode) lastChild).getText(), indentation))) {
int _size = before.getChildren().size();
int _minus = (_size - 1);
before.getChildren().remove(_minus);
}
try {
IndentNode _indentNode = new IndentNode(indentation);
IndentNode _indentNode = new IndentNode(indentation, false, true);
this.currentParent = _indentNode;
List<IGeneratorNode> _children = before.getChildren();
_children.add(this.currentParent);
@ -137,9 +130,9 @@ public class TemplateNode extends CompositeGeneratorNode implements StringConcat
{
final IGeneratorNode node = this.currentParent.getChildren().get(i);
if ((node instanceof TextNode)) {
int _length = ((TextNode)node).getText().toString().trim().length();
boolean _tripleEquals = (_length == 0);
if (_tripleEquals) {
boolean _hasContent = TemplateNode.hasContent(((TextNode)node).getText());
boolean _not = (!_hasContent);
if (_not) {
this.currentParent.getChildren().remove(i);
}
}
@ -148,6 +141,17 @@ public class TemplateNode extends CompositeGeneratorNode implements StringConcat
this.append(object, indentation);
}
protected static boolean hasContent(final CharSequence s) {
for (int i = 0; (i < s.length()); i++) {
boolean _isWhitespace = Character.isWhitespace(s.charAt(i));
boolean _not = (!_isWhitespace);
if (_not) {
return true;
}
}
return false;
}
@Override
public void newLine() {
this.nodeFactory.appendNewLine(this.currentParent);