Skip to content

Commit 4495023

Browse files
Merge pull request #1 from opencastsoftware/fold-intersperse-bracket
Add fold and intersperse functions, add bracket overload where we can choose line separator
2 parents dea32e0 + 9b8ea31 commit 4495023

File tree

3 files changed

+193
-15
lines changed

3 files changed

+193
-15
lines changed

src/main/java/com/opencastsoftware/prettier4j/Doc.java

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import java.util.AbstractMap.SimpleEntry;
44
import java.util.ArrayDeque;
5+
import java.util.Collection;
56
import java.util.Deque;
67
import java.util.Map;
8+
import java.util.function.BinaryOperator;
9+
import java.util.stream.Stream;
710

811
/**
912
* Implements the algorithm described in Philip Wadler's "A prettier printer", a
@@ -151,6 +154,8 @@ public Doc indent(int indent) {
151154
* Bracket the current document by the {@code left} and {@code right} Strings,
152155
* indented by {@code indent} spaces.
153156
*
157+
* When collapsed, line separators are replaced by spaces.
158+
*
154159
* @param indent the number of spaces of indent to apply.
155160
* @param left the left-hand bracket String.
156161
* @param right the right-hand bracket String.
@@ -164,16 +169,50 @@ public Doc bracket(int indent, String left, String right) {
164169
* Bracket the current document by the {@code left} and {@code right} documents,
165170
* indented by {@code indent} spaces.
166171
*
172+
* When collapsed, line separators are replaced by spaces.
173+
*
167174
* @param indent the number of spaces of indent to apply.
168175
* @param left the left-hand bracket document.
169176
* @param right the right-hand bracket document.
170177
* @return the bracketed document.
171178
*/
172179
public Doc bracket(int indent, Doc left, Doc right) {
180+
return bracket(indent, Doc.lineOrSpace(), left, right);
181+
}
182+
183+
/**
184+
* Bracket the current document by the {@code left} and {@code right} Strings,
185+
* indented by {@code indent} spaces.
186+
*
187+
* When collapsed, line separators are replaced by the {@code lineDoc}.
188+
*
189+
* @param indent the number of spaces of indent to apply.
190+
* @param lineDoc the line separator document.
191+
* @param left the left-hand bracket document.
192+
* @param right the right-hand bracket document.
193+
* @return the bracketed document.
194+
*/
195+
public Doc bracket(int indent, Doc lineDoc, String left, String right) {
196+
return bracket(indent, lineDoc, Doc.text(left), Doc.text(right));
197+
}
198+
199+
/**
200+
* Bracket the current document by the {@code left} and {@code right} documents,
201+
* indented by {@code indent} spaces.
202+
*
203+
* When collapsed, line separators are replaced by the {@code lineDoc}.
204+
*
205+
* @param indent the number of spaces of indent to apply.
206+
* @param lineDoc the line separator document.
207+
* @param left the left-hand bracket document.
208+
* @param right the right-hand bracket document.
209+
* @return the bracketed document.
210+
*/
211+
public Doc bracket(int indent, Doc lineDoc, Doc left, Doc right) {
173212
return group(
174213
left
175-
.append(lineOrSpace().append(this).indent(indent))
176-
.appendLineOrSpace(right));
214+
.append(lineDoc.append(this).indent(indent))
215+
.append(lineDoc.append(right)));
177216
}
178217

