1#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum Doc {
12 Nil,
14
15 Text(String),
17
18 Line,
21
22 SoftLine,
25
26 HardLine,
28
29 Indent(Box<Doc>),
31
32 Concat(Box<Doc>, Box<Doc>),
34
35 Group(Box<Doc>),
38
39 IfBreak { flat: Box<Doc>, broken: Box<Doc> },
43}
44
45impl Doc {
46 pub fn text(s: impl Into<String>) -> Doc {
48 let s = s.into();
49 if s.is_empty() { Doc::Nil } else { Doc::Text(s) }
50 }
51
52 pub fn line() -> Doc {
54 Doc::Line
55 }
56
57 pub fn softline() -> Doc {
59 Doc::SoftLine
60 }
61
62 pub fn hardline() -> Doc {
64 Doc::HardLine
65 }
66
67 pub fn indent(doc: Doc) -> Doc {
69 if matches!(doc, Doc::Nil) {
70 Doc::Nil
71 } else {
72 Doc::Indent(Box::new(doc))
73 }
74 }
75
76 pub fn group(doc: Doc) -> Doc {
78 if matches!(doc, Doc::Nil) {
79 Doc::Nil
80 } else {
81 Doc::Group(Box::new(doc))
82 }
83 }
84
85 pub fn concat(self, other: Doc) -> Doc {
87 match (self, other) {
88 (Doc::Nil, other) => other,
89 (this, Doc::Nil) => this,
90 (this, other) => Doc::Concat(Box::new(this), Box::new(other)),
91 }
92 }
93
94 pub fn if_break(flat: Doc, broken: Doc) -> Doc {
96 Doc::IfBreak {
97 flat: Box::new(flat),
98 broken: Box::new(broken),
99 }
100 }
101
102 pub fn join(docs: impl IntoIterator<Item = Doc>, sep: Doc) -> Doc {
104 let mut result = Doc::Nil;
105 let mut first = true;
106 for doc in docs {
107 if first {
108 first = false;
109 result = doc;
110 } else {
111 result = result.concat(sep.clone()).concat(doc);
112 }
113 }
114 result
115 }
116
117 pub fn concat_all(docs: impl IntoIterator<Item = Doc>) -> Doc {
119 let mut result = Doc::Nil;
120 for doc in docs {
121 result = result.concat(doc);
122 }
123 result
124 }
125
126 pub fn surround(prefix: Doc, content: Doc, suffix: Doc) -> Doc {
128 prefix.concat(content).concat(suffix)
129 }
130}
131
132#[derive(Debug, Default)]
134pub struct DocBuilder {
135 parts: Vec<Doc>,
136}
137
138impl DocBuilder {
139 pub fn new() -> Self {
141 Self { parts: Vec::new() }
142 }
143
144 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
146 self.parts.push(Doc::text(s));
147 self
148 }
149
150 pub fn line(&mut self) -> &mut Self {
152 self.parts.push(Doc::line());
153 self
154 }
155
156 pub fn softline(&mut self) -> &mut Self {
158 self.parts.push(Doc::softline());
159 self
160 }
161
162 pub fn hardline(&mut self) -> &mut Self {
164 self.parts.push(Doc::hardline());
165 self
166 }
167
168 pub fn push(&mut self, doc: Doc) -> &mut Self {
170 self.parts.push(doc);
171 self
172 }
173
174 pub fn build(self) -> Doc {
176 Doc::concat_all(self.parts)
177 }
178
179 pub fn build_group(self) -> Doc {
181 Doc::group(self.build())
182 }
183
184 pub fn build_indent(self) -> Doc {
186 Doc::indent(self.build())
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_text_empty() {
196 assert_eq!(Doc::text(""), Doc::Nil);
197 }
198
199 #[test]
200 fn test_text_non_empty() {
201 assert_eq!(Doc::text("hello"), Doc::Text("hello".to_string()));
202 }
203
204 #[test]
205 fn test_concat_nil() {
206 let doc = Doc::text("a").concat(Doc::Nil);
207 assert_eq!(doc, Doc::Text("a".to_string()));
208
209 let doc = Doc::Nil.concat(Doc::text("b"));
210 assert_eq!(doc, Doc::Text("b".to_string()));
211 }
212
213 #[test]
214 fn test_join() {
215 let docs = vec![Doc::text("a"), Doc::text("b"), Doc::text("c")];
216 let joined = Doc::join(docs, Doc::text(", "));
217 assert!(matches!(joined, Doc::Concat(_, _)));
219 }
220
221 #[test]
222 fn test_builder() {
223 let doc = {
224 let mut b = DocBuilder::new();
225 b.text("hello").line().text("world");
226 b.build()
227 };
228 assert!(matches!(doc, Doc::Concat(_, _)));
229 }
230}