aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-11-15 23:45:25 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-11-15 23:45:25 +0100
commita136840664ff6190821f276c7a081152bd391dc2 (patch)
treea2c1eeb471964b7e89fd467dad257040d81f9586
parentchore(lang): fix Sonar issue (diff)
downloadrefinery-a136840664ff6190821f276c7a081152bd391dc2.tar.gz
refinery-a136840664ff6190821f276c7a081152bd391dc2.tar.zst
refinery-a136840664ff6190821f276c7a081152bd391dc2.zip
feat(lang): basic formatting
Adds support for formatting some elements without any indentation. Mostly for testing model serialization with some human-readable formatting instead of just space-separating the tokens. Finishing the formatter to support all language constructs might be a bit more difficult due to our Prolog-like indentation rules.
-rw-r--r--language/src/main/java/tools/refinery/language/GenerateProblem.mwe23
-rw-r--r--language/src/main/java/tools/refinery/language/Problem.xtext9
-rw-r--r--language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java183
-rw-r--r--language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java235
-rw-r--r--language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java42
5 files changed, 454 insertions, 18 deletions
diff --git a/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 b/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2
index 58620d6a..0d934b68 100644
--- a/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2
+++ b/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2
@@ -39,6 +39,9 @@ Workflow {
39 serializer = { 39 serializer = {
40 generateStub = false 40 generateStub = false
41 } 41 }
42 formatter = {
43 generateStub = true
44 }
42 validator = { 45 validator = {
43 generateDeprecationValidation = true 46 generateDeprecationValidation = true
44 } 47 }
diff --git a/language/src/main/java/tools/refinery/language/Problem.xtext b/language/src/main/java/tools/refinery/language/Problem.xtext
index 26773047..c94d40ab 100644
--- a/language/src/main/java/tools/refinery/language/Problem.xtext
+++ b/language/src/main/java/tools/refinery/language/Problem.xtext
@@ -8,7 +8,8 @@ Problem:
8 statements+=Statement*; 8 statements+=Statement*;
9 9
10Statement: 10Statement:
11 ClassDeclaration | EnumDeclaration | PredicateDefinition | RuleDefinition | Assertion | NodeValueAssertion | ScopeDeclaration | 11 ClassDeclaration | EnumDeclaration | PredicateDefinition | RuleDefinition | Assertion | NodeValueAssertion |
12 ScopeDeclaration |
12 IndividualDeclaration; 13 IndividualDeclaration;
13 14
14ClassDeclaration: 15ClassDeclaration:
@@ -67,7 +68,7 @@ Literal:
67 68
68ValueLiteral: 69ValueLiteral:
69 atom=Atom 70 atom=Atom
70 (refinement?=":"|"=") 71 (refinement?=":" | "=")
71 values+=LogicConstant ("|" values+=LogicConstant)*; 72 values+=LogicConstant ("|" values+=LogicConstant)*;
72 73
73NegativeLiteral: 74NegativeLiteral:
@@ -78,7 +79,7 @@ ActionLiteral:
78 79
79ValueActionLiteral: 80ValueActionLiteral:
80 atom=Atom 81 atom=Atom
81 (refinement?=":"|"=") 82 (refinement?=":" | "=")
82 value=LogicValue; 83 value=LogicValue;
83 84
84DeleteActionLiteral: 85DeleteActionLiteral:
@@ -86,7 +87,7 @@ DeleteActionLiteral:
86 87
87NewActionLiteral: 88NewActionLiteral:
88 "new" variable=NewVariable; 89 "new" variable=NewVariable;
89 90
90NewVariable: 91NewVariable:
91 name=Identifier; 92 name=Identifier;
92 93
diff --git a/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java
new file mode 100644
index 00000000..903347f7
--- /dev/null
+++ b/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java
@@ -0,0 +1,183 @@
1/*
2 * generated by Xtext 2.26.0.M2
3 */
4package tools.refinery.language.formatting2;
5
6import org.eclipse.emf.ecore.EObject;
7import org.eclipse.xtext.formatting2.AbstractJavaFormatter;
8import org.eclipse.xtext.formatting2.IFormattableDocument;
9import org.eclipse.xtext.formatting2.IHiddenRegionFormatter;
10import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
11import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion;
12import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
13
14import tools.refinery.language.model.problem.Assertion;
15import tools.refinery.language.model.problem.Atom;
16import tools.refinery.language.model.problem.ClassDeclaration;
17import tools.refinery.language.model.problem.Conjunction;
18import tools.refinery.language.model.problem.IndividualDeclaration;
19import tools.refinery.language.model.problem.NegativeLiteral;
20import tools.refinery.language.model.problem.Parameter;
21import tools.refinery.language.model.problem.PredicateDefinition;
22import tools.refinery.language.model.problem.Problem;
23import tools.refinery.language.model.problem.ProblemPackage;
24
25public class ProblemFormatter extends AbstractJavaFormatter {
26
27 protected void format(Problem problem, IFormattableDocument doc) {
28 doc.prepend(problem, this::noSpace);
29 var region = regionFor(problem);
30 doc.append(region.keyword("problem"), this::oneSpace);
31 doc.prepend(region.keyword("."), this::noSpace);
32 appendNewLines(doc, region.keyword("."), this::twoNewLines);
33 for (var statement : problem.getStatements()) {
34 doc.format(statement);
35 }
36 }
37
38 protected void format(Assertion assertion, IFormattableDocument doc) {
39 surroundNewLines(doc, assertion, this::singleNewLine);
40 var region = regionFor(assertion);
41 doc.append(region.feature(ProblemPackage.Literals.ASSERTION__DEFAULT), this::oneSpace);
42 doc.append(region.feature(ProblemPackage.Literals.ASSERTION__VALUE), this::noSpace);
43 doc.append(region.feature(ProblemPackage.Literals.ASSERTION__RELATION), this::noSpace);
44 formatParenthesizedList(region, doc);
45 doc.prepend(region.keyword(":"), this::noSpace);
46 doc.append(region.keyword(":"), this::oneSpace);
47 doc.prepend(region.keyword("."), this::noSpace);
48 for (var argument : assertion.getArguments()) {
49 doc.format(argument);
50 }
51 }
52
53 protected void format(ClassDeclaration classDeclaration, IFormattableDocument doc) {
54 surroundNewLines(doc, classDeclaration, this::twoNewLines);
55 var region = regionFor(classDeclaration);
56 doc.append(region.feature(ProblemPackage.Literals.CLASS_DECLARATION__ABSTRACT), this::oneSpace);
57 doc.append(region.keyword("class"), this::oneSpace);
58 doc.surround(region.keyword("extends"), this::oneSpace);
59 formatList(region, ",", doc);
60 doc.prepend(region.keyword("{"), this::oneSpace);
61 doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2));
62 doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2));
63 doc.prepend(region.keyword("."), this::noSpace);
64 for (var referenceDeclaration : classDeclaration.getReferenceDeclarations()) {
65 doc.format(referenceDeclaration);
66 }
67 }
68
69 protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) {
70 surroundNewLines(doc, predicateDefinition, this::twoNewLines);
71 var region = regionFor(predicateDefinition);
72 doc.append(region.feature(ProblemPackage.Literals.PREDICATE_DEFINITION__KIND), this::oneSpace);
73 doc.append(region.keyword("pred"), this::oneSpace);
74 doc.append(region.feature(ProblemPackage.Literals.NAMED_ELEMENT__NAME), this::noSpace);
75 formatParenthesizedList(region, doc);
76 doc.surround(region.keyword("<->"), this::oneSpace);
77 formatList(region, ";", doc);
78 doc.prepend(region.keyword("."), this::noSpace);
79 for (var parameter : predicateDefinition.getParameters()) {
80 doc.format(parameter);
81 }
82 for (var body : predicateDefinition.getBodies()) {
83 doc.format(body);
84 }
85 }
86
87 protected void format(Parameter parameter, IFormattableDocument doc) {
88 doc.append(regionFor(parameter).feature(ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE), this::oneSpace);
89 }
90
91 protected void format(Conjunction conjunction, IFormattableDocument doc) {
92 var region = regionFor(conjunction);
93 formatList(region, ",", doc);
94 for (var literal : conjunction.getLiterals()) {
95 doc.format(literal);
96 }
97 }
98
99 protected void format(NegativeLiteral literal, IFormattableDocument doc) {
100 var region = regionFor(literal);
101 doc.append(region.keyword("!"), this::noSpace);
102 doc.format(literal.getAtom());
103 }
104
105 protected void format(Atom atom, IFormattableDocument doc) {
106 var region = regionFor(atom);
107 doc.append(region.feature(ProblemPackage.Literals.ATOM__RELATION), this::noSpace);
108 doc.append(region.feature(ProblemPackage.Literals.ATOM__TRANSITIVE_CLOSURE), this::noSpace);
109 formatParenthesizedList(region, doc);
110 for (var argument : atom.getArguments()) {
111 doc.format(argument);
112 }
113 }
114
115 protected void format(IndividualDeclaration individualDeclaration, IFormattableDocument doc) {
116 surroundNewLines(doc, individualDeclaration, this::singleNewLine);
117 var region = regionFor(individualDeclaration);
118 doc.append(region.keyword("indiv"), this::oneSpace);
119 formatList(region, ",", doc);
120 doc.prepend(region.keyword("."), this::noSpace);
121 }
122
123 protected void formatParenthesizedList(ISemanticRegionsFinder region, IFormattableDocument doc) {
124 doc.append(region.keyword("("), this::noSpace);
125 doc.prepend(region.keyword(")"), this::noSpace);
126 formatList(region, ",", doc);
127 }
128
129 protected void formatList(ISemanticRegionsFinder region, String separator, IFormattableDocument doc) {
130 for (var comma : region.keywords(separator)) {
131 doc.prepend(comma, this::noSpace);
132 doc.append(comma, this::oneSpace);
133 }
134 }
135
136 protected void singleNewLine(IHiddenRegionFormatter it) {
137 it.setNewLines(1, 1, 2);
138 }
139
140 protected void twoNewLines(IHiddenRegionFormatter it) {
141 it.highPriority();
142 it.setNewLines(2);
143 }
144
145 protected void surroundNewLines(IFormattableDocument doc, EObject eObject,
146 Procedure1<? super IHiddenRegionFormatter> init) {
147 var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject);
148 preprendNewLines(doc, region, init);
149 appendNewLines(doc, region, init);
150 }
151
152 protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region,
153 Procedure1<? super IHiddenRegionFormatter> init) {
154 if (region == null) {
155 return;
156 }
157 var previousHiddenRegion = region.getPreviousHiddenRegion();
158 if (previousHiddenRegion == null) {
159 return;
160 }
161 if (previousHiddenRegion.getPreviousSequentialRegion() == null) {
162 doc.set(previousHiddenRegion, it -> it.setNewLines(0));
163 } else {
164 doc.set(previousHiddenRegion, init);
165 }
166 }
167
168 protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region,
169 Procedure1<? super IHiddenRegionFormatter> init) {
170 if (region == null) {
171 return;
172 }
173 var nextHiddenRegion = region.getNextHiddenRegion();
174 if (nextHiddenRegion == null) {
175 return;
176 }
177 if (nextHiddenRegion.getNextSequentialRegion() == null) {
178 doc.set(nextHiddenRegion, it -> it.setNewLines(1));
179 } else {
180 doc.set(nextHiddenRegion, init);
181 }
182 }
183}
diff --git a/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java b/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java
new file mode 100644
index 00000000..854c3da1
--- /dev/null
+++ b/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java
@@ -0,0 +1,235 @@
1package tools.refinery.language.tests.formatting2;
2
3import static org.hamcrest.MatcherAssert.assertThat;
4import static org.hamcrest.Matchers.equalTo;
5
6import java.util.List;
7
8import org.eclipse.xtext.formatting2.FormatterRequest;
9import org.eclipse.xtext.formatting2.IFormatter2;
10import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
11import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
12import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder;
13import org.eclipse.xtext.resource.XtextResource;
14import org.eclipse.xtext.testing.InjectWith;
15import org.eclipse.xtext.testing.extensions.InjectionExtension;
16import org.eclipse.xtext.testing.util.ParseHelper;
17import org.junit.jupiter.api.Test;
18import org.junit.jupiter.api.extension.ExtendWith;
19
20import com.google.inject.Inject;
21import com.google.inject.Provider;
22
23import tools.refinery.language.model.problem.Problem;
24import tools.refinery.language.tests.ProblemInjectorProvider;
25
26@ExtendWith(InjectionExtension.class)
27@InjectWith(ProblemInjectorProvider.class)
28class ProblemFormatterTest {
29 @Inject
30 private ParseHelper<Problem> parseHelper;
31
32 @Inject
33 private Provider<FormatterRequest> formatterRequestProvider;
34
35 @Inject
36 private TextRegionAccessBuilder regionBuilder;
37
38 @Inject
39 private IFormatter2 formatter2;
40
41 @Test
42 void problemNameTest() {
43 testFormatter(" problem problem . ", "problem problem.\n");
44 }
45
46 @Test
47 void assertionTest() {
48 testFormatter(" equals ( a , b , * ) : true . ", "equals(a, b, *): true.\n");
49 }
50
51 @Test
52 void defaultAssertionTest() {
53 testFormatter(" default equals ( a , b , * ) : true . ", "default equals(a, b, *): true.\n");
54 }
55
56 @Test
57 void assertionShortTrueTest() {
58 testFormatter(" equals ( a , b , * ) . ", "equals(a, b, *).\n");
59 }
60
61 @Test
62 void defaultAssertionShortTrueTest() {
63 testFormatter(" default equals ( a , b , * ) . ", "default equals(a, b, *).\n");
64 }
65
66 @Test
67 void assertionShortFalseTest() {
68 testFormatter(" ! equals ( a , b , * ) . ", "!equals(a, b, *).\n");
69 }
70
71 @Test
72 void defaultAssertionShortFalseTest() {
73 testFormatter(" default ! equals ( a , b , * ) . ", "default !equals(a, b, *).\n");
74 }
75
76 @Test
77 void assertionShortUnknownTest() {
78 testFormatter(" ? equals ( a , b , * ) . ", "?equals(a, b, *).\n");
79 }
80
81 @Test
82 void defaultAssertionShortUnknownTest() {
83 testFormatter(" default ? equals ( a , b , * ) . ", "default ?equals(a, b, *).\n");
84 }
85
86 @Test
87 void multipleAssertionsTest() {
88 testFormatter(" exists ( a ) . ? equals ( a , a ).", """
89 exists(a).
90 ?equals(a, a).
91 """);
92 }
93
94 @Test
95 void multipleAssertionsNamedProblemTest() {
96 testFormatter(" problem foo . exists ( a ) . ? equals ( a , a ).", """
97 problem foo.
98
99 exists(a).
100 ?equals(a, a).
101 """);
102 }
103
104 @Test
105 void classWithoutBodyTest() {
106 testFormatter(" class Foo . ", "class Foo.\n");
107 }
108
109 @Test
110 void abstractClassWithoutBodyTest() {
111 testFormatter(" abstract class Foo . ", "abstract class Foo.\n");
112 }
113
114 @Test
115 void classExtendsWithoutBodyTest() {
116 testFormatter(" class Foo. class Bar . class Quux extends Foo , Bar . ", """
117 class Foo.
118
119 class Bar.
120
121 class Quux extends Foo, Bar.
122 """);
123 }
124
125 @Test
126 void classWithEmptyBodyTest() {
127 testFormatter(" class Foo { } ", """
128 class Foo {
129 }
130 """);
131 }
132
133 @Test
134 void classExtendsWithBodyTest() {
135 testFormatter(" class Foo. class Bar . class Quux extends Foo , Bar { } ", """
136 class Foo.
137
138 class Bar.
139
140 class Quux extends Foo, Bar {
141 }
142 """);
143 }
144
145 @Test
146 void predicateWithoutBodyTest() {
147 testFormatter(" pred foo ( node a , b ) . ", "pred foo(node a, b).\n");
148 }
149
150 @Test
151 void predicateWithBodyTest() {
152 testFormatter(
153 " pred foo ( node a , b ) <-> equal (a , _c ) , ! equal ( a , b ) ; equal+( a , b ) . ",
154 "pred foo(node a, b) <-> equal(a, _c), !equal(a, b); equal+(a, b).\n");
155 }
156
157 @Test
158 void predicatesWithoutBodyTest() {
159 testFormatter(" pred foo ( node a , b ) . pred bar ( node c ) . ", """
160 pred foo(node a, b).
161
162 pred bar(node c).
163 """);
164 }
165
166 @Test
167 void predicateCommentsTest() {
168 testFormatter("""
169 % Some foo
170 pred foo ( node a , b ) .
171 % Some bar
172 pred bar ( node c ) .
173 """, """
174 % Some foo
175 pred foo(node a, b).
176
177 % Some bar
178 pred bar(node c).
179 """);
180 }
181
182 @Test
183 void individualDeclarationTest() {
184 testFormatter(" indiv a , b . ", "indiv a, b.\n");
185 }
186
187 @Test
188 void mixedDeclarationsTest() {
189 testFormatter("""
190 problem test.
191 pred foo(node a).
192 class Foo.
193 foo(n1, n2).
194 indiv i1.
195 !foo(i1, n1).
196 pred bar(node a, node b).
197 pred quux().
198 default !bar(*, *).
199 """, """
200 problem test.
201
202 pred foo(node a).
203
204 class Foo.
205
206 foo(n1, n2).
207 indiv i1.
208 !foo(i1, n1).
209
210 pred bar(node a, node b).
211
212 pred quux().
213
214 default !bar(*, *).
215 """);
216 }
217
218 private void testFormatter(String toFormat, String expected) {
219 Problem problem;
220 try {
221 problem = parseHelper.parse(toFormat);
222 } catch (Exception e) {
223 throw new RuntimeException("Failed to parse document", e);
224 }
225 var resource = (XtextResource) problem.eResource();
226 FormatterRequest request = formatterRequestProvider.get();
227 request.setAllowIdentityEdits(false);
228 request.setFormatUndefinedHiddenRegionsOnly(false);
229 ITextRegionAccess regionAccess = regionBuilder.forNodeModel(resource).create();
230 request.setTextRegionAccess(regionAccess);
231 List<ITextReplacement> replacements = formatter2.format(request);
232 var formattedString = regionAccess.getRewriter().renderToString(replacements);
233 assertThat(formattedString, equalTo(expected));
234 }
235}
diff --git a/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
index 22c79a09..ba3aaeb7 100644
--- a/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
+++ b/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
@@ -38,16 +38,16 @@ import tools.refinery.language.tests.ProblemInjectorProvider;
38@InjectWith(ProblemInjectorProvider.class) 38@InjectWith(ProblemInjectorProvider.class)
39class ProblemSerializerTest { 39class ProblemSerializerTest {
40 @Inject 40 @Inject
41 ResourceSet resourceSet; 41 private ResourceSet resourceSet;
42 42
43 @Inject 43 @Inject
44 ProblemTestUtil testUtil; 44 private ProblemTestUtil testUtil;
45 45
46 Resource resource; 46 private Resource resource;
47 47
48 Problem problem; 48 private Problem problem;
49 49
50 Problem builtin; 50 private Problem builtin;
51 51
52 @BeforeEach 52 @BeforeEach
53 void beforeEach() { 53 void beforeEach() {
@@ -68,14 +68,16 @@ class ProblemSerializerTest {
68 problem.getStatements().add(individualDeclaration); 68 problem.getStatements().add(individualDeclaration);
69 createAssertion(pred, node, value); 69 createAssertion(pred, node, value);
70 70
71 assertSerializedResult("pred foo ( node p ) . indiv a . " + serializedAssertion); 71 assertSerializedResult("""
72 pred foo(node p).
73
74 indiv a.
75 """ + serializedAssertion + "\n");
72 } 76 }
73 77
74 static Stream<Arguments> assertionTest() { 78 static Stream<Arguments> assertionTest() {
75 return Stream.of(Arguments.of(LogicValue.TRUE, "foo ( a ) ."), 79 return Stream.of(Arguments.of(LogicValue.TRUE, "foo(a)."), Arguments.of(LogicValue.FALSE, "!foo(a)."),
76 Arguments.of(LogicValue.FALSE, "! foo ( a ) ."), 80 Arguments.of(LogicValue.UNKNOWN, "?foo(a)."), Arguments.of(LogicValue.ERROR, "foo(a): error."));
77 Arguments.of(LogicValue.UNKNOWN, "? foo ( a ) ."),
78 Arguments.of(LogicValue.ERROR, "foo ( a ) : error ."));
79 } 81 }
80 82
81 @Test 83 @Test
@@ -86,7 +88,11 @@ class ProblemSerializerTest {
86 problem.getNodes().add(node); 88 problem.getNodes().add(node);
87 createAssertion(pred, node); 89 createAssertion(pred, node);
88 90
89 assertSerializedResult("pred foo ( node p ) . foo ( a ) ."); 91 assertSerializedResult("""
92 pred foo(node p).
93
94 foo(a).
95 """);
90 } 96 }
91 97
92 private PredicateDefinition createPred() { 98 private PredicateDefinition createPred() {
@@ -111,7 +117,11 @@ class ProblemSerializerTest {
111 problem.getStatements().add(classDeclaration); 117 problem.getStatements().add(classDeclaration);
112 createAssertion(classDeclaration, newNode); 118 createAssertion(classDeclaration, newNode);
113 119
114 assertSerializedResult("class Foo . Foo ( Foo::new ) ."); 120 assertSerializedResult("""
121 class Foo.
122
123 Foo(Foo::new).
124 """);
115 } 125 }
116 126
117 private void createAssertion(Relation relation, Node node) { 127 private void createAssertion(Relation relation, Node node) {
@@ -151,7 +161,9 @@ class ProblemSerializerTest {
151 pred.getBodies().add(conjunction); 161 pred.getBodies().add(conjunction);
152 problem.getStatements().add(pred); 162 problem.getStatements().add(pred);
153 163
154 assertSerializedResult("pred foo ( node p1 , node p2 ) <-> equals ( p1 , q ) , equals ( q , p2 ) ."); 164 assertSerializedResult("""
165 pred foo(node p1, node p2) <-> equals(p1, q), equals(q, p2).
166 """);
155 } 167 }
156 168
157 private Atom createAtom(Relation relation, VariableOrNode variable1, VariableOrNode variable2) { 169 private Atom createAtom(Relation relation, VariableOrNode variable1, VariableOrNode variable2) {
@@ -192,7 +204,9 @@ class ProblemSerializerTest {
192 pred.getBodies().add(conjunction); 204 pred.getBodies().add(conjunction);
193 problem.getStatements().add(pred); 205 problem.getStatements().add(pred);
194 206
195 assertSerializedResult("pred foo ( node p ) <-> equals ( p , _q ) ."); 207 assertSerializedResult("""
208 pred foo(node p) <-> equals(p, _q).
209 """);
196 } 210 }
197 211
198 private void assertSerializedResult(String expected) { 212 private void assertSerializedResult(String expected) {