1use crate::config::FormatConfig;
10use crate::doc::Doc;
11use crate::printer::Printer;
12
13use eure_document::document::node::{NodeArray, NodeMap, NodeTuple, NodeValue};
14use eure_document::document::{EureDocument, NodeId};
15use eure_document::identifier::Identifier;
16use eure_document::path::ArrayIndexKind;
17use eure_document::source::{
18 ArrayElementSource, BindSource, BindingSource, Comment, EureSource, SectionBody, SectionSource,
19 SourceDocument, SourceId, SourceKey, SourcePathSegment, StringStyle, Trivia,
20};
21use eure_document::text::{Language, SyntaxHint};
22use eure_document::value::{ObjectKey, PartialObjectKey, PrimitiveValue};
23
24pub fn build_source_doc(source: &SourceDocument) -> Doc {
28 SourceDocBuilder::new(source).build()
29}
30
31pub fn format_source_document(source: &SourceDocument) -> String {
36 let doc = build_source_doc(source);
37 Printer::new(FormatConfig::default()).print(&doc)
38}
39
40struct SourceDocBuilder<'a> {
41 source: &'a SourceDocument,
42}
43
44impl<'a> SourceDocBuilder<'a> {
45 fn new(source: &'a SourceDocument) -> Self {
46 Self { source }
47 }
48
49 fn doc(&self) -> &EureDocument {
50 &self.source.document
51 }
52
53 fn get_source(&self, id: SourceId) -> &EureSource {
54 self.source.source(id)
55 }
56
57 fn build(&self) -> Doc {
58 self.build_eure_source(self.source.root_source())
59 }
60
61 fn build_eure_source(&self, eure: &EureSource) -> Doc {
62 let mut parts = Vec::new();
63
64 if !eure.leading_trivia.is_empty() {
66 parts.push(self.build_trivia(&eure.leading_trivia));
67 }
68
69 if let Some(node_id) = eure.value {
71 parts.push(
72 Doc::text("= ")
73 .concat(self.build_value(node_id))
74 .concat(Doc::hardline()),
75 );
76 }
77
78 for binding in &eure.bindings {
80 parts.push(self.build_binding(binding));
81 }
82
83 for section in &eure.sections {
85 parts.push(self.build_section(section));
86 }
87
88 if !eure.trailing_trivia.is_empty() {
90 parts.push(self.build_trivia(&eure.trailing_trivia));
91 }
92
93 Doc::concat_all(parts)
94 }
95
96 fn build_trivia(&self, trivia: &[Trivia]) -> Doc {
97 let mut parts = Vec::new();
98 for item in trivia {
99 match item {
100 Trivia::Comment(comment) => {
101 parts.push(self.build_comment(comment));
102 }
103 Trivia::BlankLine => {
104 parts.push(Doc::hardline());
105 }
106 }
107 }
108 Doc::concat_all(parts)
109 }
110
111 fn build_comment(&self, comment: &Comment) -> Doc {
112 match comment {
113 Comment::Line(s) => {
114 if s.is_empty() {
115 Doc::text("//").concat(Doc::hardline())
116 } else {
117 Doc::text("// ")
118 .concat(Doc::text(s.clone()))
119 .concat(Doc::hardline())
120 }
121 }
122 Comment::Block(s) => {
123 if s.is_empty() {
124 Doc::text("/**/").concat(Doc::hardline())
125 } else {
126 Doc::text("/* ")
127 .concat(Doc::text(s.clone()))
128 .concat(Doc::text(" */"))
129 .concat(Doc::hardline())
130 }
131 }
132 }
133 }
134
135 fn build_array_comment(&self, comment: &Comment) -> Doc {
137 match comment {
138 Comment::Line(s) => {
139 if s.is_empty() {
140 Doc::text("//")
141 } else {
142 Doc::text("// ").concat(Doc::text(s.clone()))
143 }
144 }
145 Comment::Block(s) => {
146 if s.is_empty() {
147 Doc::text("/**/")
148 } else {
149 Doc::text("/* ")
150 .concat(Doc::text(s.clone()))
151 .concat(Doc::text(" */"))
152 }
153 }
154 }
155 }
156
157 fn build_trailing_comment(&self, comment: &Comment) -> Doc {
159 match comment {
160 Comment::Line(s) => {
161 if s.is_empty() {
162 Doc::text(" //")
163 } else {
164 Doc::text(" // ").concat(Doc::text(s.clone()))
165 }
166 }
167 Comment::Block(s) => {
168 if s.is_empty() {
169 Doc::text(" /**/")
170 } else {
171 Doc::text(" /* ")
172 .concat(Doc::text(s.clone()))
173 .concat(Doc::text(" */"))
174 }
175 }
176 }
177 }
178
179 fn build_binding(&self, binding: &BindingSource) -> Doc {
180 let mut parts = Vec::new();
181
182 if !binding.trivia_before.is_empty() {
184 parts.push(self.build_trivia(&binding.trivia_before));
185 }
186
187 let path_doc = self.build_path(&binding.path);
188
189 let body_doc = match &binding.bind {
190 BindSource::Value(node_id) => Doc::text(" = ").concat(self.build_value(*node_id)),
191 BindSource::Array { node, elements } => {
192 Doc::text(" = ").concat(self.build_array_with_trivia(*node, elements))
193 }
194 BindSource::Block(source_id) => {
195 let inner = self.build_eure_source(self.get_source(*source_id));
196 self.build_braced_block(Doc::text(""), inner)
197 }
198 };
199
200 let mut doc = path_doc.concat(body_doc);
201
202 if let Some(comment) = &binding.trailing_comment {
203 doc = doc.concat(self.build_trailing_comment(comment));
204 }
205
206 parts.push(doc.concat(Doc::hardline()));
207 Doc::concat_all(parts)
208 }
209
210 fn build_section(&self, section: &SectionSource) -> Doc {
211 let mut parts = Vec::new();
212
213 if !section.trivia_before.is_empty() {
215 parts.push(self.build_trivia(§ion.trivia_before));
216 }
217
218 let mut header = Doc::text("@ ").concat(self.build_path(§ion.path));
219
220 if let Some(comment) = §ion.trailing_comment {
221 header = header.concat(self.build_trailing_comment(comment));
222 }
223
224 let section_doc = match §ion.body {
225 SectionBody::Items { value, bindings } => {
226 if let Some(node_id) = value
227 && bindings.is_empty()
228 {
229 parts.push(
230 header
231 .concat(Doc::text(" = "))
232 .concat(self.build_value(*node_id))
233 .concat(Doc::hardline()),
234 );
235 return Doc::concat_all(parts);
236 }
237
238 let mut body_parts = Vec::new();
239
240 if let Some(node_id) = value {
242 body_parts.push(
243 Doc::text("= ")
244 .concat(self.build_value(*node_id))
245 .concat(Doc::hardline()),
246 );
247 }
248
249 for binding in bindings {
251 body_parts.push(self.build_binding(binding));
252 }
253
254 header
255 .concat(Doc::hardline())
256 .concat(Doc::concat_all(body_parts))
257 }
258 SectionBody::Block(source_id) => {
259 let inner = self.build_eure_source(self.get_source(*source_id));
260 self.build_braced_block(header, inner)
261 .concat(Doc::hardline())
262 }
263 };
264
265 parts.push(section_doc);
266 Doc::concat_all(parts)
267 }
268
269 fn build_braced_block(&self, header: Doc, inner: Doc) -> Doc {
270 let (inner, removed) = strip_one_trailing_hardline(inner);
271 if !removed || matches!(inner, Doc::Nil) {
272 return header.concat(Doc::text(" {}"));
273 }
274
275 header
276 .concat(Doc::text(" {"))
277 .concat(Doc::indent(Doc::hardline().concat(inner)))
278 .concat(Doc::hardline())
279 .concat(Doc::text("}"))
280 }
281
282 fn build_path(&self, path: &[SourcePathSegment]) -> Doc {
283 let mut result = Doc::Nil;
284 for (i, segment) in path.iter().enumerate() {
285 if i > 0 {
286 result = result.concat(Doc::text("."));
287 }
288 result = result.concat(self.build_key(&segment.key));
289 if let Some(index) = &segment.array {
290 result = result.concat(Doc::text("["));
291 match index {
292 ArrayIndexKind::Push => {}
293 ArrayIndexKind::Current => {
294 result = result.concat(Doc::text("^"));
295 }
296 ArrayIndexKind::Specific(n) => {
297 result = result.concat(Doc::text(n.to_string()));
298 }
299 }
300 result = result.concat(Doc::text("]"));
301 }
302 }
303 result
304 }
305
306 fn build_key(&self, key: &SourceKey) -> Doc {
307 match key {
308 SourceKey::Ident(s) => Doc::text(s.as_ref()),
309 SourceKey::Extension(s) => Doc::text("$").concat(Doc::text(s.as_ref())),
310 SourceKey::Hole(None) => Doc::text("!"),
311 SourceKey::Hole(Some(label)) => Doc::text("!").concat(Doc::text(label.as_ref())),
312 SourceKey::String(s, style) => match style {
313 StringStyle::Quoted => Doc::text("\"")
314 .concat(Doc::text(escape_string(s)))
315 .concat(Doc::text("\"")),
316 StringStyle::Literal => Doc::text("'")
317 .concat(Doc::text(s.clone()))
318 .concat(Doc::text("'")),
319 StringStyle::DelimitedLitStr(level) => {
320 let delim_open: String = "<".repeat(*level as usize);
321 let delim_close: String = ">".repeat(*level as usize);
322 Doc::text(format!("{delim_open}'{s}'{delim_close}"))
323 }
324 StringStyle::DelimitedCode(level) => {
325 let delim_open: String = "<".repeat(*level as usize);
326 let delim_close: String = ">".repeat(*level as usize);
327 Doc::text(format!("{delim_open}`{s}`{delim_close}"))
328 }
329 },
330 SourceKey::Integer(n) => Doc::text(n.to_string()),
331 SourceKey::Tuple(keys) => {
332 let inner = Doc::join(keys.iter().map(|k| self.build_key(k)), Doc::text(", "));
333 Doc::text("(").concat(inner).concat(Doc::text(")"))
334 }
335 SourceKey::TupleIndex(n) => Doc::text("#").concat(Doc::text(n.to_string())),
336 }
337 }
338
339 fn build_value(&self, node_id: NodeId) -> Doc {
340 let node = self.doc().node(node_id);
341 match &node.content {
342 NodeValue::Hole(_) => Doc::text("null"),
343 NodeValue::Primitive(prim) => self.build_primitive(prim),
344 NodeValue::Array(arr) => self.build_array(node_id, arr),
345 NodeValue::Tuple(tuple) => self.build_tuple(tuple),
346 NodeValue::Map(map) => self.build_map(map),
347 NodeValue::PartialMap(map) => self.build_partial_map(map),
348 }
349 }
350
351 fn build_primitive(&self, prim: &PrimitiveValue) -> Doc {
352 match prim {
353 PrimitiveValue::Null => Doc::text("null"),
354 PrimitiveValue::Bool(b) => Doc::text(if *b { "true" } else { "false" }),
355 PrimitiveValue::Integer(n) => Doc::text(n.to_string()),
356 PrimitiveValue::F64(f) => self.build_f64(*f),
357 PrimitiveValue::F32(f) => self.build_f32(*f),
358 PrimitiveValue::Text(text) => self.build_text(text),
359 }
360 }
361
362 fn build_f64(&self, f: f64) -> Doc {
363 if f.is_nan() {
364 Doc::text("nan")
365 } else if f.is_infinite() {
366 if f.is_sign_positive() {
367 Doc::text("inf")
368 } else {
369 Doc::text("-inf")
370 }
371 } else {
372 let s = f.to_string();
373 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
374 Doc::text(s).concat(Doc::text(".0"))
375 } else {
376 Doc::text(s)
377 }
378 }
379 }
380
381 fn build_f32(&self, f: f32) -> Doc {
382 if f.is_nan() {
383 Doc::text("nan")
384 } else if f.is_infinite() {
385 if f.is_sign_positive() {
386 Doc::text("inf")
387 } else {
388 Doc::text("-inf")
389 }
390 } else {
391 Doc::text(f.to_string())
392 }
393 }
394
395 fn build_text(&self, text: &eure_document::text::Text) -> Doc {
396 let is_block = matches!(
397 text.syntax_hint,
398 Some(SyntaxHint::Block)
399 | Some(SyntaxHint::Block3)
400 | Some(SyntaxHint::Block4)
401 | Some(SyntaxHint::Block5)
402 | Some(SyntaxHint::Block6)
403 );
404
405 if is_block {
406 self.build_block_text(text)
407 } else {
408 self.build_inline_text(text)
409 }
410 }
411
412 fn build_block_text(&self, text: &eure_document::text::Text) -> Doc {
413 let backticks = match text.syntax_hint {
414 Some(SyntaxHint::Block6) => "``````",
415 Some(SyntaxHint::Block5) => "`````",
416 Some(SyntaxHint::Block4) => "````",
417 _ => "```",
418 };
419
420 let mut doc = Doc::text(backticks);
421
422 if let Language::Other(lang) = &text.language {
423 doc = doc.concat(Doc::text(lang.clone()));
424 }
425
426 doc = doc.concat(Doc::text("\n"));
429 doc = doc.concat(Doc::text(text.content.clone()));
430 if !text.content.ends_with('\n') {
431 doc = doc.concat(Doc::text("\n"));
432 }
433 doc.concat(Doc::text(backticks))
434 }
435
436 fn build_inline_text(&self, text: &eure_document::text::Text) -> Doc {
437 match &text.language {
438 Language::Plaintext => Doc::text("\"")
439 .concat(Doc::text(escape_string(&text.content)))
440 .concat(Doc::text("\"")),
441 Language::Implicit => Doc::text("`")
442 .concat(Doc::text(text.content.clone()))
443 .concat(Doc::text("`")),
444 Language::Other(lang) => Doc::text(lang.clone())
445 .concat(Doc::text("`"))
446 .concat(Doc::text(text.content.clone()))
447 .concat(Doc::text("`")),
448 }
449 }
450
451 fn build_array(&self, node_id: NodeId, arr: &NodeArray) -> Doc {
452 if arr.is_empty() {
453 return Doc::text("[]");
454 }
455
456 let force_multiline = self.source.is_multiline_array(node_id);
458
459 if force_multiline {
460 let elements = Doc::join(
462 arr.iter()
463 .map(|&id| self.build_value(id).concat(Doc::text(","))),
464 Doc::hardline(),
465 );
466
467 Doc::text("[")
468 .concat(Doc::indent(Doc::hardline().concat(elements)))
469 .concat(Doc::hardline())
470 .concat(Doc::text("]"))
471 } else {
472 let elements = Doc::join(
474 arr.iter().map(|&id| self.build_value(id)),
475 Doc::text(",").concat(Doc::line()),
476 );
477
478 Doc::group(
479 Doc::text("[")
480 .concat(Doc::softline())
481 .concat(Doc::indent(elements))
482 .concat(Doc::softline())
483 .concat(Doc::text("]")),
484 )
485 }
486 }
487
488 fn build_array_with_trivia(&self, node_id: NodeId, elements: &[ArrayElementSource]) -> Doc {
490 let arr = match &self.doc().node(node_id).content {
491 NodeValue::Array(arr) => arr,
492 _ => return self.build_value(node_id), };
494
495 if elements.is_empty() {
496 return self.build_array(node_id, arr);
498 }
499
500 let mut inner_parts = Vec::new();
502
503 for (i, elem_source) in elements.iter().enumerate() {
504 for trivia in &elem_source.trivia_before {
506 match trivia {
507 Trivia::Comment(comment) => {
508 inner_parts.push(self.build_array_comment(comment));
510 inner_parts.push(Doc::hardline());
511 }
512 Trivia::BlankLine => {
513 inner_parts.push(Doc::hardline());
514 }
515 }
516 }
517
518 let value_id = arr.get(elem_source.index).unwrap();
520 let mut elem_doc = self.build_value(value_id);
521
522 elem_doc = elem_doc.concat(Doc::text(","));
524
525 if let Some(comment) = &elem_source.trailing_comment {
527 elem_doc = elem_doc.concat(self.build_trailing_comment(comment));
528 }
529
530 inner_parts.push(elem_doc);
531 if i < elements.len() - 1 {
533 inner_parts.push(Doc::hardline());
534 }
535 }
536
537 Doc::text("[")
541 .concat(Doc::indent(
542 Doc::hardline().concat(Doc::concat_all(inner_parts)),
543 ))
544 .concat(Doc::hardline())
545 .concat(Doc::text("]"))
546 }
547
548 fn build_tuple(&self, tuple: &NodeTuple) -> Doc {
549 if tuple.is_empty() {
550 return Doc::text("()");
551 }
552
553 let elements = Doc::join(
554 tuple.iter().map(|&id| self.build_value(id)),
555 Doc::text(", "),
556 );
557
558 Doc::text("(").concat(elements).concat(Doc::text(")"))
559 }
560
561 fn build_map(&self, map: &NodeMap) -> Doc {
562 if map.is_empty() {
563 return Doc::text("{}");
564 }
565
566 let entries = Doc::join(
567 map.iter().map(|(key, &child_id)| {
568 self.build_object_key(key)
569 .concat(Doc::text(" => "))
570 .concat(self.build_value(child_id))
571 }),
572 Doc::text(", "),
573 );
574
575 Doc::text("{ ").concat(entries).concat(Doc::text(" }"))
576 }
577
578 fn build_partial_map(&self, map: &eure_document::map::PartialNodeMap) -> Doc {
579 if map.is_empty() {
580 return Doc::text("{}");
581 }
582
583 let entries = Doc::join(
584 map.iter().map(|(key, &child_id)| {
585 self.build_partial_object_key(key)
586 .concat(Doc::text(" => "))
587 .concat(self.build_value(child_id))
588 }),
589 Doc::text(", "),
590 );
591
592 Doc::text("{ ").concat(entries).concat(Doc::text(" }"))
593 }
594
595 fn build_object_key(&self, key: &ObjectKey) -> Doc {
596 match key {
597 ObjectKey::String(s) => {
598 if s.parse::<Identifier>().is_ok() {
601 Doc::text(s.clone())
603 } else {
604 Doc::text("\"")
606 .concat(Doc::text(escape_string(s)))
607 .concat(Doc::text("\""))
608 }
609 }
610 ObjectKey::Number(n) => Doc::text(n.to_string()),
611 ObjectKey::Tuple(keys) => {
612 let inner = Doc::join(
613 keys.iter().map(|k| self.build_object_key(k)),
614 Doc::text(", "),
615 );
616 Doc::text("(").concat(inner).concat(Doc::text(")"))
617 }
618 }
619 }
620
621 fn build_partial_object_key(&self, key: &PartialObjectKey) -> Doc {
622 match key {
623 PartialObjectKey::String(s) => {
624 if s.parse::<Identifier>().is_ok() {
625 Doc::text(s.clone())
626 } else {
627 Doc::text("\"")
628 .concat(Doc::text(escape_string(s)))
629 .concat(Doc::text("\""))
630 }
631 }
632 PartialObjectKey::Number(n) => Doc::text(n.to_string()),
633 PartialObjectKey::Hole(None) => Doc::text("!"),
634 PartialObjectKey::Hole(Some(label)) => Doc::text("!").concat(Doc::text(label.as_ref())),
635 PartialObjectKey::Tuple(keys) => {
636 let inner = Doc::join(
637 keys.iter().map(|k| self.build_partial_object_key(k)),
638 Doc::text(", "),
639 );
640 Doc::text("(").concat(inner).concat(Doc::text(")"))
641 }
642 }
643 }
644}
645
646fn strip_one_trailing_hardline(doc: Doc) -> (Doc, bool) {
647 match doc {
648 Doc::HardLine => (Doc::Nil, true),
649 Doc::Concat(left, right) => {
650 let left = *left;
651 let right = *right;
652
653 let (new_right, removed) = strip_one_trailing_hardline(right.clone());
654 if removed {
655 return (left.concat(new_right), true);
656 }
657
658 let (new_left, removed) = strip_one_trailing_hardline(left);
659 (new_left.concat(right), removed)
660 }
661 other => (other, false),
662 }
663}
664
665fn escape_string(s: &str) -> String {
667 let mut result = String::with_capacity(s.len());
668 for c in s.chars() {
669 match c {
670 '"' => result.push_str("\\\""),
671 '\\' => result.push_str("\\\\"),
672 '\n' => result.push_str("\\n"),
673 '\r' => result.push_str("\\r"),
674 '\t' => result.push_str("\\t"),
675 _ => result.push(c),
676 }
677 }
678 result
679}