1use alloc::string::{String, ToString};
7use alloc::vec::Vec;
8
9use crate::document::node::NodeValue;
10use crate::document::{EureDocument, NodeId};
11use crate::identifier::Identifier;
12use crate::parse::union::VARIANT;
13use crate::parse::variant_path::VariantPath;
14use crate::path::PathSegment;
15use crate::source::{
16 BindSource, BindingSource, EureSource, SectionBody, SectionSource, SourceDocument, SourceId,
17 SourceKey, SourcePath, SourcePathSegment,
18};
19use crate::value::{ObjectKey, PartialObjectKey};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub enum LayoutStyle {
24 #[default]
26 Auto,
27 Passthrough,
29 Section,
31 Nested,
33 Binding,
35 SectionBinding,
37 SectionRootBinding,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Default)]
43pub struct DocLayout {
44 pub style_rules: Vec<LayoutStyleRule>,
46 pub order_rules: Vec<LayoutOrderRule>,
48 pub fallback_style: LayoutStyle,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct LayoutStyleRule {
55 pub path: Vec<PathSegment>,
56 pub style: LayoutStyle,
57 pub variant_constraints: Vec<VariantConstraint>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct LayoutOrderRule {
63 pub parent_path: Vec<PathSegment>,
64 pub child_order: Vec<PathSegment>,
65 pub append_unlisted: bool,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct VariantConstraint {
71 pub union_path: Vec<PathSegment>,
73 pub variant: String,
75 pub requires_explicit_tag: bool,
77}
78
79impl DocLayout {
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn add_style_rule(&mut self, path: Vec<PathSegment>, style: LayoutStyle) {
85 self.style_rules.push(LayoutStyleRule {
86 path,
87 style,
88 variant_constraints: Vec::new(),
89 });
90 }
91
92 pub fn add_style_rule_with_constraints(
93 &mut self,
94 path: Vec<PathSegment>,
95 style: LayoutStyle,
96 variant_constraints: Vec<VariantConstraint>,
97 ) {
98 self.style_rules.push(LayoutStyleRule {
99 path,
100 style,
101 variant_constraints,
102 });
103 }
104
105 pub fn add_order_rule(
106 &mut self,
107 parent_path: Vec<PathSegment>,
108 child_order: Vec<PathSegment>,
109 append_unlisted: bool,
110 ) {
111 self.order_rules.push(LayoutOrderRule {
112 parent_path,
113 child_order,
114 append_unlisted,
115 });
116 }
117
118 fn order_rule_for_parent(&self, path: &[PathSegment]) -> Option<&LayoutOrderRule> {
119 self.order_rules
120 .iter()
121 .rev()
122 .find(|rule| rule.parent_path == path)
123 }
124
125 fn style_for_path(&self, doc: &EureDocument, path: &[PathSegment]) -> LayoutStyle {
126 let mut exact_style: Option<LayoutStyle> = None;
127 let mut exact_conflict = false;
128
129 let mut potential_style: Option<LayoutStyle> = None;
130 let mut potential_conflict = false;
131
132 for rule in self.style_rules.iter().filter(|rule| rule.path == path) {
133 match rule_match_status(doc, rule) {
134 RuleMatchStatus::NoMatch => {}
135 RuleMatchStatus::Exact(style) => {
136 if let Some(existing) = exact_style {
137 if existing != style {
138 exact_conflict = true;
139 }
140 } else {
141 exact_style = Some(style);
142 }
143 }
144 RuleMatchStatus::Potential(style) => {
145 if let Some(existing) = potential_style {
146 if existing != style {
147 potential_conflict = true;
148 }
149 } else {
150 potential_style = Some(style);
151 }
152 }
153 }
154 }
155
156 if exact_conflict {
157 return LayoutStyle::Auto;
158 }
159 if let Some(style) = exact_style {
160 return style;
161 }
162
163 if potential_conflict {
164 return LayoutStyle::Auto;
165 }
166 if let Some(style) = potential_style {
167 return style;
168 }
169
170 self.fallback_style
171 }
172}
173
174pub fn project_with_layout(doc: &EureDocument, layout: &DocLayout) -> SourceDocument {
176 let projected = LayoutBuilder::new(doc, layout).build();
177 projected.into_source_document(doc.clone())
178}
179
180#[derive(Debug, Clone)]
181struct ProjectedDoc {
182 root: ProjectedBlock,
183}
184
185#[derive(Debug, Clone)]
186struct ProjectedBlock {
187 value: Option<NodeId>,
188 bindings: Vec<ProjectedBinding>,
189 sections: Vec<ProjectedSection>,
190}
191
192#[derive(Debug, Clone)]
193struct ProjectedBinding {
194 path: Vec<PathSegment>,
195 body: ProjectedBindingBody,
196}
197
198#[derive(Debug, Clone)]
199enum ProjectedBindingBody {
200 Value(NodeId),
201 Block(ProjectedBlock),
202}
203
204#[derive(Debug, Clone)]
205struct ProjectedSection {
206 path: Vec<PathSegment>,
207 body: ProjectedSectionBody,
208}
209
210#[derive(Debug, Clone)]
211enum ProjectedSectionBody {
212 Items {
213 value: Option<NodeId>,
214 bindings: Vec<ProjectedBinding>,
215 },
216 Block(ProjectedBlock),
217}
218
219impl ProjectedDoc {
220 fn into_source_document(self, doc: EureDocument) -> SourceDocument {
221 let mut sources = Vec::new();
222 let root_id = build_source_block(&self.root, &mut sources);
223 debug_assert_eq!(root_id.0, 0, "root source must be index 0");
224 SourceDocument::new(doc, sources)
225 }
226}
227
228struct LayoutBuilder<'a> {
229 doc: &'a EureDocument,
230 layout: &'a DocLayout,
231}
232
233struct EntryContext<'a> {
234 bindings: &'a mut Vec<ProjectedBinding>,
235 sections: &'a mut Vec<ProjectedSection>,
236 node_path: &'a [PathSegment],
237 path_prefix: &'a [PathSegment],
238 allow_sections: bool,
239}
240
241#[derive(Debug, Clone)]
242struct ChildEntry {
243 segment: PathSegment,
244 node_id: NodeId,
245}
246
247impl<'a> LayoutBuilder<'a> {
248 fn new(doc: &'a EureDocument, layout: &'a DocLayout) -> Self {
249 Self { doc, layout }
250 }
251
252 fn build(&self) -> ProjectedDoc {
253 let root_id = self.doc.get_root_id();
254 let root_node = self.doc.node(root_id);
255 let root_is_map = is_map_like(&root_node.content);
256 let value = if root_is_map { None } else { Some(root_id) };
257
258 let (bindings, sections) = self.build_entries(root_id, &[], &[], root_is_map, true);
259
260 ProjectedDoc {
261 root: ProjectedBlock {
262 value,
263 bindings,
264 sections,
265 },
266 }
267 }
268
269 fn build_block(&self, node_id: NodeId, node_path: &[PathSegment]) -> ProjectedBlock {
270 let node_is_map = is_map_like(&self.doc.node(node_id).content);
271 let (bindings, sections) = self.build_entries(node_id, node_path, &[], node_is_map, true);
272 ProjectedBlock {
273 value: None,
274 bindings,
275 sections,
276 }
277 }
278
279 fn build_entries(
280 &self,
281 node_id: NodeId,
282 node_path: &[PathSegment],
283 path_prefix: &[PathSegment],
284 emit_map_fields: bool,
285 allow_sections: bool,
286 ) -> (Vec<ProjectedBinding>, Vec<ProjectedSection>) {
287 let mut bindings = Vec::new();
288 let mut sections = Vec::new();
289 let mut ctx = EntryContext {
290 bindings: &mut bindings,
291 sections: &mut sections,
292 node_path,
293 path_prefix,
294 allow_sections,
295 };
296
297 let node = self.doc.node(node_id);
298 let mut children = Vec::new();
299
300 for (ident, &child_id) in node.extensions.iter() {
301 children.push(ChildEntry {
302 segment: PathSegment::Extension(ident.clone()),
303 node_id: child_id,
304 });
305 }
306
307 if emit_map_fields {
308 children.extend(map_like_children(node));
309 }
310
311 let children = self.order_children(node_path, children);
312 for child in children {
313 self.append_child_entries(&mut ctx, child.segment, child.node_id);
314 }
315
316 (bindings, sections)
317 }
318
319 fn order_children(
320 &self,
321 parent_path: &[PathSegment],
322 children: Vec<ChildEntry>,
323 ) -> Vec<ChildEntry> {
324 let Some(rule) = self.layout.order_rule_for_parent(parent_path) else {
325 return children;
326 };
327
328 if !rule.append_unlisted {
329 let mut out = children;
330 let listed: Vec<(usize, ChildEntry)> = out
331 .iter()
332 .enumerate()
333 .filter(|(_, entry)| {
334 rule.child_order
335 .iter()
336 .any(|segment| segment == &entry.segment)
337 })
338 .map(|(idx, entry)| (idx, entry.clone()))
339 .collect();
340
341 let mut sorted = listed
342 .iter()
343 .map(|(_, entry)| entry.clone())
344 .collect::<Vec<_>>();
345 sorted.sort_by_key(|entry| {
346 rule.child_order
347 .iter()
348 .position(|segment| segment == &entry.segment)
349 .unwrap_or(usize::MAX)
350 });
351
352 for ((idx, _), replacement) in listed.into_iter().zip(sorted.into_iter()) {
353 out[idx] = replacement;
354 }
355
356 return out;
357 }
358
359 let mut remaining = children;
360 let mut ordered = Vec::new();
361
362 for segment in &rule.child_order {
363 if let Some(pos) = remaining.iter().position(|entry| &entry.segment == segment) {
364 ordered.push(remaining.remove(pos));
365 }
366 }
367
368 ordered.extend(remaining);
369
370 ordered
371 }
372
373 fn append_child_entries(
374 &self,
375 ctx: &mut EntryContext<'_>,
376 child_seg: PathSegment,
377 child_id: NodeId,
378 ) {
379 let child_node_path = concat_path(ctx.node_path, &child_seg);
380 let child_print_path = concat_path(ctx.path_prefix, &child_seg);
381
382 let style = self.layout.style_for_path(self.doc, &child_node_path);
383
384 if style == LayoutStyle::Passthrough {
385 let child_is_map = is_map_like(&self.doc.node(child_id).content);
386 let (b, s) = self.build_entries(
387 child_id,
388 &child_node_path,
389 &child_print_path,
390 child_is_map,
391 ctx.allow_sections,
392 );
393 ctx.bindings.extend(b);
394 ctx.sections.extend(s);
395 return;
396 }
397
398 let child_is_map = is_map_like(&self.doc.node(child_id).content);
399 let mut style = self.normalize_style(style, child_is_map, ctx.allow_sections);
400
401 if style == LayoutStyle::Binding
402 && child_is_map
403 && self.inline_binding_hides_descendant_entries(child_id, &child_node_path)
404 {
405 style = LayoutStyle::SectionBinding;
406 }
407
408 if style == LayoutStyle::Section && self.has_section_entries(child_id, &child_node_path) {
409 style = LayoutStyle::Nested;
410 }
411
412 match style {
413 LayoutStyle::Binding | LayoutStyle::Auto | LayoutStyle::Passthrough => {
414 ctx.bindings.push(ProjectedBinding {
415 path: child_print_path.clone(),
416 body: ProjectedBindingBody::Value(child_id),
417 });
418
419 let (b, s) = self.build_entries(
420 child_id,
421 &child_node_path,
422 &child_print_path,
423 false,
424 ctx.allow_sections,
425 );
426 ctx.bindings.extend(b);
427 ctx.sections.extend(s);
428 }
429 LayoutStyle::SectionBinding => {
430 if child_is_map {
431 let block = self.build_block(child_id, &child_node_path);
432 ctx.bindings.push(ProjectedBinding {
433 path: child_print_path,
434 body: ProjectedBindingBody::Block(block),
435 });
436 } else {
437 ctx.bindings.push(ProjectedBinding {
438 path: child_print_path.clone(),
439 body: ProjectedBindingBody::Value(child_id),
440 });
441 let (b, s) = self.build_entries(
442 child_id,
443 &child_node_path,
444 &child_print_path,
445 false,
446 ctx.allow_sections,
447 );
448 ctx.bindings.extend(b);
449 ctx.sections.extend(s);
450 }
451 }
452 LayoutStyle::Section => {
453 let (child_bindings, child_sections) =
454 self.build_entries(child_id, &child_node_path, &[], child_is_map, false);
455 debug_assert!(child_sections.is_empty());
456 ctx.sections.push(ProjectedSection {
457 path: child_print_path,
458 body: ProjectedSectionBody::Items {
459 value: None,
460 bindings: child_bindings,
461 },
462 });
463 }
464 LayoutStyle::SectionRootBinding => {
465 if child_is_map {
466 let (child_bindings, child_sections) =
467 self.build_entries(child_id, &child_node_path, &[], true, false);
468 debug_assert!(child_sections.is_empty());
469 ctx.sections.push(ProjectedSection {
470 path: child_print_path,
471 body: ProjectedSectionBody::Items {
472 value: None,
473 bindings: child_bindings,
474 },
475 });
476 } else {
477 let (child_bindings, child_sections) =
478 self.build_entries(child_id, &child_node_path, &[], false, false);
479 debug_assert!(child_sections.is_empty());
480 ctx.sections.push(ProjectedSection {
481 path: child_print_path,
482 body: ProjectedSectionBody::Items {
483 value: Some(child_id),
484 bindings: child_bindings,
485 },
486 });
487 }
488 }
489 LayoutStyle::Nested => {
490 if child_is_map {
491 let block = self.build_block(child_id, &child_node_path);
492 ctx.sections.push(ProjectedSection {
493 path: child_print_path,
494 body: ProjectedSectionBody::Block(block),
495 });
496 } else {
497 ctx.bindings.push(ProjectedBinding {
498 path: child_print_path.clone(),
499 body: ProjectedBindingBody::Value(child_id),
500 });
501 let (b, s) = self.build_entries(
502 child_id,
503 &child_node_path,
504 &child_print_path,
505 false,
506 ctx.allow_sections,
507 );
508 ctx.bindings.extend(b);
509 ctx.sections.extend(s);
510 }
511 }
512 }
513 }
514
515 fn normalize_style(
516 &self,
517 style: LayoutStyle,
518 node_is_map: bool,
519 allow_sections: bool,
520 ) -> LayoutStyle {
521 let mut style = match style {
522 LayoutStyle::Auto | LayoutStyle::Passthrough => LayoutStyle::Binding,
523 other => other,
524 };
525
526 if !node_is_map {
527 style = match style {
528 LayoutStyle::SectionRootBinding => LayoutStyle::SectionRootBinding,
529 _ => LayoutStyle::Binding,
530 };
531 } else if style == LayoutStyle::SectionRootBinding {
532 style = LayoutStyle::Section;
533 }
534
535 if !allow_sections
536 && matches!(
537 style,
538 LayoutStyle::Section | LayoutStyle::Nested | LayoutStyle::SectionRootBinding
539 )
540 {
541 style = if node_is_map {
542 LayoutStyle::SectionBinding
543 } else {
544 LayoutStyle::Binding
545 };
546 }
547
548 style
549 }
550
551 fn inline_binding_hides_descendant_entries(
552 &self,
553 node_id: NodeId,
554 node_path: &[PathSegment],
555 ) -> bool {
556 for ChildEntry {
557 segment,
558 node_id: child_id,
559 } in map_like_children(self.doc.node(node_id))
560 {
561 let child_node_path = concat_path(node_path, &segment);
562 if self.subtree_has_deferred_entries(child_id, &child_node_path) {
563 return true;
564 }
565 }
566
567 false
568 }
569
570 fn subtree_has_deferred_entries(&self, node_id: NodeId, node_path: &[PathSegment]) -> bool {
571 let node = self.doc.node(node_id);
572 if !node.extensions.is_empty() {
573 return true;
574 }
575
576 if self.has_section_entries(node_id, node_path) {
577 return true;
578 }
579
580 if !is_map_like(&node.content) {
581 return false;
582 }
583
584 for ChildEntry {
585 segment,
586 node_id: child_id,
587 } in map_like_children(node)
588 {
589 let child_node_path = concat_path(node_path, &segment);
590 if self.subtree_has_deferred_entries(child_id, &child_node_path) {
591 return true;
592 }
593 }
594
595 false
596 }
597
598 fn has_section_entries(&self, node_id: NodeId, node_path: &[PathSegment]) -> bool {
599 let node = self.doc.node(node_id);
600 let node_is_map = is_map_like(&node.content);
601
602 for (ident, &child_id) in node.extensions.iter() {
603 let seg = PathSegment::Extension(ident.clone());
604 let child_node_path = concat_path(node_path, &seg);
605 if self.child_is_section(child_id, &child_node_path) {
606 return true;
607 }
608 }
609
610 if node_is_map {
611 for ChildEntry {
612 segment,
613 node_id: child_id,
614 } in map_like_children(node)
615 {
616 let child_node_path = concat_path(node_path, &segment);
617 if self.child_is_section(child_id, &child_node_path) {
618 return true;
619 }
620 }
621 }
622
623 false
624 }
625
626 fn child_is_section(&self, child_id: NodeId, child_node_path: &[PathSegment]) -> bool {
627 let style = self.layout.style_for_path(self.doc, child_node_path);
628 if style == LayoutStyle::Passthrough {
629 return self.has_section_entries(child_id, child_node_path);
630 }
631
632 let child_is_map = is_map_like(&self.doc.node(child_id).content);
633 let style = self.normalize_style(style, child_is_map, true);
634 matches!(
635 style,
636 LayoutStyle::Section | LayoutStyle::Nested | LayoutStyle::SectionRootBinding
637 )
638 }
639}
640
641fn is_map_like(content: &NodeValue) -> bool {
642 matches!(content, NodeValue::Map(_) | NodeValue::PartialMap(_))
643}
644
645fn map_like_children(node: &crate::document::node::Node) -> Vec<ChildEntry> {
646 match &node.content {
647 NodeValue::Map(map) => map
648 .iter()
649 .map(|(key, &child_id)| ChildEntry {
650 segment: PathSegment::Value(key.clone()),
651 node_id: child_id,
652 })
653 .collect(),
654 NodeValue::PartialMap(map) => map
655 .iter()
656 .map(|(key, &child_id)| ChildEntry {
657 segment: PathSegment::from_partial_object_key(key.clone()),
658 node_id: child_id,
659 })
660 .collect(),
661 _ => Vec::new(),
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
666enum RuleMatchStatus {
667 NoMatch,
668 Exact(LayoutStyle),
669 Potential(LayoutStyle),
670}
671
672fn rule_match_status(doc: &EureDocument, rule: &LayoutStyleRule) -> RuleMatchStatus {
673 if rule.variant_constraints.is_empty() {
674 return RuleMatchStatus::Exact(rule.style);
675 }
676
677 let mut inherited_variant: Option<VariantPath> = None;
678 let mut uncertain = false;
679
680 for constraint in &rule.variant_constraints {
681 let Some(union_node_id) = node_id_at_path(doc, &constraint.union_path) else {
682 return RuleMatchStatus::NoMatch;
683 };
684
685 let explicit_variant = if let Some(vp) = inherited_variant.as_ref()
686 && !vp.is_empty()
687 {
688 Some(vp.clone())
689 } else {
690 match parse_variant_extension(doc, union_node_id) {
691 Ok(path) => path,
692 Err(()) => return RuleMatchStatus::NoMatch,
693 }
694 };
695
696 match explicit_variant {
697 Some(vp) if !vp.is_empty() => {
698 let Some(first) = vp.first() else {
699 return RuleMatchStatus::NoMatch;
700 };
701 if first.as_ref() != constraint.variant {
702 return RuleMatchStatus::NoMatch;
703 }
704 inherited_variant = vp.rest();
705 }
706 _ => {
707 inherited_variant = None;
708 if constraint.requires_explicit_tag {
709 return RuleMatchStatus::NoMatch;
710 }
711 uncertain = true;
712 }
713 }
714 }
715
716 if uncertain {
717 RuleMatchStatus::Potential(rule.style)
718 } else {
719 RuleMatchStatus::Exact(rule.style)
720 }
721}
722
723fn parse_variant_extension(doc: &EureDocument, node_id: NodeId) -> Result<Option<VariantPath>, ()> {
724 let node = doc.node(node_id);
725 let Some(&variant_node_id) = node.extensions.get(&VARIANT) else {
726 return Ok(None);
727 };
728
729 let Ok(variant_text) = doc.parse::<&str>(variant_node_id) else {
730 return Err(());
731 };
732
733 VariantPath::parse(variant_text).map(Some).map_err(|_| ())
734}
735
736fn node_id_at_path(doc: &EureDocument, path: &[PathSegment]) -> Option<NodeId> {
737 let mut current = doc.get_root_id();
738 for segment in path {
739 current = child_node_id(doc, current, segment)?;
740 }
741 Some(current)
742}
743
744fn build_source_block(block: &ProjectedBlock, sources: &mut Vec<EureSource>) -> SourceId {
745 let id = SourceId(sources.len());
746 sources.push(EureSource::default());
747 let mut eure = EureSource {
748 value: block.value,
749 ..Default::default()
750 };
751
752 for binding in &block.bindings {
753 let path = to_source_path(&binding.path);
754 let bind = match &binding.body {
755 ProjectedBindingBody::Value(node_id) => BindSource::Value(*node_id),
756 ProjectedBindingBody::Block(inner) => {
757 let inner_id = build_source_block(inner, sources);
758 BindSource::Block(inner_id)
759 }
760 };
761 eure.bindings.push(BindingSource {
762 trivia_before: Vec::new(),
763 path,
764 bind,
765 trailing_comment: None,
766 });
767 }
768
769 for section in &block.sections {
770 let path = to_source_path(§ion.path);
771 let body = match §ion.body {
772 ProjectedSectionBody::Items { value, bindings } => {
773 let mut items = Vec::new();
774 for binding in bindings {
775 let path = to_source_path(&binding.path);
776 let bind = match &binding.body {
777 ProjectedBindingBody::Value(node_id) => BindSource::Value(*node_id),
778 ProjectedBindingBody::Block(inner) => {
779 let inner_id = build_source_block(inner, sources);
780 BindSource::Block(inner_id)
781 }
782 };
783 items.push(BindingSource {
784 trivia_before: Vec::new(),
785 path,
786 bind,
787 trailing_comment: None,
788 });
789 }
790 SectionBody::Items {
791 value: *value,
792 bindings: items,
793 }
794 }
795 ProjectedSectionBody::Block(inner) => {
796 let inner_id = build_source_block(inner, sources);
797 SectionBody::Block(inner_id)
798 }
799 };
800 eure.sections.push(SectionSource {
801 trivia_before: Vec::new(),
802 path,
803 body,
804 trailing_comment: None,
805 });
806 }
807
808 sources[id.0] = eure;
809 id
810}
811
812fn to_source_path(path: &[PathSegment]) -> SourcePath {
813 let mut out: Vec<SourcePathSegment> = Vec::new();
814 for seg in path {
815 match seg {
816 PathSegment::Ident(id) => out.push(SourcePathSegment::ident(id.clone())),
817 PathSegment::Extension(id) => out.push(SourcePathSegment::extension(id.clone())),
818 PathSegment::PartialValue(key) => out.push(SourcePathSegment {
819 key: partial_object_key_to_source_key(key),
820 array: None,
821 }),
822 PathSegment::HoleKey(label) => out.push(SourcePathSegment {
823 key: SourceKey::hole(label.clone()),
824 array: None,
825 }),
826 PathSegment::Value(key) => out.push(SourcePathSegment {
827 key: object_key_to_source_key(key),
828 array: None,
829 }),
830 PathSegment::TupleIndex(index) => out.push(SourcePathSegment {
831 key: SourceKey::TupleIndex(*index),
832 array: None,
833 }),
834 PathSegment::ArrayIndex(index) => {
835 if let Some(last) = out.last_mut() {
836 last.array = Some(*index);
837 }
838 }
839 }
840 }
841 out
842}
843
844fn object_key_to_source_key(key: &ObjectKey) -> SourceKey {
845 match key {
846 ObjectKey::String(s) => {
847 if let Ok(id) = s.parse::<Identifier>() {
848 SourceKey::Ident(id)
849 } else {
850 SourceKey::quoted(s.clone())
851 }
852 }
853 ObjectKey::Number(n) => {
854 if let Ok(n64) = i64::try_from(n) {
855 SourceKey::Integer(n64)
856 } else {
857 SourceKey::quoted(n.to_string())
858 }
859 }
860 ObjectKey::Tuple(keys) => {
861 SourceKey::Tuple(keys.iter().map(object_key_to_source_key).collect())
862 }
863 }
864}
865
866fn partial_object_key_to_source_key(key: &PartialObjectKey) -> SourceKey {
867 match key {
868 PartialObjectKey::String(s) => {
869 if let Ok(id) = s.parse::<Identifier>() {
870 SourceKey::Ident(id)
871 } else {
872 SourceKey::quoted(s.clone())
873 }
874 }
875 PartialObjectKey::Number(n) => {
876 if let Ok(n64) = i64::try_from(n) {
877 SourceKey::Integer(n64)
878 } else {
879 SourceKey::quoted(n.to_string())
880 }
881 }
882 PartialObjectKey::Hole(label) => SourceKey::hole(label.clone()),
883 PartialObjectKey::Tuple(keys) => {
884 SourceKey::Tuple(keys.iter().map(partial_object_key_to_source_key).collect())
885 }
886 }
887}
888
889fn concat_path(prefix: &[PathSegment], seg: &PathSegment) -> Vec<PathSegment> {
890 let mut out = Vec::with_capacity(prefix.len() + 1);
891 out.extend_from_slice(prefix);
892 out.push(seg.clone());
893 out
894}
895
896fn child_node_id(doc: &EureDocument, parent_id: NodeId, segment: &PathSegment) -> Option<NodeId> {
897 let parent = doc.node(parent_id);
898 match segment {
899 PathSegment::Extension(ext) => parent.extensions.get(ext).copied(),
900 PathSegment::Ident(ident) => match &parent.content {
901 NodeValue::Map(map) => map.get(&ObjectKey::String(ident.to_string())).copied(),
902 NodeValue::PartialMap(map) => map
903 .find(&PartialObjectKey::String(ident.to_string()))
904 .copied(),
905 _ => None,
906 },
907 PathSegment::Value(key) => match &parent.content {
908 NodeValue::Map(map) => map.get(key).copied(),
909 NodeValue::PartialMap(map) => map.find(&PartialObjectKey::from(key.clone())).copied(),
910 _ => None,
911 },
912 PathSegment::PartialValue(key) => match &parent.content {
913 NodeValue::Map(map) => ObjectKey::try_from(key.clone())
914 .ok()
915 .and_then(|object_key| map.get(&object_key))
916 .copied(),
917 NodeValue::PartialMap(map) => map.find(key).copied(),
918 _ => None,
919 },
920 PathSegment::ArrayIndex(index) => match &parent.content {
921 NodeValue::Array(array) => index.and_then(|i| array.get(i)),
922 _ => None,
923 },
924 PathSegment::TupleIndex(index) => match &parent.content {
925 NodeValue::Tuple(tuple) => tuple.get(*index as usize),
926 _ => None,
927 },
928 PathSegment::HoleKey(label) => match &parent.content {
929 NodeValue::PartialMap(map) => map.find(&PartialObjectKey::Hole(label.clone())).copied(),
930 _ => None,
931 },
932 }
933}
934
935#[cfg(test)]
936mod tests {
937 use super::*;
938 use crate::document::constructor::DocumentConstructor;
939 use crate::value::ObjectKey;
940 use alloc::vec;
941
942 fn make_doc() -> EureDocument {
943 let mut c = DocumentConstructor::new();
944 c.bind_empty_map().unwrap();
945
946 let scope = c.begin_scope();
947 c.navigate(PathSegment::Value(ObjectKey::String("a".to_string())))
948 .unwrap();
949 c.bind_empty_map().unwrap();
950 let inner = c.begin_scope();
951 c.navigate(PathSegment::Value(ObjectKey::String("x".to_string())))
952 .unwrap();
953 c.bind_primitive(crate::value::PrimitiveValue::Integer(1.into()))
954 .unwrap();
955 c.end_scope(inner).unwrap();
956 c.end_scope(scope).unwrap();
957
958 let scope = c.begin_scope();
959 c.navigate(PathSegment::Value(ObjectKey::String("b".to_string())))
960 .unwrap();
961 c.bind_empty_map().unwrap();
962 c.end_scope(scope).unwrap();
963
964 c.finish()
965 }
966
967 #[test]
968 fn applies_section_binding_rule() {
969 let doc = make_doc();
970 let mut layout = DocLayout::new();
971 layout.add_style_rule(
972 vec![PathSegment::Value(ObjectKey::String("a".to_string()))],
973 LayoutStyle::SectionBinding,
974 );
975
976 let source = project_with_layout(&doc, &layout);
977 let root = source.root_source();
978
979 assert_eq!(root.bindings.len(), 2);
980 assert!(matches!(root.bindings[0].bind, BindSource::Block(_)));
981 }
982
983 #[test]
984 fn applies_order_rule() {
985 let doc = make_doc();
986 let mut layout = DocLayout::new();
987 layout.add_order_rule(
988 Vec::new(),
989 vec![PathSegment::Value(ObjectKey::String("b".to_string()))],
990 true,
991 );
992
993 let source = project_with_layout(&doc, &layout);
994 let root = source.root_source();
995
996 let first = &root.bindings[0].path[0];
997 assert!(matches!(
998 first,
999 SourcePathSegment {
1000 key: SourceKey::Ident(id),
1001 ..
1002 } if id.as_ref() == "b"
1003 ));
1004 }
1005
1006 #[test]
1007 fn passthrough_flattens_parent_binding() {
1008 let doc = make_doc();
1009 let mut layout = DocLayout::new();
1010 layout.add_style_rule(
1011 vec![PathSegment::Value(ObjectKey::String("a".to_string()))],
1012 LayoutStyle::Passthrough,
1013 );
1014
1015 let source = project_with_layout(&doc, &layout);
1016 let root = source.root_source();
1017
1018 assert!(!root.bindings.iter().any(|b| {
1019 matches!(
1020 b.path.as_slice(),
1021 [SourcePathSegment {
1022 key: SourceKey::Ident(id),
1023 ..
1024 }] if id.as_ref() == "a"
1025 )
1026 }));
1027 assert!(root.bindings.iter().any(|b| {
1028 matches!(
1029 b.path.as_slice(),
1030 [
1031 SourcePathSegment {
1032 key: SourceKey::Ident(first),
1033 ..
1034 },
1035 SourcePathSegment {
1036 key: SourceKey::Ident(second),
1037 ..
1038 }
1039 ] if first.as_ref() == "a" && second.as_ref() == "x"
1040 )
1041 }));
1042 }
1043
1044 #[test]
1045 fn auto_promotes_inline_map_when_nested_extensions_would_be_lost() {
1046 let mut c = DocumentConstructor::new();
1047 c.bind_empty_map().unwrap();
1048
1049 let outer_scope = c.begin_scope();
1050 c.navigate(PathSegment::Value(ObjectKey::String("outer".to_string())))
1051 .unwrap();
1052 c.bind_empty_map().unwrap();
1053
1054 let inner_scope = c.begin_scope();
1055 c.navigate(PathSegment::Value(ObjectKey::String("inner".to_string())))
1056 .unwrap();
1057 c.bind_primitive(crate::value::PrimitiveValue::Integer(1.into()))
1058 .unwrap();
1059 c.set_extension("flag", true).unwrap();
1060 c.end_scope(inner_scope).unwrap();
1061 c.end_scope(outer_scope).unwrap();
1062
1063 let doc = c.finish();
1064 let source = project_with_layout(&doc, &DocLayout::new());
1065 let root = source.root_source();
1066
1067 assert_eq!(root.bindings.len(), 1);
1068 let BindSource::Block(outer_block_id) = &root.bindings[0].bind else {
1069 panic!("expected outer map to be promoted to a block binding");
1070 };
1071 let outer_block = source.source(*outer_block_id);
1072 assert_eq!(outer_block.bindings.len(), 2);
1073 }
1074}