179218
/**
@@ -691,13 +730,67 @@ public static Doc lineOr(String altText) {
691730
return new LineOr(text(altText));
692731
}
693732

733+
/**
734+
* Reduce a collection of documents using the binary operator {@code fn},
735+
* returning an empty document if the collection is empty.
736+
*
737+
* @param documents the collection of documents.
738+
* @param fn the binary operator for combining documents.
739+
* @return a document built by reducing the {@code documents} using the operator
740+
* {@code fn}.
741+
*/
742+
public static Doc fold(Collection<Doc> documents, BinaryOperator<Doc> fn) {
743+
return fold(documents.stream(), fn);
744+
}
745+
746+
/**
747+
* Reduce a stream of documents using the binary operator {@code fn}, returning
748+
* an empty document if the stream is empty.
749+
*
750+
* @param documents the stream of documents.
751+
* @param fn the binary operator for combining documents.
752+
* @return a document built by reducing the {@code documents} using the operator
753+
* {@code fn}.
754+
*/
755+
public static Doc fold(Stream<Doc> documents, BinaryOperator<Doc> fn) {
756+
return documents.reduce(fn).orElse(Doc.empty());
757+
}
758+
759+
/**
760+
* Intersperse a {@code separator} document in between the elements of a
761+
* collection of documents.
762+
*
763+
* @param separator the separator document.
764+
* @param documents the collection of documents.
765+
* @return a document containing the concatenation of {@code documents}
766+
* separated by the {@code separator}.
767+
*/
768+
public static Doc intersperse(Doc separator, Collection<Doc> documents) {
769+
return intersperse(separator, documents.stream());
770+
}
771+
772+
/**
773+
* Intersperse a {@code separator} document in between the elements of a stream
774+
* of documents.
775+
*
776+
* @param separator the separator document.
777+
* @param documents the stream of documents.
778+
* @return a document containing the concatenation of {@code documents}
779+
* separated by the {@code separator}.
780+
*/
781+
public static Doc intersperse(Doc separator, Stream<Doc> documents) {
782+
return Doc.fold(documents, (left, right) -> {
783+
return left.append(separator).append(right);
784+
});
785+
}
786+
694787
/**
695788
* Creates a {@link com.opencastsoftware.prettier4j.Doc Doc} which represents a
696789
* group that can be flattened into a more compact layout.
697790
*
698791
* @param doc the document which is declared as a group which may be flattened.
699792
* @return a {@link com.opencastsoftware.prettier4j.Doc Doc} which represents a
700-
* group that can be flattened into a more compact layout.
793+
* group that can be flattened into a more compact layout.
701794
*/
702795
public static Doc group(Doc doc) {
703796
return alternatives(doc.flatten(), doc);

src/test/java/com/opencastsoftware/prettier4j/DocTest.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,93 @@ void testAppendLineOrFlattening() {
158158
assertThat(actual, is(equalTo(expected)));
159159
}
160160

161+
@Test
162+
void testBracket() {
163+
String expected = "functionCall(a, b, c)";
164+
String actual = text("functionCall")
165+
.append(
166+
Doc.intersperse(
167+
Doc.text(",").append(Doc.lineOrSpace()),
168+
Arrays.asList("a", "b", "c").stream().map(Doc::text))
169+
.bracket(2, Doc.lineOrEmpty(), Doc.text("("), Doc.text(")")))
170+
.render(80);
171+
172+
assertThat(actual, is(equalTo(expected)));
173+
}
174+
175+
@Test
176+
void testBracketStringOverload() {
177+
String expected = "functionCall(a, b, c)";
178+
String actual = text("functionCall")
179+
.append(
180+
Doc.intersperse(
181+
Doc.text(",").append(Doc.lineOrSpace()),
182+
Arrays.asList("a", "b", "c").stream().map(Doc::text))
183+
.bracket(2, Doc.lineOrEmpty(), "(", ")"))
184+
.render(80);
185+
186+
assertThat(actual, is(equalTo(expected)));
187+
}
188+
189+
@Test
190+
void testBracketFlattening() {
191+
String expected = "functionCall(\n a,\n b,\n c\n)";
192+
String actual = text("functionCall")
193+
.append(
194+
Doc.intersperse(
195+
Doc.text(",").append(Doc.lineOrSpace()),
196+
Arrays.asList("a", "b", "c").stream().map(Doc::text))
197+
.bracket(2, Doc.lineOrEmpty(), Doc.text("("), Doc.text(")")))
198+
.render(10);
199+
200+
assertThat(actual, is(equalTo(expected)));
201+
}
202+
203+
@Test
204+
void testIntersperse() {
205+
String expected = "a, b, c";
206+
String actual = Doc.intersperse(
207+
Doc.text(", "),
208+
Arrays.asList(Doc.text("a"), Doc.text("b"), Doc.text("c"))).render(80);
209+
assertThat(actual, is(equalTo(expected)));
210+
}
211+
212+
/**
213+
* This tests the {@code spread} operator of the original paper, implemented via
214+
* the Haskell functions:
215+
*
216+
* <pre>{@code
217+
* x <+> y = x <> text " " <> y
218+
* spread = folddoc (<+>)
219+
* }</pre>
220+
*/
221+
@Test
222+
void testSpreadOperator() {
223+
String expected = "a b c";
224+
String actual = Doc.fold(
225+
Arrays.asList(Doc.text("a"), Doc.text("b"), Doc.text("c")),
226+
(l, r) -> l.appendSpace(r)).render(80);
227+
assertThat(actual, is(equalTo(expected)));
228+
}
229+
230+
/**
231+
* This tests the {@code stack} operator of the original paper, implemented via
232+
* the Haskell functions:
233+
*
234+
* <pre>{@code
235+
* x </> y = x <> line <> y
236+
* stack = folddoc (</>)
237+
* }</pre>
238+
*/
239+
@Test
240+
void testStackOperator() {
241+
String expected = "a\nb\nc";
242+
String actual = Doc.fold(
243+
Arrays.asList(Doc.text("a"), Doc.text("b"), Doc.text("c")),
244+
(l, r) -> l.appendLine(r)).render(80);
245+
assertThat(actual, is(equalTo(expected)));
246+
}
247+
161248
/**
162249
* This property represents the observation that an atomic token must render
163250
* as nothing more than its text content.
@@ -407,6 +494,10 @@ Arbitrary<Doc> docs() {
407494
() -> Arbitraries.just(Doc.empty()),
408495
// Append
409496
() -> docs().tuple2().map(tuple -> tuple.get1().append(tuple.get2())),
497+
// Indent
498+
() -> docs().map(doc -> doc.indent(2)),
499+
// Bracketing
500+
() -> docs().map(doc -> doc.bracket(2, Doc.lineOrEmpty(), Doc.text("["), Doc.text("]"))),
410501
// Alternatives
411502
() -> docs().map(Doc::group));
412503
}

src/test/java/com/opencastsoftware/prettier4j/Node.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ public Doc show() {
1717
subTrees.isEmpty() ? Doc.empty()
1818
: Doc.text("[")
1919
.append(
20-
subTrees.stream()
21-
.map(Node::show)
22-
.reduce(Doc.empty(), (left, right) -> {
23-
return left instanceof Doc.Empty ? right
24-
: left.append(Doc.text(",")).appendLineOrSpace(right);
25-
}).indent(1))
20+
Doc.intersperse(
21+
Doc.text(",").append(Doc.lineOrSpace()),
22+
subTrees.stream().map(Node::show)).indent(1))
2623
.append(Doc.text("]"))
2724
.indent(data.length())));
2825
}
@@ -31,12 +28,9 @@ public Doc showPrime() {
3128
return Doc.text(data)
3229
.append(
3330
subTrees.isEmpty() ? Doc.empty()
34-
: subTrees.stream()
35-
.map(Node::showPrime)
36-
.reduce(Doc.empty(), (left, right) -> {
37-
return left instanceof Doc.Empty ? right
38-
: left.append(Doc.text(",")).appendLineOrSpace(right);
39-
})
31+
: Doc.intersperse(
32+
Doc.text(",").append(Doc.lineOrSpace()),
33+
subTrees.stream().map(Node::showPrime))
4034
.bracket(2, "[", "]"));
4135
}
4236
}

0 commit comments

Comments
 (0)