Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » TMF (Xtext) » Nested Conditional Formatters always create Conflict
Nested Conditional Formatters always create Conflict [message #1862548] Mon, 11 December 2023 13:01 Go to next message
Oscar Ablinger is currently offline Oscar AblingerFriend
Messages: 6
Registered: August 2023
Junior Member
In our language we have certain constructs that can be written both single- as well as multiline.
XText does offer the perfect solution for this in the form of
formatConditionally
as described in the presentation (https://www.slideshare.net/meysholdt/xtexts-new-formatter-api).
However, when we need to format at the end of these conditionally formatted areas, we get an exception in the case of tail-recursive elements.
I have created an almost minimal example that replicate this issue.

Let's say we have the following simple grammar of a language that only has an If statement and some terminal rules:

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Model:
	statements+=Statement*;

Statement:
	if=IfStatement | content=ShortStatement | content=LongStatement;

IfStatement:
	'If' condition=Boolean ':' statement=Statement;

Boolean:
	'true' | 'false';

ShortStatement:
	'shortstatement';

LongStatement:
	'superduperlongstatementthatdoesntfitinoneline';


I now want to format it such that the body of the if statement is either on the same line if it fits or in a new line once indented.
I wrote the following Formatter for that:

public class MyDslFormatter extends AbstractJavaFormatter {
  protected void format(Model model, IFormattableDocument doc) {
    for (Statement statement : model.getStatements()) {
      doc.format(statement);
    }
  }

  protected void format(Statement statement, IFormattableDocument doc) {
    if (statement.getIf() != null) {
      doc.format(statement.getIf());
    }
  }

  protected void format(IfStatement ifStatement, IFormattableDocument doc) {
    var regionFinder = textRegionExtensions.regionFor(ifStatement);

    var booleanRegion = regionFinder.feature(MyDslPackage.Literals.IF_STATEMENT__CONDITION);
    doc.prepend(regionFinder.keyword("if"), IHiddenRegionFormatter::noSpace);
    doc.prepend(booleanRegion, IHiddenRegionFormatter::oneSpace);
    doc.append(booleanRegion, IHiddenRegionFormatter::noSpace);
    // doc.append(ifStatement, IHiddenRegionFormatter::newLine);

    ISubFormatter oneline = subdoc -> {
      var region = subdoc.getRegion();
      subdoc = subdoc.requireFitsInLine(region.getOffset(), region.getLength(), 50);

      subdoc.append(regionFinder.keyword(":"), IHiddenRegionFormatter::oneSpace);
      subdoc.append(ifStatement, IHiddenRegionFormatter::newLine);
      
      subdoc.format(ifStatement.getStatement());
    };
    ISubFormatter multiline = subdoc -> {
      subdoc.append(regionFinder.keyword(":"), IHiddenRegionFormatter::newLine);
      subdoc.append(ifStatement, IHiddenRegionFormatter::newLine);
      
      var prevHR = textRegionExtensions.previousHiddenRegion(ifStatement.getStatement());
      var nextHR = textRegionExtensions.nextHiddenRegion(ifStatement.getStatement());
      subdoc.set(prevHR, nextHR, IHiddenRegionFormatter::indent);
      
      subdoc.format(ifStatement.getStatement());
    };

    doc.formatConditionally(ifStatement, oneline, multiline);
  }
}


This works well for singly nested if statements, but the following input: (note that after the last line there has to be some whitespace for the error to appear)

If true:
  superduperlongstatementthatdoesntfitinoneline



Will create the following exception:

ERROR org.eclipse.xtext.util.ExceptionAcceptor  - The region HiddenRegionReplacer must not be outside MaxLineWidthDocument.
{{{}}}: MaxLineWidthDocument at offset=0 length=57
[[[]]]: HiddenRegionReplacer at offset=57 length=2
------------------------------- document snippet -------------------------------
{{{if true:
  superduperlongstatementthatdoesntfitinoneline}}}[[[
]]]
--------------------------------------------------------------------------------
org.eclipse.xtext.formatting2.internal.RegionsOutsideFrameException: The region HiddenRegionReplacer must not be outside MaxLineWidthDocument.
{{{}}}: MaxLineWidthDocument at offset=0 length=57
[[[]]]: HiddenRegionReplacer at offset=57 length=2
------------------------------- document snippet -------------------------------
{{{if true:
  superduperlongstatementthatdoesntfitinoneline}}}[[[
]]]
--------------------------------------------------------------------------------
	at org.eclipse.xtext.formatting2.internal.FormattableDocument.addReplacer(FormattableDocument.java:72)
	at org.eclipse.xtext.formatting2.internal.MaxLineWidthDocument.addReplacer(MaxLineWidthDocument.java:34)
	at org.eclipse.xtext.formatting2.internal.FormattableDocument.set(FormattableDocument.java:322)
	at org.eclipse.xtext.formatting2.internal.FormattableDocument.append(FormattableDocument.java:99)
	at org.xtext.example.mydsl.formatting2.MyDslFormatter.lambda$3(MyDslFormatter.java:47)
	at org.eclipse.xtext.formatting2.internal.ConditionalReplacer.createReplacements(ConditionalReplacer.java:42)
	at org.eclipse.xtext.formatting2.internal.FormattableDocument.createReplacements(FormattableDocument.java:147)
	at org.eclipse.xtext.formatting2.internal.FormattableDocument.renderToTextReplacements(FormattableDocument.java:294)
	at org.eclipse.xtext.formatting2.AbstractFormatter2.format(AbstractFormatter2.java:281)


I've already thought about including the next hidden region in the formatConditionally, but then, if there is a newline in the hidden region, it will always choose the multi-line option.
I've also already tried to simply increase the indentation inside the formatConditionally and decrease the indentation outside of it (using the api underneath the 'new' formatting api), but that becomes an issue with twice nested ifs like

If true:
  If true:
    superduperlongstatementthatdoesntfitinoneline


In that case, even the outer layer of the call would be at the end of a subdocument.
Re: Nested Conditional Formatters always create Conflict [message #1862549 is a reply to message #1862548] Mon, 11 December 2023 13:34 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14695
Registered: July 2009
Senior Member
no idea. did you check the useage in xbase?

Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Re: Nested Conditional Formatters always create Conflict [message #1862614 is a reply to message #1862549] Thu, 14 December 2023 13:24 Go to previous messageGo to next message
Oscar Ablinger is currently offline Oscar AblingerFriend
Messages: 6
Registered: August 2023
Junior Member
From what I've seen in xbase, they try to generally avoid formatConditionally, prefering to check through other means (like whether or not the area is multiline).

In the cases where they use it, it's a strict subpart of the part that the same function deals with. For instance, for closures:
[ ... | ... ]

The subdocument for the conditional formatting would only include the things between | and ] (including the whitespace immediatedly before/after these tokens, but not the tokens themselves).

That works well for most cases, but I have an issue specifically with the case where there is no token ending the area.

Weirdly enough I'm struggling to reproduce it in the minimal example right now :/
I'll update once I find a way to reproduce it otherwise it doesn't really make sense to theorise imo.

In the minimal example it seems to simply work if you change it to include the whitespace before and after the statement.
I do have a different issue when doing that, though: I would like to have each statement on its own line.
But then it will always use the multiline version, which makes sense.
he requireFitsInLine of course checks that there is no newlines and I explicitely add a newline with

doc.append(statement, IHiddenRegionFormatter::newLine);


But if I exclude it from the requireFitsInLine then I cannot do the indentation in nested statements:

{{ }}: Subdocument from requireFitsInLine
[[ ]]:  Subdocument from formatConditionally

if true:{{{ if true:[[[ superduperlongstatementthatdoesntfitinoneline}}}
]]]


I do actually suspect that this might be the reason for why it didn't work in our grammar proper.
If you know a recommended way to go about this, I'd appreciate it.
Otherwise I'll continue trying to reproduce it in the minimal example today & next week and come back if/when I manage.


In any case, thanks for your help Christian :)
Re: Nested Conditional Formatters always create Conflict [message #1862615 is a reply to message #1862614] Thu, 14 December 2023 13:26 Go to previous message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14695
Registered: July 2009
Senior Member
i have zero clue. i propose you debug what is happening.
of course a reproduce will help


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de

[Updated on: Thu, 14 December 2023 13:49]

Report message to a moderator

Previous Topic:Ambiguous rules and Predicate behavior
Next Topic:OutofMemoryError when trying to use Spring Boot
Goto Forum:
  


Current Time: Sat Jul 13 13:29:00 GMT 2024

Powered by FUDForum. Page generated in 0.03585 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top