1use crate::{Code, Concat, Format, FormatCode};
2
3#[derive(derivative::Derivative)]
5#[derivative(Debug, Clone, PartialEq)]
6pub struct Block {
7 pub connect: bool,
10 pub start: String,
12 pub end: String,
14 concat_body: Concat,
16 #[derivative(Debug = "ignore", PartialEq = "ignore")]
18 inline_condition: Option<fn(&Block) -> bool>,
19}
20
21impl Block {
22 pub fn empty<TStart, TEnd>(start: TStart, end: TEnd) -> Self
24 where
25 TStart: ToString,
26 TEnd: ToString,
27 {
28 Self {
29 connect: false,
30 start: start.to_string(),
31 concat_body: Concat::empty(),
32 end: end.to_string(),
33 inline_condition: None,
34 }
35 }
36
37 pub fn new<TStart, TBody, TEnd>(start: TStart, body: TBody, end: TEnd) -> Self
39 where
40 TStart: ToString,
41 TEnd: ToString,
42 TBody: IntoIterator,
43 TBody::Item: Into<Code>,
44 {
45 Self {
46 connect: false,
47 start: start.to_string(),
48 concat_body: Concat::new(body),
49 end: end.to_string(),
50 inline_condition: None,
51 }
52 }
53
54 pub fn connected(mut self) -> Self {
56 self.connect = true;
57 self
58 }
59
60 pub fn inline_when(mut self, condition: fn(&Block) -> bool) -> Self {
62 self.inline_condition = Some(condition);
63 self
64 }
65
66 pub fn inlined(mut self) -> Self {
68 self.inline_condition = Some(|_| true);
69 self
70 }
71
72 pub fn never_inlined(mut self) -> Self {
74 self.inline_condition = Some(|_| false);
75 self
76 }
77
78 #[inline]
80 pub fn body(&self) -> &[Code] {
81 &self.concat_body
82 }
83
84 pub fn should_inline(&self) -> bool {
86 if let Some(condition) = self.inline_condition {
87 condition(self)
88 } else {
89 self.should_inline_intrinsic()
90 }
91 }
92
93 pub fn should_inline_intrinsic(&self) -> bool {
97 self.body().len() == 1 && self.body()[0].should_inline()
98 }
99}
100
101impl From<Block> for Code {
102 fn from(x: Block) -> Self {
103 Code::Block(Box::new(x))
104 }
105}
106
107impl std::fmt::Display for Block {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 write!(f, "{}", self.format())
110 }
111}
112
113impl FormatCode for Block {
114 fn size_hint(&self) -> usize {
115 self.concat_body.size_hint() + 2
117 }
118
119 fn format_into_vec_with(
120 &self,
121 format: &Format,
122 out: &mut Vec<String>,
123 connect: bool,
124 indent: &str,
125 ) {
126 let connect = self.connect || connect;
127 crate::append_line(out, &self.start, connect, indent);
128 let should_inline = self.should_inline();
129
130 if should_inline {
131 for code in self.body() {
132 code.format_into_vec_with(format, out, true, indent);
133 }
134 } else {
135 let i = format.indent;
137 let new_indent = if i < 0 {
138 format!("\t{indent}")
139 } else {
140 let i = i as usize;
141 format!("{:i$}{indent}", "")
142 };
143 for code in self.body() {
144 code.format_into_vec_with(format, out, false, &new_indent);
145 }
146 }
147 crate::append_line(out, &self.end, should_inline, indent);
148 }
149}
150
151#[macro_export]
200macro_rules! cblock {
201 ($start:expr, [] , $end:expr) => {
202 $crate::Block::empty($start, $end)
203 };
204 ($start:expr, [ $( $body:expr ),* $(,)? ] , $end:expr) => {
205 $crate::Block::new($start, [ $($crate::Code::from($body)),* ], $end)
206 };
207 ($start:expr, $body:expr, $end:expr) => {
208 $crate::Block::new($start, $body, $end)
209 };
210}
211
212#[cfg(test)]
213mod test {
214 use indoc::indoc;
215
216 #[test]
217 fn empty() {
218 let code = cblock!("", [], "");
219 assert_eq!("\n", code.to_string());
221 }
222
223 #[test]
224 fn empty_body() {
225 let code = cblock!("fn main() {", [], "}");
226 assert_eq!("fn main() {\n}", code.to_string());
227 }
228
229 #[test]
230 fn different_types() {
231 let code = cblock!(
232 "fn main() {",
233 [
234 "foo",
235 "bar".to_string(),
236 cblock!("if (x) {", ["baz", "qux".to_string(),], "}"),
237 ],
238 "}"
239 );
240 let expected = indoc! {"
241 fn main() {
242 foo
243 bar
244 if (x) {
245 baz
246 qux
247 }
248 }"};
249 assert_eq!(expected, code.to_string());
250 }
251
252 #[test]
253 fn iteratable() {
254 let body = vec![
255 cblock!("if (x()) {", ["bar();"], "}"),
256 cblock!("else if (y) {", ["baz();"], "}").connected(),
257 ];
258 let code = cblock!("fn foo(y: bool) {", body, "}");
259 let expected = indoc! {"
260 fn foo(y: bool) {
261 if (x()) {
262 bar();
263 } else if (y) {
264 baz();
265 }
266 }"};
267 assert_eq!(expected, code.to_string());
268 }
269
270 fn is_one_thing(block: &crate::Block) -> bool {
271 block.body().len() == 1
272 }
273
274 #[test]
275 fn inline_condition() {
276 let body = vec![
277 cblock!("if (x()) {", ["bar();"], "}").inline_when(is_one_thing),
278 cblock!("else if (y) {", ["baz();", "baz();"], "}")
279 .connected()
280 .inline_when(is_one_thing),
281 ];
282 let code = cblock!("fn foo(y: bool) {", body, "}");
283 let expected = indoc! {"
284 fn foo(y: bool) {
285 if (x()) { bar(); } else if (y) {
286 baz();
287 baz();
288 }
289 }"};
290 assert_eq!(expected, code.to_string());
291 }
292
293 #[test]
294 fn no_end_chaining() {
295 let code = cblock! {
296 "{",
297 [
298 "",
299 cblock!{
300 "if xxx:",
301 ["foo()"],
302 ""
303 }.connected(),
304 cblock!{
305 "elif yyy:",
306 ["bar()"],
307 ""
308 }.connected(),
309 ],
310 "}"
311 }
312 .never_inlined();
313 let expected = indoc! {"
314 {
315 if xxx:
316 foo()
317 elif yyy:
318 bar()
319
320 }"};
321 assert_eq!(expected, code.to_string());
322 }
323}