Skip to content

Commit 6bd08fe

Browse files
committed
Add TransitiveClosure test, improve handling of generic construct to ensure comments are added
1 parent 7e6539c commit 6bd08fe

File tree

9 files changed

+268
-13
lines changed

9 files changed

+268
-13
lines changed

src/main/java/me/fponzi/tlaplusformatter/TlaDocBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private void registerDefaultConstructs() {
4848
registry.register(new VariableConstruct());
4949
registry.register(new OperatorConstruct());
5050
registry.register(new ConstantConstruct());
51+
registry.register(new InfixLhsConstruct());
5152

5253
// Basic elements
5354
registry.register(new IdentifierConstruct());
@@ -103,6 +104,7 @@ private void registerDefaultConstructs() {
103104
registry.register(new TupleConstruct());
104105
registry.register(new SetEnumerateConstruct());
105106
registry.register(new TimesConstruct());
107+
registry.register(new IdPrefixConstruct());
106108

107109
}
108110

src/main/java/me/fponzi/tlaplusformatter/constructs/ConstructContext.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ public FormatConfig getConfig() {
4545
*/
4646
public Doc buildChild(TreeNode child) {
4747
var childDoc = docBuilder.build(child);
48-
var comments = Arrays.stream(child.getPreComments())
48+
return addComments(child, childDoc);
49+
}
50+
51+
public static Doc addComments(TreeNode node, Doc mainDoc) {
52+
var comments = Arrays.stream(node.getPreComments())
4953
.map((v) -> Doc.text(v.trim()))
5054
.collect(Collectors.toList());
51-
comments.add(childDoc);
55+
comments.add(mainDoc);
5256
return Doc.intersperse(Doc.line(), comments);
5357
}
5458

src/main/java/me/fponzi/tlaplusformatter/constructs/impl/AbstractAppendImageConstruct.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,34 @@
55
import me.fponzi.tlaplusformatter.constructs.TlaConstruct;
66
import tla2sany.st.TreeNode;
77

8+
import java.util.Optional;
9+
810
public abstract class AbstractAppendImageConstruct implements TlaConstruct {
911
public abstract int getSupportedNodeKind();
1012

1113
public abstract String getName();
1214

1315
@Override
1416
public final Doc buildDoc(TreeNode node, ConstructContext context, int indentSize) {
15-
return Doc.text(node.getHumanReadableImage());
17+
var z = node.zero();
18+
var o = node.one();
19+
if ((z == null || z.length == 0) && (o == null || o.length == 0)) {
20+
return Doc.text(node.getHumanReadableImage());
21+
}
22+
return Optional.ofNullable(buildChildren(node, context, z)).orElse(buildChildren(node, context, o));
23+
}
24+
25+
private Doc buildChildren(TreeNode node, ConstructContext context, TreeNode[] c) {
26+
if (c == null) {
27+
return null;
28+
}
29+
var childDoc = context.buildChild(c[0]);
30+
for (int i = 1; i < c.length; i++) {
31+
var nextChildDoc = context.buildChild(c[i]);
32+
if (nextChildDoc != null) {
33+
childDoc = childDoc.appendSpace(nextChildDoc);
34+
}
35+
}
36+
return childDoc;
1637
}
1738
}

src/main/java/me/fponzi/tlaplusformatter/constructs/impl/FunctionDefinitionConstruct.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@ public int getSupportedNodeKind() {
2828
@Override
2929
public Doc buildDoc(TreeNode node, ConstructContext context, int indentSize) {
3030
var o = node.one();
31-
assert (o != null && o.length == 6);
31+
assert (o != null && o.length >= 6);
3232
var oDoc = Arrays.stream(o).map(context::buildChild).collect(Collectors.toList());
33+
var boundVars = oDoc.get(2);
34+
for (int i = 3; i < o.length - 3; i++) {
35+
if (o[i].getImage().equals(",")) {
36+
boundVars = boundVars.append(oDoc.get(i));
37+
continue;
38+
}
39+
boundVars = boundVars.appendLineOrSpace(oDoc.get(i));
40+
}
41+
3342
return Doc.group(
3443
oDoc.get(0) // function name
3544
.append(oDoc.get(1))// [
36-
.append(oDoc.get(2)) // bound variables
37-
.append(oDoc.get(3)) // ]
38-
.appendSpace(oDoc.get(4)) // ==
39-
.appendLineOrSpace(oDoc.get(5)) // expr
45+
.append(boundVars) // one or more comma separated bound variables
46+
.append(oDoc.get(o.length - 3)) // ]
47+
.appendSpace(oDoc.get(o.length - 2)) // ==
48+
.appendLineOrSpace(oDoc.get(o.length - 1)) // expr
4049
).indent(indentSize);
4150
}
4251
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package me.fponzi.tlaplusformatter.constructs.impl;
2+
3+
import me.fponzi.tlaplusformatter.constructs.NodeKind;
4+
5+
/**
6+
* So far, I've always seen this used with an empty image.
7+
*/
8+
public class IdPrefixConstruct extends AbstractAppendImageConstruct {
9+
@Override
10+
public int getSupportedNodeKind() {
11+
return NodeKind.ID_PREFIX.getId();
12+
}
13+
14+
@Override
15+
public String getName() {
16+
return "N_IdPrefix";
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package me.fponzi.tlaplusformatter.constructs.impl;
2+
3+
import me.fponzi.tlaplusformatter.constructs.NodeKind;
4+
5+
/**
6+
* Example: 'R**T'
7+
*/
8+
public class InfixLhsConstruct extends AbstractAppendImageConstruct {
9+
@Override
10+
public int getSupportedNodeKind() {
11+
return NodeKind.INFIX_LHS.getId();
12+
}
13+
14+
@Override
15+
public String getName() {
16+
return "N_InfixLHS";
17+
}
18+
}

src/test/java/me/fponzi/tlaplusformatter/InputFolderTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ void testTowerOfHanoi() {
3434
}
3535

3636
@Test
37-
// TODO: fix.
3837
void testSlush() {
3938
testSpecFiles("Slush");
4039
}
@@ -43,4 +42,9 @@ void testSlush() {
4342
void testAllConstructs() {
4443
testSpecFiles("AllConstructs");
4544
}
45+
46+
@Test
47+
void testTransitiveClosure() {
48+
testSpecFiles("TransitiveClosure");
49+
}
4650
}

src/test/java/me/fponzi/tlaplusformatter/LexiconTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,7 @@ boolean compareAst(TreeNode root1, TreeNode root2) {
9090
void compareComments(TreeNode root1, TreeNode root2) {
9191
boolean hasComments1 = root1.getPreComments() != null && root1.getPreComments().length > 0;
9292
boolean hasComments2 = root2.getPreComments() != null && root2.getPreComments().length > 0;
93-
if (hasComments1 != hasComments2) {
94-
System.out.println("Node kind: " + root1.getKind());
95-
}
96-
assertEquals(hasComments1, hasComments2);
93+
assertEquals(hasComments1, hasComments2, "Comments do not match, Node image: " + root1.getHumanReadableImage() + " location: " + root1.getLocation());
9794
if (hasComments1) {
9895
assertEquals(root1.getPreComments().length, root2.getPreComments().length);
9996
for (int i = 0; i < root1.getPreComments().length; i++) {
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
-------------------------- MODULE TransitiveClosure -------------------------
2+
(***************************************************************************)
3+
(* Mathematicians define a relation R to be a set of ordered pairs, and *)
4+
(* write `s R t' to mean `<<s, t>> \in R'. The transitive closure TC(R) *)
5+
(* of the relation R is the smallest relation containing R such that, *)
6+
(* `s TC(R) t' and `t TC(R) u' imply `s TC(R) u', for any s, t, and u. *)
7+
(* This module shows several ways of defining the operator TC. *)
8+
(* *)
9+
(* It is sometimes more convenient to represent a relation as a *)
10+
(* Boolean-valued function of two arguments, where `s R t' means R[s, t]. *)
11+
(* It is a straightforward exercise to translate everything in this module *)
12+
(* to that representation. *)
13+
(* *)
14+
(* Mathematicians say that R is a relation on a set S iff R is a subset of *)
15+
(* S \X S. Let the `support' of a relation R be the set of all elements s *)
16+
(* such that `s R t' or `t R s' for some t. Then any relation is a *)
17+
(* relation on its support. Moreover, the support of R is the support of *)
18+
(* TC(R). So, to define the transitive closure of R, there's no need to *)
19+
(* say what set R is a relation on. *)
20+
(* *)
21+
(* Let's begin by importing some modules we'll need and defining the the *)
22+
(* support of a relation. *)
23+
(***************************************************************************)
24+
EXTENDS Integers, Sequences, FiniteSets, TLC
25+
26+
Support(R) == { r[1]: r \in R} \cup { r[2]: r \in R}
27+
28+
(***************************************************************************)
29+
(* A relation R defines a directed graph on its support, where there is an *)
30+
(* edge from s to t iff `s R t'. We can define TC(R) to be the relation *)
31+
(* such that `s R t' holds iff there is a path from s to t in this graph. *)
32+
(* We represent a path by the sequence of nodes on the path, so the length *)
33+
(* of the path (the number of edges) is one greater than the length of the *)
34+
(* sequence. We then get the following definition of TC. *)
35+
(***************************************************************************)
36+
TC(R) ==
37+
LET S == Support( R)
38+
IN {<<s ,t>> \in S \X S: \E p \in Seq( S):
39+
/\ Len( p) > 1
40+
/\ p[1] = s
41+
/\ p[ Len( p)] = t
42+
/\ \A i \in 1 .. ( Len( p) - 1 ): << p[ i], p[ i + 1] >> \in R
43+
}
44+
45+
(***************************************************************************)
46+
(* This definition can't be evaluated by TLC because Seq(S) is an infinite *)
47+
(* set. However, it's not hard to see that if R is a finite set, then it *)
48+
(* suffices to consider paths whose length is at most Cardinality(S). *)
49+
(* Modifying the definition of TC we get the following definition that *)
50+
(* defines TC1(R) to be the transitive closure of R, if R is a finite set. *)
51+
(* The LET expression defines BoundedSeq(S, n) to be the set of all *)
52+
(* sequences in Seq(S) of length at most n. *)
53+
(***************************************************************************)
54+
TC1(R) ==
55+
LET BoundedSeq(S, n) == UNION {[1 .. i -> S]: i \in 0 .. n}
56+
S == Support( R)
57+
IN {<<s ,t>> \in S \X S: \E p \in BoundedSeq( S, Cardinality( S) + 1):
58+
/\ Len( p) > 1
59+
/\ p[1] = s
60+
/\ p[ Len( p)] = t
61+
/\ \A i \in 1 .. ( Len( p) - 1 ): << p[ i], p[ i + 1] >> \in R
62+
}
63+
64+
(***************************************************************************)
65+
(* This naive method used by TLC to evaluate expressions makes this *)
66+
(* definition rather inefficient. (As an exercise, find an upper bound on *)
67+
(* its complexity.) To obtain a definition that TLC can evaluate more *)
68+
(* efficiently, let's look at the closure operation more algebraically. *)
69+
(* Let's define the composition of two relations R and T as follows. *)
70+
(***************************************************************************)
71+
R ** T ==
72+
LET SR == Support( R)
73+
ST == Support( T)
74+
IN {<<r ,t>> \in SR \X ST: \E s \in SR \cap ST:
75+
( << r, s >> \in R ) /\ ( << s, t >> \in T )
76+
}
77+
78+
(***************************************************************************)
79+
(* We can then define the closure of R to equal *)
80+
(* *)
81+
(* R \cup (R ** R) \cup (R ** R ** R) \cup ... *)
82+
(* *)
83+
(* For R finite, this union converges to the transitive closure when the *)
84+
(* number of terms equals the cardinality of the support of R. This leads *)
85+
(* to the following definition. *)
86+
(***************************************************************************)
87+
TC2(R) ==
88+
LET C[n \in Nat] ==
89+
IF n = 0 THEN R ELSE C[ n - 1] \cup ( C[ n - 1] ** R )
90+
IN IF R = {} THEN {} ELSE C[ Cardinality( Support( R)) - 1]
91+
92+
(***************************************************************************)
93+
(* These definitions of TC1 and TC2 are somewhat unsatisfactory because of *)
94+
(* their use of Cardinality(S). For example, it would be easy to make a *)
95+
(* mistake and use Cardinality(S) instead of Cardinality(S)+1 in the *)
96+
(* definition of TC1(R). I find the following definition more elegant *)
97+
(* than the preceding two. It is also more asymptotically more efficient *)
98+
(* because it makes O(log Cardinality (S)) rather than O(Cardinality(S)) *)
99+
(* recursive calls. *)
100+
(***************************************************************************)
101+
RECURSIVE TC3 ( _ )
102+
TC3(R) ==
103+
LET RR == R ** R IN IF RR \subseteq R THEN R ELSE TC3( R \cup RR)
104+
105+
(***************************************************************************)
106+
(* The preceding two definitions can be made slightly more efficient to *)
107+
(* execute by expanding the definition of ** and making some simple *)
108+
(* optimizations. But, this is unlikely to be worth complicating the *)
109+
(* definitions for. *)
110+
(* *)
111+
(* The following definition is (asymptotically) the most efficient. It is *)
112+
(* essentially the TLA+ representation of Warshall's algorithm. *)
113+
(* (Warshall's algorithm is typically written as an iterative procedure *)
114+
(* for the case of a relation on a set i..j of integers, when the relation *)
115+
(* is represented as a Boolean-valued function.) *)
116+
(***************************************************************************)
117+
TC4(R) ==
118+
LET S == Support( R)
119+
RECURSIVE TCR ( _ )
120+
TCR(T) ==
121+
IF T = {}
122+
THEN R
123+
ELSE LET r == CHOOSE s \in T: TRUE
124+
RR == TCR( T \ { r })
125+
IN RR \cup
126+
{<<s ,t>> \in S \X S: << s, r >> \in RR /\ << r, t >> \in RR
127+
}
128+
IN TCR( S)
129+
130+
(***************************************************************************)
131+
(* We now test that these four definitions are equivalent. Since it's *)
132+
(* unlikely that all four are wrong in the same way, their equivalence *)
133+
(* makes it highly probable that they're correct. *)
134+
(***************************************************************************)
135+
ASSUME \A N \in 0 .. 3:
136+
\A R \in SUBSET ( ( 1 .. N ) \X ( 1 .. N ) ): /\ TC1( R) = TC2( R)
137+
/\ TC2( R) = TC3( R)
138+
/\ TC3( R) = TC4( R)
139+
140+
(***************************************************************************)
141+
(* Sometimes we want to represent a relation as a Boolean-valued operator, *)
142+
(* so we can write `s R t' as R(s, t). This representation is less *)
143+
(* convenient for manipulating relations, since an operator is not an *)
144+
(* ordinary value the way a function is. For example, since TLA+ does not *)
145+
(* permit us to define operator-valued operators, we cannot define a *)
146+
(* transitive closure operator TC so TC(R) is the operator that represents *)
147+
(* the transitive closure. Moreover, an operator R by itself cannot *)
148+
(* represent a relation; we also have to know what set it is an operator *)
149+
(* on. (If R is a function, its domain tells us that.) *)
150+
(* *)
151+
(* However, there may be situations in which you want to represent *)
152+
(* relations by operators. In that case, you can define an operator TC so *)
153+
(* that, if R is an operator representing a relation on S, and TCR is the *)
154+
(* operator representing it transitive closure, then *)
155+
(* *)
156+
(* TCR(s, t) = TC(R, S, s, t) *)
157+
(* *)
158+
(* for all s, t. Here is the definition. (This assumes that for an *)
159+
(* operator R on a set S, R(s, t) equals FALSE for all s and t not in S.) *)
160+
(***************************************************************************)
161+
TC5(R ( _ , _ ), S, s, t) ==
162+
LET CR[n \in Nat,
163+
v \in S] ==
164+
IF n = 0
165+
THEN R( s, v)
166+
ELSE \/ CR[ n - 1, v]
167+
\/ \E u \in S: CR[ n - 1, u] /\ R( u, v)
168+
IN /\ s \in S
169+
/\ t \in S
170+
/\ CR[ Cardinality( S) - 1, t]
171+
172+
(***************************************************************************)
173+
(* Finally, the following assumption checks that our definition TC5 agrees *)
174+
(* with our definition TC1. *)
175+
(***************************************************************************)
176+
ASSUME \A N \in 0 .. 3:
177+
\A R \in SUBSET ( ( 1 .. N ) \X ( 1 .. N ) ):
178+
LET RR(s, t) == << s, t >> \in R
179+
S == Support( R)
180+
IN \A s, t \in S:
181+
TC5( RR, S, s, t) <=> ( << s, t >> \in TC1( R) )
182+
=============================================================================

0 commit comments

Comments
 (0)