1use crate::ast::*;
9use crate::span::Span;
10
11const INLINE_MAX: usize = 80;
12
13pub fn format(file: &File, src: &str) -> String {
14 let f = Formatter { src };
15 let mut out = String::new();
16 let version = file
17 .header
18 .as_ref()
19 .map(|h| h.version.as_str())
20 .unwrap_or("0.1");
21 out.push_str(&format!("drawl {version}\n"));
22
23 let mut first = true;
24 for stmt in &file.stmts {
25 let want_blank = if first {
29 true
30 } else {
31 stmt.trivia.blank_before || !stmt.trivia.leading.is_empty() || is_block_stmt(stmt)
32 };
33 if want_blank {
34 out.push('\n');
35 }
36 first = false;
37 f.write_stmt(&mut out, stmt, 0);
38 }
39 while out.ends_with("\n\n") {
41 out.pop();
42 }
43 if !out.ends_with('\n') {
44 out.push('\n');
45 }
46 out
47}
48
49fn is_block_stmt(stmt: &Stmt) -> bool {
50 matches!(
51 stmt.kind,
52 StmtKind::Canvas(_)
53 | StmtKind::Def(_)
54 | StmtKind::Group(_)
55 | StmtKind::Class(_)
56 | StmtKind::Constrain(_)
57 | StmtKind::For(_)
58 )
59}
60
61struct Formatter<'a> {
62 src: &'a str,
63}
64
65impl<'a> Formatter<'a> {
66 fn snip(&self, span: Span) -> &str {
67 let s = span.start.min(self.src.len());
68 let e = span.end.min(self.src.len());
69 self.src[s..e].trim()
70 }
71
72 fn write_stmt(&self, out: &mut String, stmt: &Stmt, depth: usize) {
73 let indent = " ".repeat(depth);
74 for c in &stmt.trivia.leading {
75 out.push_str(&indent);
76 if c.is_empty() {
77 out.push_str("//\n");
78 } else {
79 out.push_str(&format!("// {c}\n"));
80 }
81 }
82 if let StmtKind::Prop(p) = &stmt.kind {
84 if p.key.is_empty() {
85 return;
86 }
87 }
88
89 let trailing = stmt
90 .trivia
91 .trailing
92 .as_ref()
93 .map(|c| format!(" // {c}"))
94 .unwrap_or_default();
95
96 match &stmt.kind {
97 StmtKind::Canvas(block) => {
98 out.push_str(&format!("{indent}canvas {{\n"));
99 self.write_block_stmts(out, block, depth + 1);
100 out.push_str(&format!("{indent}}}{trailing}\n"));
101 }
102 StmtKind::Def(d) => {
103 let params: Vec<&str> = d.params.iter().map(|p| p.name.as_str()).collect();
104 out.push_str(&format!(
105 "{indent}def {}({}) {{\n",
106 d.name.name,
107 params.join(", ")
108 ));
109 self.write_block_stmts(out, &d.body, depth + 1);
110 out.push_str(&format!("{indent}}}{trailing}\n"));
111 }
112 StmtKind::Group(g) => {
113 let label = g
114 .label
115 .as_ref()
116 .map(|l| format!(" {}", self.snip(l.span)))
117 .unwrap_or_default();
118 out.push_str(&format!("{indent}group {}{label} {{\n", g.name.name));
119 self.write_block_stmts(out, &g.body, depth + 1);
120 out.push_str(&format!("{indent}}}{trailing}\n"));
121 }
122 StmtKind::Class(c) => {
123 if let Some(line) = self.inline_block(&c.body) {
124 out.push_str(&format!("{indent}class {} {line}{trailing}\n", c.name.name));
125 } else {
126 out.push_str(&format!("{indent}class {} {{\n", c.name.name));
127 self.write_block_stmts(out, &c.body, depth + 1);
128 out.push_str(&format!("{indent}}}{trailing}\n"));
129 }
130 }
131 StmtKind::Constrain(cs) => {
132 out.push_str(&format!("{indent}constrain {{\n"));
133 let inner = " ".repeat(depth + 1);
134 for c in cs {
135 for lc in &c.trivia.leading {
136 out.push_str(&format!("{inner}// {lc}\n"));
137 }
138 let t = c
139 .trivia
140 .trailing
141 .as_ref()
142 .map(|x| format!(" // {x}"))
143 .unwrap_or_default();
144 out.push_str(&format!("{inner}{}{t}\n", self.snip(c.span)));
145 }
146 out.push_str(&format!("{indent}}}{trailing}\n"));
147 }
148 StmtKind::Pin(p) => {
149 out.push_str(&format!(
150 "{indent}pin {} at ({}, {}){trailing}\n",
151 self.snip(p.target.span),
152 self.snip(p.x.span),
153 self.snip(p.y.span)
154 ));
155 }
156 StmtKind::For(f) => {
157 if let Some(line) = self.inline_for(f) {
158 if indent.len() + line.len() <= INLINE_MAX {
159 out.push_str(&format!("{indent}{line}{trailing}\n"));
160 return;
161 }
162 }
163 out.push_str(&format!(
164 "{indent}for {} in {}..{} {{\n",
165 f.var.name,
166 self.snip(f.start.span),
167 self.snip(f.end.span)
168 ));
169 self.write_block_stmts(out, &f.body, depth + 1);
170 out.push_str(&format!("{indent}}}{trailing}\n"));
171 }
172 StmtKind::Port(p) => {
173 if let Some(line) = self.inline_block(&p.body) {
174 out.push_str(&format!("{indent}port {} {line}{trailing}\n", p.name.name));
175 } else {
176 out.push_str(&format!("{indent}port {} {{\n", p.name.name));
177 self.write_block_stmts(out, &p.body, depth + 1);
178 out.push_str(&format!("{indent}}}{trailing}\n"));
179 }
180 }
181 StmtKind::Prop(p) => {
182 out.push_str(&format!("{indent}{}{trailing}\n", self.prop_line(p)));
183 }
184 StmtKind::Node(n) => {
185 if let Some(line) = self.inline_node(n) {
186 if indent.len() + line.len() <= INLINE_MAX {
187 out.push_str(&format!("{indent}{line}{trailing}\n"));
188 return;
189 }
190 }
191 self.write_node_multiline(out, n, depth, &trailing);
192 }
193 StmtKind::Edge(e) => {
194 let op = match e.op {
195 EdgeOp::Forward => "->",
196 EdgeOp::Bidirectional => "<->",
197 };
198 let label = e
199 .label
200 .as_ref()
201 .map(|l| format!(" : {}", self.snip(l.span)))
202 .unwrap_or_default();
203 let props = match &e.props {
204 Some(b) => match self.inline_block(b) {
205 Some(line) => format!(" {line}"),
206 None => format!(" {}", self.snip(b.span)),
207 },
208 None => String::new(),
209 };
210 out.push_str(&format!(
211 "{indent}{} {op} {}{label}{props}{trailing}\n",
212 self.snip(e.from.span),
213 self.snip(e.to.span)
214 ));
215 }
216 }
217 }
218
219 fn write_block_stmts(&self, out: &mut String, block: &Block, depth: usize) {
220 let mut first = true;
221 for stmt in &block.stmts {
222 if !first && (stmt.trivia.blank_before || !stmt.trivia.leading.is_empty()) {
223 out.push('\n');
224 }
225 first = false;
226 self.write_stmt(out, stmt, depth);
227 }
228 }
229
230 fn write_node_multiline(&self, out: &mut String, n: &Node, depth: usize, trailing: &str) {
231 let indent = " ".repeat(depth);
232 match &n.kind {
233 NodeKind::Plain { body } => {
234 let name = n.name.as_ref().expect("plain nodes are named");
235 if body.stmts.is_empty() {
236 out.push_str(&format!("{indent}{}{trailing}\n", name.name));
237 return;
238 }
239 out.push_str(&format!("{indent}{} {{\n", name.name));
240 self.write_block_stmts(out, body, depth + 1);
241 out.push_str(&format!("{indent}}}{trailing}\n"));
242 }
243 NodeKind::Container { ctype, body, .. } => {
244 let name = n.name.as_ref().expect("containers are named");
245 let kw = match ctype {
246 ContainerType::Row => "row".to_string(),
247 ContainerType::Column => "column".to_string(),
248 ContainerType::Grid { cols, rows } => format!("grid {cols}x{rows}"),
249 };
250 out.push_str(&format!("{indent}{}: {kw} {{\n", name.name));
251 self.write_block_stmts(out, body, depth + 1);
252 out.push_str(&format!("{indent}}}{trailing}\n"));
253 }
254 NodeKind::Call { callee, args, body } => {
255 let args: Vec<&str> = args.iter().map(|a| self.snip(a.span)).collect();
256 let head = match &n.name {
257 Some(name) => format!("{}: {}({})", name.name, callee.name, args.join(", ")),
258 None => format!("{}({})", callee.name, args.join(", ")),
259 };
260 match body {
261 Some(b) if !b.stmts.is_empty() => {
262 out.push_str(&format!("{indent}{head} {{\n"));
263 self.write_block_stmts(out, b, depth + 1);
264 out.push_str(&format!("{indent}}}{trailing}\n"));
265 }
266 _ => out.push_str(&format!("{indent}{head}{trailing}\n")),
267 }
268 }
269 }
270 }
271
272 fn prop_line(&self, p: &Prop) -> String {
273 let key: Vec<&str> = p.key.iter().map(|k| k.name.as_str()).collect();
274 format!("{}: {}", key.join("."), self.snip(p.value.span()))
275 }
276
277 fn inline_block(&self, block: &Block) -> Option<String> {
279 if block.stmts.is_empty() {
280 return None;
281 }
282 if block.stmts.len() > 2 {
283 return None;
284 }
285 let mut parts = Vec::new();
286 for s in &block.stmts {
287 if !s.trivia.leading.is_empty() || s.trivia.trailing.is_some() {
288 return None;
289 }
290 match &s.kind {
291 StmtKind::Prop(p) if !p.key.is_empty() => parts.push(self.prop_line(p)),
292 _ => return None,
293 }
294 }
295 let line = format!("{{ {} }}", parts.join("; "));
296 (line.len() <= INLINE_MAX - 10).then_some(line)
297 }
298
299 fn inline_node(&self, n: &Node) -> Option<String> {
301 match &n.kind {
302 NodeKind::Plain { body } => {
303 let name = n.name.as_ref()?;
304 if body.stmts.is_empty() {
305 return Some(name.name.clone());
306 }
307 if body.stmts.len() > 2 {
309 return None;
310 }
311 let mut parts = Vec::new();
312 for s in &body.stmts {
313 if !s.trivia.leading.is_empty() || s.trivia.trailing.is_some() {
314 return None;
315 }
316 match &s.kind {
317 StmtKind::Prop(p) if !p.key.is_empty() => parts.push(self.prop_line(p)),
318 StmtKind::Port(p) => {
319 let inner = self.inline_block(&p.body)?;
320 parts.push(format!("port {} {inner}", p.name.name));
321 }
322 _ => return None,
323 }
324 }
325 Some(format!("{} {{ {} }}", name.name, parts.join("; ")))
326 }
327 NodeKind::Container { ctype, body, .. } => {
328 let name = n.name.as_ref()?;
329 if body.stmts.len() != 1 {
330 return None;
331 }
332 let inner_stmt = &body.stmts[0];
333 if !inner_stmt.trivia.leading.is_empty() || inner_stmt.trivia.trailing.is_some() {
334 return None;
335 }
336 let inner = match &inner_stmt.kind {
337 StmtKind::For(f) => self.inline_for(f)?,
338 StmtKind::Node(inner_n) => self.inline_node(inner_n)?,
339 _ => return None,
340 };
341 let kw = match ctype {
342 ContainerType::Row => "row".to_string(),
343 ContainerType::Column => "column".to_string(),
344 ContainerType::Grid { cols, rows } => format!("grid {cols}x{rows}"),
345 };
346 Some(format!("{}: {kw} {{ {inner} }}", name.name))
347 }
348 NodeKind::Call { callee, args, body } => {
349 let args: Vec<&str> = args.iter().map(|a| self.snip(a.span)).collect();
350 let head = match &n.name {
351 Some(name) => format!("{}: {}({})", name.name, callee.name, args.join(", ")),
352 None => format!("{}({})", callee.name, args.join(", ")),
353 };
354 match body {
355 None => Some(head),
356 Some(b) if b.stmts.is_empty() => Some(head),
357 Some(b) => {
358 let inner = self.inline_block(b)?;
359 Some(format!("{head} {inner}"))
360 }
361 }
362 }
363 }
364 }
365
366 fn inline_for(&self, f: &For) -> Option<String> {
367 if f.body.stmts.len() != 1 {
368 return None;
369 }
370 let s = &f.body.stmts[0];
371 if !s.trivia.leading.is_empty() || s.trivia.trailing.is_some() {
372 return None;
373 }
374 let inner = match &s.kind {
375 StmtKind::Node(n) => self.inline_node(n)?,
376 StmtKind::Edge(e) => {
377 let op = match e.op {
378 EdgeOp::Forward => "->",
379 EdgeOp::Bidirectional => "<->",
380 };
381 let label = e
382 .label
383 .as_ref()
384 .map(|l| format!(" : {}", self.snip(l.span)))
385 .unwrap_or_default();
386 let props = match &e.props {
387 Some(b) => format!(" {}", self.inline_block(b)?),
388 None => String::new(),
389 };
390 format!(
391 "{} {op} {}{label}{props}",
392 self.snip(e.from.span),
393 self.snip(e.to.span)
394 )
395 }
396 _ => return None,
397 };
398 Some(format!(
399 "for {} in {}..{} {{ {inner} }}",
400 f.var.name,
401 self.snip(f.start.span),
402 self.snip(f.end.span)
403 ))
404 }
405}