1use std::collections::HashSet;
35
36use crate::document::{EureDocument, NodeId};
37use crate::prelude_internal::*;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub struct SourceId(pub usize);
46
47#[derive(Debug, Clone, Default)]
51pub struct EureSource {
52 pub leading_trivia: Vec<Trivia>,
54 pub value: Option<NodeId>,
56 pub bindings: Vec<BindingSource>,
58 pub sections: Vec<SectionSource>,
60 pub trailing_trivia: Vec<Trivia>,
62}
63
64#[derive(Debug, Clone)]
68pub struct BindingSource {
69 pub trivia_before: Vec<Trivia>,
71 pub path: SourcePath,
73 pub bind: BindSource,
75 pub trailing_comment: Option<Comment>,
77}
78
79#[derive(Debug, Clone)]
83pub enum BindSource {
84 Value(NodeId),
86 Array {
90 node: NodeId,
92 elements: Vec<ArrayElementSource>,
94 },
95 Block(SourceId),
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct ArrayElementSource {
105 pub trivia_before: Vec<Trivia>,
107 pub index: usize,
109 pub trailing_comment: Option<Comment>,
111}
112
113#[derive(Debug, Clone)]
117pub struct SectionSource {
118 pub trivia_before: Vec<Trivia>,
120 pub path: SourcePath,
122 pub body: SectionBody,
124 pub trailing_comment: Option<Comment>,
126}
127
128#[derive(Debug, Clone)]
132pub enum SectionBody {
133 Items {
135 value: Option<NodeId>,
137 bindings: Vec<BindingSource>,
139 },
140 Block(SourceId),
142}
143
144#[derive(Debug, Clone)]
153pub struct SourceDocument {
154 pub document: EureDocument,
156 pub sources: Vec<EureSource>,
158 pub root: SourceId,
160 pub multiline_arrays: HashSet<NodeId>,
162}
163
164impl SourceDocument {
165 #[must_use]
167 pub fn new(document: EureDocument, sources: Vec<EureSource>) -> Self {
168 Self {
169 document,
170 sources,
171 root: SourceId(0),
172 multiline_arrays: HashSet::new(),
173 }
174 }
175
176 pub fn empty() -> Self {
178 Self {
179 document: EureDocument::new_empty(),
180 sources: vec![EureSource::default()],
181 root: SourceId(0),
182 multiline_arrays: HashSet::new(),
183 }
184 }
185
186 pub fn mark_multiline_array(&mut self, node_id: NodeId) {
188 self.multiline_arrays.insert(node_id);
189 }
190
191 pub fn is_multiline_array(&self, node_id: NodeId) -> bool {
193 self.multiline_arrays.contains(&node_id)
194 }
195
196 pub fn document(&self) -> &EureDocument {
198 &self.document
199 }
200
201 pub fn document_mut(&mut self) -> &mut EureDocument {
203 &mut self.document
204 }
205
206 pub fn root_source(&self) -> &EureSource {
208 &self.sources[self.root.0]
209 }
210
211 pub fn source(&self, id: SourceId) -> &EureSource {
213 &self.sources[id.0]
214 }
215
216 pub fn source_mut(&mut self, id: SourceId) -> &mut EureSource {
218 &mut self.sources[id.0]
219 }
220}
221
222pub type SourcePath = Vec<SourcePathSegment>;
228
229#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct SourcePathSegment {
232 pub key: SourceKey,
234 pub array: Option<Option<usize>>,
239}
240
241impl SourcePathSegment {
242 pub fn ident(name: Identifier) -> Self {
244 Self {
245 key: SourceKey::Ident(name),
246 array: None,
247 }
248 }
249
250 pub fn extension(name: Identifier) -> Self {
252 Self {
253 key: SourceKey::Extension(name),
254 array: None,
255 }
256 }
257
258 pub fn with_array_push(mut self) -> Self {
260 self.array = Some(None);
261 self
262 }
263
264 pub fn with_array_index(mut self, index: usize) -> Self {
266 self.array = Some(Some(index));
267 self
268 }
269
270 pub fn quoted_string(s: impl Into<String>) -> Self {
272 Self {
273 key: SourceKey::quoted(s),
274 array: None,
275 }
276 }
277
278 pub fn literal_string(s: impl Into<String>) -> Self {
280 Self {
281 key: SourceKey::literal(s),
282 array: None,
283 }
284 }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
292pub enum StringStyle {
293 #[default]
295 Quoted,
296 Literal,
299 DelimitedLitStr(u8),
303 DelimitedCode(u8),
306}
307
308#[derive(Debug, Clone)]
312pub enum SourceKey {
313 Ident(Identifier),
315
316 Extension(Identifier),
318
319 Hole(Option<Identifier>),
321
322 String(String, StringStyle),
328
329 Integer(i64),
331
332 Tuple(Vec<SourceKey>),
334
335 TupleIndex(u8),
337}
338
339impl PartialEq for SourceKey {
340 fn eq(&self, other: &Self) -> bool {
341 match (self, other) {
342 (Self::Ident(a), Self::Ident(b)) => a == b,
343 (Self::Extension(a), Self::Extension(b)) => a == b,
344 (Self::Hole(a), Self::Hole(b)) => a == b,
345 (Self::String(a, _), Self::String(b, _)) => a == b,
347 (Self::Integer(a), Self::Integer(b)) => a == b,
348 (Self::Tuple(a), Self::Tuple(b)) => a == b,
349 (Self::TupleIndex(a), Self::TupleIndex(b)) => a == b,
350 _ => false,
351 }
352 }
353}
354
355impl Eq for SourceKey {}
356
357impl SourceKey {
358 pub fn hole(label: Option<Identifier>) -> Self {
360 SourceKey::Hole(label)
361 }
362
363 pub fn quoted(s: impl Into<String>) -> Self {
365 SourceKey::String(s.into(), StringStyle::Quoted)
366 }
367
368 pub fn literal(s: impl Into<String>) -> Self {
370 SourceKey::String(s.into(), StringStyle::Literal)
371 }
372
373 pub fn delimited_lit_str(s: impl Into<String>, level: u8) -> Self {
375 SourceKey::String(s.into(), StringStyle::DelimitedLitStr(level))
376 }
377
378 pub fn delimited_code(s: impl Into<String>, level: u8) -> Self {
380 SourceKey::String(s.into(), StringStyle::DelimitedCode(level))
381 }
382}
383
384impl From<Identifier> for SourceKey {
385 fn from(id: Identifier) -> Self {
386 SourceKey::Ident(id)
387 }
388}
389
390impl From<i64> for SourceKey {
391 fn from(n: i64) -> Self {
392 SourceKey::Integer(n)
393 }
394}
395
396#[derive(Debug, Clone, PartialEq, Eq)]
402pub enum Comment {
403 Line(String),
405 Block(String),
407}
408
409impl Comment {
410 pub fn line(s: impl Into<String>) -> Self {
412 Comment::Line(s.into())
413 }
414
415 pub fn block(s: impl Into<String>) -> Self {
417 Comment::Block(s.into())
418 }
419
420 pub fn text(&self) -> &str {
422 match self {
423 Comment::Line(s) | Comment::Block(s) => s,
424 }
425 }
426}
427
428#[derive(Debug, Clone, PartialEq, Eq)]
432pub enum Trivia {
433 Comment(Comment),
435 BlankLine,
437}
438
439impl Trivia {
440 pub fn line_comment(s: impl Into<String>) -> Self {
442 Trivia::Comment(Comment::Line(s.into()))
443 }
444
445 pub fn block_comment(s: impl Into<String>) -> Self {
447 Trivia::Comment(Comment::Block(s.into()))
448 }
449
450 pub fn blank_line() -> Self {
452 Trivia::BlankLine
453 }
454}
455
456impl From<Comment> for Trivia {
457 fn from(comment: Comment) -> Self {
458 Trivia::Comment(comment)
459 }
460}
461
462impl EureSource {
467 pub fn new() -> Self {
469 Self::default()
470 }
471
472 pub fn push_binding(&mut self, binding: BindingSource) {
474 self.bindings.push(binding);
475 }
476
477 pub fn push_section(&mut self, section: SectionSource) {
479 self.sections.push(section);
480 }
481}
482
483impl BindingSource {
484 pub fn value(path: SourcePath, node: NodeId) -> Self {
486 Self {
487 trivia_before: Vec::new(),
488 path,
489 bind: BindSource::Value(node),
490 trailing_comment: None,
491 }
492 }
493
494 pub fn block(path: SourcePath, source_id: SourceId) -> Self {
496 Self {
497 trivia_before: Vec::new(),
498 path,
499 bind: BindSource::Block(source_id),
500 trailing_comment: None,
501 }
502 }
503
504 pub fn with_trailing_comment(mut self, comment: Comment) -> Self {
506 self.trailing_comment = Some(comment);
507 self
508 }
509
510 pub fn with_trivia(mut self, trivia: Vec<Trivia>) -> Self {
512 self.trivia_before = trivia;
513 self
514 }
515
516 pub fn array(path: SourcePath, node: NodeId, elements: Vec<ArrayElementSource>) -> Self {
518 Self {
519 trivia_before: Vec::new(),
520 path,
521 bind: BindSource::Array { node, elements },
522 trailing_comment: None,
523 }
524 }
525}
526
527impl SectionSource {
528 pub fn items(path: SourcePath, value: Option<NodeId>, bindings: Vec<BindingSource>) -> Self {
530 Self {
531 trivia_before: Vec::new(),
532 path,
533 body: SectionBody::Items { value, bindings },
534 trailing_comment: None,
535 }
536 }
537
538 pub fn block(path: SourcePath, source_id: SourceId) -> Self {
540 Self {
541 trivia_before: Vec::new(),
542 path,
543 body: SectionBody::Block(source_id),
544 trailing_comment: None,
545 }
546 }
547
548 pub fn with_trailing_comment(mut self, comment: Comment) -> Self {
550 self.trailing_comment = Some(comment);
551 self
552 }
553
554 pub fn with_trivia(mut self, trivia: Vec<Trivia>) -> Self {
556 self.trivia_before = trivia;
557 self
558 }
559}
560
561impl ArrayElementSource {
562 pub fn new(index: usize) -> Self {
564 Self {
565 trivia_before: Vec::new(),
566 index,
567 trailing_comment: None,
568 }
569 }
570
571 pub fn with_trivia(mut self, trivia: Vec<Trivia>) -> Self {
573 self.trivia_before = trivia;
574 self
575 }
576
577 pub fn with_trailing_comment(mut self, comment: Comment) -> Self {
579 self.trailing_comment = Some(comment);
580 self
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn test_source_path_segment_ident() {
590 let actual = SourcePathSegment::ident(Identifier::new_unchecked("foo"));
591 let expected = SourcePathSegment {
592 key: SourceKey::Ident(Identifier::new_unchecked("foo")),
593 array: None,
594 };
595 assert_eq!(actual, expected);
596 }
597
598 #[test]
599 fn test_source_path_segment_with_array_push() {
600 let actual = SourcePathSegment::ident(Identifier::new_unchecked("items")).with_array_push();
601 let expected = SourcePathSegment {
602 key: SourceKey::Ident(Identifier::new_unchecked("items")),
603 array: Some(None),
604 };
605 assert_eq!(actual, expected);
606 }
607
608 #[test]
609 fn test_source_path_segment_with_array_index() {
610 let actual =
611 SourcePathSegment::ident(Identifier::new_unchecked("items")).with_array_index(0);
612 let expected = SourcePathSegment {
613 key: SourceKey::Ident(Identifier::new_unchecked("items")),
614 array: Some(Some(0)),
615 };
616 assert_eq!(actual, expected);
617 }
618
619 #[test]
620 fn test_binding_source_value() {
621 let path = vec![SourcePathSegment::ident(Identifier::new_unchecked("foo"))];
622 let binding = BindingSource::value(path.clone(), NodeId(1));
623 assert_eq!(binding.path, path);
624 assert!(matches!(binding.bind, BindSource::Value(NodeId(1))));
625 assert!(binding.trivia_before.is_empty());
626 }
627
628 #[test]
629 fn test_binding_source_block() {
630 let path = vec![SourcePathSegment::ident(Identifier::new_unchecked("user"))];
631 let binding = BindingSource::block(path.clone(), SourceId(1));
632 assert_eq!(binding.path, path);
633 assert!(matches!(binding.bind, BindSource::Block(SourceId(1))));
634 assert!(binding.trivia_before.is_empty());
635 }
636
637 #[test]
638 fn test_binding_with_trivia() {
639 let path = vec![SourcePathSegment::ident(Identifier::new_unchecked("foo"))];
640 let trivia = vec![Trivia::BlankLine, Trivia::line_comment("comment")];
641 let binding = BindingSource::value(path.clone(), NodeId(1)).with_trivia(trivia.clone());
642 assert_eq!(binding.trivia_before, trivia);
643 }
644
645 #[test]
646 fn test_section_source_items() {
647 let path = vec![SourcePathSegment::ident(Identifier::new_unchecked(
648 "server",
649 ))];
650 let section = SectionSource::items(path.clone(), None, vec![]);
651 assert_eq!(section.path, path);
652 assert!(matches!(
653 section.body,
654 SectionBody::Items {
655 value: None,
656 bindings
657 } if bindings.is_empty()
658 ));
659 assert!(section.trivia_before.is_empty());
660 }
661
662 #[test]
663 fn test_section_source_block() {
664 let path = vec![SourcePathSegment::ident(Identifier::new_unchecked(
665 "config",
666 ))];
667 let section = SectionSource::block(path.clone(), SourceId(2));
668 assert_eq!(section.path, path);
669 assert!(matches!(section.body, SectionBody::Block(SourceId(2))));
670 assert!(section.trivia_before.is_empty());
671 }
672
673 #[test]
674 fn test_section_with_trivia() {
675 let path = vec![SourcePathSegment::ident(Identifier::new_unchecked(
676 "server",
677 ))];
678 let trivia = vec![Trivia::BlankLine];
679 let section = SectionSource::items(path.clone(), None, vec![]).with_trivia(trivia.clone());
680 assert_eq!(section.trivia_before, trivia);
681 }
682
683 #[test]
684 fn test_source_document_empty() {
685 let doc = SourceDocument::empty();
686 assert_eq!(doc.sources.len(), 1);
687 assert_eq!(doc.root, SourceId(0));
688 assert!(doc.root_source().bindings.is_empty());
689 assert!(doc.root_source().sections.is_empty());
690 }
691}