Skip to main content

eure_document/plan/
emit.rs

1//! Emission of a [`crate::source::SourceDocument`] from a validated
2//! [`LayoutPlan`].
3//!
4//! The walk is shared between the structural validation pass (`dry_walk_validate`)
5//! and the real emission (`emit`); both use the same control flow so that
6//! validation mirrors what emission would produce.
7
8use alloc::string::ToString;
9use alloc::vec::Vec;
10
11use crate::document::node::NodeValue;
12use crate::document::{EureDocument, NodeId};
13use crate::identifier::Identifier;
14use crate::path::{ArrayIndexKind, PathSegment};
15use crate::plan::{ArrayForm, Form, LayoutPlan, PlanError};
16use crate::source::{
17    BindSource, BindingSource, EureSource, SectionBody, SectionSource, SourceDocument, SourceId,
18    SourceKey, SourcePath, SourcePathSegment,
19};
20use crate::value::{ObjectKey, PartialObjectKey};
21
22// ============================================================================
23// Entry points
24// ============================================================================
25
26pub fn emit(plan: LayoutPlan) -> SourceDocument {
27    // Validation during build() already checked structure; here we just walk
28    // without collecting errors.
29    let LayoutPlan {
30        doc,
31        forms,
32        array_forms,
33        order,
34    } = plan;
35    let sources = {
36        let mut ctx = EmitCtx {
37            doc: &doc,
38            forms: &forms,
39            array_forms: &array_forms,
40            order: &order,
41            sources: Vec::new(),
42            record_errors: None,
43            emitted: Vec::new(),
44        };
45        let root = build_root(&mut ctx);
46        debug_assert_eq!(root.0, 0);
47        ctx.sources
48    };
49    SourceDocument::new(doc, sources)
50}
51
52pub fn dry_walk_validate(plan: &LayoutPlan) -> Result<(), PlanError> {
53    let mut errors: Vec<PlanError> = Vec::new();
54    let mut ctx = EmitCtx {
55        doc: &plan.doc,
56        forms: &plan.forms,
57        array_forms: &plan.array_forms,
58        order: &plan.order,
59        sources: Vec::new(),
60        record_errors: Some(&mut errors),
61        emitted: Vec::new(),
62    };
63    build_root(&mut ctx);
64    // Coverage/uniqueness already tracked in `emitted` via duplicate detection.
65    if let Some(err) = errors.into_iter().next() {
66        return Err(err);
67    }
68    Ok(())
69}
70
71// ============================================================================
72// Walk state
73// ============================================================================
74
75struct EmitCtx<'a> {
76    doc: &'a EureDocument,
77    forms: &'a crate::map::Map<NodeId, Form>,
78    array_forms: &'a crate::map::Map<NodeId, ArrayForm>,
79    order: &'a crate::map::Map<NodeId, Vec<NodeId>>,
80    sources: Vec<EureSource>,
81    /// When Some, errors are collected into this buffer and emission is a
82    /// no-op (used by the dry validation pass).
83    record_errors: Option<&'a mut Vec<PlanError>>,
84    emitted: Vec<NodeId>,
85}
86
87impl EmitCtx<'_> {
88    fn is_dry(&self) -> bool {
89        self.record_errors.is_some()
90    }
91
92    fn push_error(&mut self, err: PlanError) {
93        if let Some(buf) = self.record_errors.as_deref_mut() {
94            buf.push(err);
95        }
96    }
97
98    fn record_emission(&mut self, id: NodeId) {
99        if self.emitted.contains(&id) {
100            self.push_error(PlanError::DuplicateEmission { node: id });
101        } else {
102            self.emitted.push(id);
103        }
104    }
105
106    fn reserve_source(&mut self) -> SourceId {
107        let id = SourceId(self.sources.len());
108        self.sources.push(EureSource::default());
109        id
110    }
111
112    fn set_source(&mut self, id: SourceId, src: EureSource) {
113        self.sources[id.0] = src;
114    }
115}
116
117// ============================================================================
118// Root / block emission
119// ============================================================================
120
121fn build_root(ctx: &mut EmitCtx) -> SourceId {
122    let root_id = ctx.doc.get_root_id();
123    let id = ctx.reserve_source();
124    let root_node = ctx.doc.node(root_id);
125    let root_is_map = matches!(
126        root_node.content,
127        NodeValue::Map(_) | NodeValue::PartialMap(_)
128    );
129    let value = if root_is_map { None } else { Some(root_id) };
130    ctx.record_emission(root_id);
131
132    let mut eure = EureSource {
133        value,
134        ..Default::default()
135    };
136    emit_children(ctx, root_id, &[], &[], root_is_map, true, &mut eure);
137    ctx.set_source(id, eure);
138    id
139}
140
141/// Emit the children of `parent_id` as bindings/sections into `dest`.
142///
143/// - `node_path`: semantic path in the document used for ordering and schema
144///   lookups.
145/// - `path_prefix`: source-path prefix to prepend when emitting bindings for
146///   hoisted children (used by `Flatten` forms).
147/// - `emit_map_fields`: whether to emit the map/partial-map children of this
148///   node (extensions are always emitted).
149/// - `allow_sections`: whether a child may be emitted as a section in this
150///   context.
151#[allow(clippy::too_many_arguments)]
152fn emit_children(
153    ctx: &mut EmitCtx,
154    parent_id: NodeId,
155    node_path: &[PathSegment],
156    path_prefix: &[PathSegment],
157    emit_map_fields: bool,
158    allow_sections: bool,
159    dest: &mut EureSource,
160) {
161    let node = ctx.doc.node(parent_id);
162    let mut children: Vec<(PathSegment, NodeId)> = Vec::new();
163
164    for (ident, &cid) in node.extensions.iter() {
165        children.push((PathSegment::Extension(ident.clone()), cid));
166    }
167
168    if emit_map_fields {
169        match &node.content {
170            NodeValue::Map(map) => {
171                for (key, &cid) in map.iter() {
172                    children.push((PathSegment::Value(key.clone()), cid));
173                }
174            }
175            NodeValue::PartialMap(pm) => {
176                for (key, &cid) in pm.iter() {
177                    children.push((PathSegment::from_partial_object_key(key.clone()), cid));
178                }
179            }
180            _ => {}
181        }
182    }
183
184    // Apply ordering if set on the parent.
185    let children = apply_order(ctx, parent_id, children);
186
187    for (seg, child_id) in children {
188        let child_node_path = concat_path(node_path, &seg);
189        let child_print_path = concat_path(path_prefix, &seg);
190        emit_child(
191            ctx,
192            child_id,
193            &seg,
194            &child_node_path,
195            &child_print_path,
196            allow_sections,
197            dest,
198        );
199    }
200}
201
202fn apply_order(
203    ctx: &EmitCtx,
204    parent_id: NodeId,
205    children: Vec<(PathSegment, NodeId)>,
206) -> Vec<(PathSegment, NodeId)> {
207    // Extensions always precede value children to keep tags like `$variant`
208    // at the top of a block; the user-supplied order only ranks value
209    // children.
210    let (extensions, values): (Vec<_>, Vec<_>) = children
211        .into_iter()
212        .partition(|(seg, _)| matches!(seg, PathSegment::Extension(_)));
213    let Some(order) = ctx.order.get(&parent_id) else {
214        let mut out = extensions;
215        out.extend(values);
216        return out;
217    };
218    let mut by_id: Vec<(PathSegment, NodeId)> = values;
219    let mut ordered: Vec<(PathSegment, NodeId)> = extensions;
220    for id in order {
221        if let Some(pos) = by_id.iter().position(|(_, cid)| cid == id) {
222            ordered.push(by_id.remove(pos));
223        }
224    }
225    ordered.extend(by_id);
226    ordered
227}
228
229fn emit_child(
230    ctx: &mut EmitCtx,
231    child_id: NodeId,
232    _seg: &PathSegment,
233    child_node_path: &[PathSegment],
234    child_print_path: &[PathSegment],
235    allow_sections: bool,
236    dest: &mut EureSource,
237) {
238    let kind = ctx.doc.node(child_id).content.value_kind();
239    if matches!(kind, crate::value::ValueKind::Array) {
240        let Some(array_form) = ctx.array_forms.get(&child_id).copied() else {
241            ctx.push_error(PlanError::MissingForm(child_id));
242            return;
243        };
244        emit_array_child(
245            ctx,
246            child_id,
247            child_node_path,
248            child_print_path,
249            allow_sections,
250            array_form,
251            dest,
252        );
253        return;
254    }
255
256    let Some(form) = ctx.forms.get(&child_id).copied() else {
257        ctx.push_error(PlanError::MissingForm(child_id));
258        return;
259    };
260
261    emit_non_array_child(
262        ctx,
263        child_id,
264        child_node_path,
265        child_print_path,
266        allow_sections,
267        form,
268        dest,
269    );
270}
271
272fn emit_non_array_child(
273    ctx: &mut EmitCtx,
274    child_id: NodeId,
275    child_node_path: &[PathSegment],
276    child_print_path: &[PathSegment],
277    allow_sections: bool,
278    form: Form,
279    dest: &mut EureSource,
280) {
281    let is_section = matches!(
282        form,
283        Form::Section | Form::SectionBlock | Form::SectionValueBlock
284    );
285    if is_section && !allow_sections {
286        ctx.push_error(PlanError::SectionInForbiddenContext(child_id));
287        return;
288    }
289
290    match form {
291        Form::Inline => {
292            ctx.record_emission(child_id);
293            if !ctx.is_dry() {
294                dest.bindings.push(BindingSource {
295                    trivia_before: Vec::new(),
296                    path: to_source_path(child_print_path),
297                    bind: BindSource::Value(child_id),
298                    trailing_comment: None,
299                });
300            }
301            // Inline binds the value; extensions are not lost — they descend
302            // as bindings prefixed by the inline path.
303            emit_extensions_only(
304                ctx,
305                child_id,
306                child_node_path,
307                child_print_path,
308                allow_sections,
309                dest,
310            );
311        }
312        Form::BindingBlock => {
313            ctx.record_emission(child_id);
314            let block_id = build_block(ctx, child_id, child_node_path, /*with_value=*/ false);
315            if !ctx.is_dry() {
316                dest.bindings.push(BindingSource {
317                    trivia_before: Vec::new(),
318                    path: to_source_path(child_print_path),
319                    bind: BindSource::Block(block_id),
320                    trailing_comment: None,
321                });
322            }
323        }
324        Form::BindingValueBlock => {
325            ctx.record_emission(child_id);
326            let block_id = build_block(ctx, child_id, child_node_path, /*with_value=*/ true);
327            if !ctx.is_dry() {
328                dest.bindings.push(BindingSource {
329                    trivia_before: Vec::new(),
330                    path: to_source_path(child_print_path),
331                    bind: BindSource::Block(block_id),
332                    trailing_comment: None,
333                });
334            }
335        }
336        Form::Section => {
337            ctx.record_emission(child_id);
338            let (value, bindings) = build_items(ctx, child_id, child_node_path, false);
339            if !ctx.is_dry() {
340                dest.sections.push(SectionSource {
341                    trivia_before: Vec::new(),
342                    path: to_source_path(child_print_path),
343                    body: SectionBody::Items { value, bindings },
344                    trailing_comment: None,
345                });
346            }
347        }
348        Form::SectionBlock => {
349            ctx.record_emission(child_id);
350            let block_id = build_block(ctx, child_id, child_node_path, /*with_value=*/ false);
351            if !ctx.is_dry() {
352                dest.sections.push(SectionSource {
353                    trivia_before: Vec::new(),
354                    path: to_source_path(child_print_path),
355                    body: SectionBody::Block(block_id),
356                    trailing_comment: None,
357                });
358            }
359        }
360        Form::SectionValueBlock => {
361            ctx.record_emission(child_id);
362            let (value, bindings) = build_items(ctx, child_id, child_node_path, true);
363            if !ctx.is_dry() {
364                dest.sections.push(SectionSource {
365                    trivia_before: Vec::new(),
366                    path: to_source_path(child_print_path),
367                    body: SectionBody::Items { value, bindings },
368                    trailing_comment: None,
369                });
370            }
371        }
372        Form::Flatten => {
373            // No self-emission. Children hoist under the current print path.
374            let is_map = matches!(
375                ctx.doc.node(child_id).content,
376                NodeValue::Map(_) | NodeValue::PartialMap(_)
377            );
378            emit_children(
379                ctx,
380                child_id,
381                child_node_path,
382                child_print_path,
383                is_map,
384                allow_sections,
385                dest,
386            );
387        }
388    }
389}
390
391fn emit_array_child(
392    ctx: &mut EmitCtx,
393    child_id: NodeId,
394    child_node_path: &[PathSegment],
395    child_print_path: &[PathSegment],
396    allow_sections: bool,
397    array_form: ArrayForm,
398    dest: &mut EureSource,
399) {
400    match array_form {
401        ArrayForm::Inline => {
402            ctx.record_emission(child_id);
403            if !ctx.is_dry() {
404                dest.bindings.push(BindingSource {
405                    trivia_before: Vec::new(),
406                    path: to_source_path(child_print_path),
407                    bind: BindSource::Value(child_id),
408                    trailing_comment: None,
409                });
410            }
411            // Emit extensions (e.g. `$optional`, `$variant`) as path-prefixed
412            // bindings, mirroring how Form::Inline handles them.
413            emit_extensions_only(
414                ctx,
415                child_id,
416                child_node_path,
417                child_print_path,
418                allow_sections,
419                dest,
420            );
421        }
422        ArrayForm::PerElement(element_form) | ArrayForm::PerElementIndexed(element_form) => {
423            ctx.record_emission(child_id);
424            let NodeValue::Array(arr) = &ctx.doc.node(child_id).content else {
425                return;
426            };
427            let indexed = matches!(array_form, ArrayForm::PerElementIndexed(_));
428            let ids: Vec<NodeId> = arr.iter().copied().collect();
429            for (i, el_id) in ids.into_iter().enumerate() {
430                let mut element_print_path = child_print_path.to_vec();
431                element_print_path.push(PathSegment::ArrayIndex(if indexed {
432                    ArrayIndexKind::Specific(i)
433                } else {
434                    ArrayIndexKind::Push
435                }));
436                let mut element_node_path = child_node_path.to_vec();
437                element_node_path.push(PathSegment::ArrayIndex(ArrayIndexKind::Specific(i)));
438                emit_non_array_child(
439                    ctx,
440                    el_id,
441                    &element_node_path,
442                    &element_print_path,
443                    allow_sections,
444                    element_form,
445                    dest,
446                );
447            }
448        }
449    }
450}
451
452fn emit_extensions_only(
453    ctx: &mut EmitCtx,
454    node_id: NodeId,
455    child_node_path: &[PathSegment],
456    child_print_path: &[PathSegment],
457    allow_sections: bool,
458    dest: &mut EureSource,
459) {
460    let node = ctx.doc.node(node_id);
461    let ext_children: Vec<(Identifier, NodeId)> = node
462        .extensions
463        .iter()
464        .map(|(k, &v)| (k.clone(), v))
465        .collect();
466    for (ident, ext_id) in ext_children {
467        let seg = PathSegment::Extension(ident);
468        let mut ext_node_path = child_node_path.to_vec();
469        ext_node_path.push(seg.clone());
470        let mut ext_print_path = child_print_path.to_vec();
471        ext_print_path.push(seg);
472        emit_child(
473            ctx,
474            ext_id,
475            ext_print_path.last().unwrap(),
476            &ext_node_path,
477            &ext_print_path,
478            allow_sections,
479            dest,
480        );
481    }
482}
483
484fn build_block(
485    ctx: &mut EmitCtx,
486    node_id: NodeId,
487    node_path: &[PathSegment],
488    with_value: bool,
489) -> SourceId {
490    let id = ctx.reserve_source();
491    let value = if with_value { Some(node_id) } else { None };
492    let node_is_map = matches!(
493        ctx.doc.node(node_id).content,
494        NodeValue::Map(_) | NodeValue::PartialMap(_)
495    );
496    let mut inner = EureSource {
497        value,
498        ..Default::default()
499    };
500    emit_children(ctx, node_id, node_path, &[], node_is_map, true, &mut inner);
501    ctx.set_source(id, inner);
502    id
503}
504
505fn build_items(
506    ctx: &mut EmitCtx,
507    node_id: NodeId,
508    node_path: &[PathSegment],
509    with_value: bool,
510) -> (Option<NodeId>, Vec<BindingSource>) {
511    // For Section and SectionValueBlock, emit the items body: `{value}` + bindings only,
512    // disallowing nested sections (they must be sibling sections at the same level).
513    let node_is_map = matches!(
514        ctx.doc.node(node_id).content,
515        NodeValue::Map(_) | NodeValue::PartialMap(_)
516    );
517    let mut inner = EureSource {
518        value: if with_value { Some(node_id) } else { None },
519        ..Default::default()
520    };
521    emit_children(ctx, node_id, node_path, &[], node_is_map, false, &mut inner);
522    let value = inner.value;
523    let bindings = inner.bindings;
524    debug_assert!(inner.sections.is_empty());
525    (value, bindings)
526}
527
528// ============================================================================
529// Path conversion (verbatim from layout.rs)
530// ============================================================================
531
532pub(crate) fn to_source_path(path: &[PathSegment]) -> SourcePath {
533    let mut out: Vec<SourcePathSegment> = Vec::new();
534    for seg in path {
535        match seg {
536            PathSegment::Ident(id) => out.push(SourcePathSegment::ident(id.clone())),
537            PathSegment::Extension(id) => out.push(SourcePathSegment::extension(id.clone())),
538            PathSegment::PartialValue(key) => out.push(SourcePathSegment {
539                key: partial_object_key_to_source_key(key),
540                array: None,
541            }),
542            PathSegment::HoleKey(label) => out.push(SourcePathSegment {
543                key: SourceKey::hole(label.clone()),
544                array: None,
545            }),
546            PathSegment::Value(key) => out.push(SourcePathSegment {
547                key: object_key_to_source_key(key),
548                array: None,
549            }),
550            PathSegment::TupleIndex(index) => out.push(SourcePathSegment {
551                key: SourceKey::TupleIndex(*index),
552                array: None,
553            }),
554            PathSegment::ArrayIndex(index) => {
555                if let Some(last) = out.last_mut() {
556                    last.array = Some(*index);
557                }
558            }
559        }
560    }
561    out
562}
563
564fn object_key_to_source_key(key: &ObjectKey) -> SourceKey {
565    match key {
566        ObjectKey::String(s) => {
567            if let Ok(id) = s.parse::<Identifier>() {
568                SourceKey::Ident(id)
569            } else {
570                SourceKey::quoted(s.clone())
571            }
572        }
573        ObjectKey::Number(n) => {
574            if let Ok(n64) = i64::try_from(n) {
575                SourceKey::Integer(n64)
576            } else {
577                SourceKey::quoted(n.to_string())
578            }
579        }
580        ObjectKey::Tuple(keys) => {
581            SourceKey::Tuple(keys.iter().map(object_key_to_source_key).collect())
582        }
583    }
584}
585
586fn partial_object_key_to_source_key(key: &PartialObjectKey) -> SourceKey {
587    match key {
588        PartialObjectKey::String(s) => {
589            if let Ok(id) = s.parse::<Identifier>() {
590                SourceKey::Ident(id)
591            } else {
592                SourceKey::quoted(s.clone())
593            }
594        }
595        PartialObjectKey::Number(n) => {
596            if let Ok(n64) = i64::try_from(n) {
597                SourceKey::Integer(n64)
598            } else {
599                SourceKey::quoted(n.to_string())
600            }
601        }
602        PartialObjectKey::Hole(label) => SourceKey::hole(label.clone()),
603        PartialObjectKey::Tuple(keys) => {
604            SourceKey::Tuple(keys.iter().map(partial_object_key_to_source_key).collect())
605        }
606    }
607}
608
609fn concat_path(prefix: &[PathSegment], seg: &PathSegment) -> Vec<PathSegment> {
610    let mut out = Vec::with_capacity(prefix.len() + 1);
611    out.extend_from_slice(prefix);
612    out.push(seg.clone());
613    out
614}