panproto_parse/parse_emit_protolens.rs
1//! The parse / decorate / emit lens packaged as a first-class
2//! [`Protolens`](panproto_lens::Protolens).
3//!
4//! For every registered grammar `G`, [`parse_emit_protolens()`] returns
5//! a [`Protolens`](panproto_lens::Protolens) whose source endofunctor strips the layout
6//! enrichment fibre (yielding an abstract schema) and whose target
7//! endofunctor adds it back via the registered
8//! [`LayoutEnricher`](panproto_lens::enrichment_registry::LayoutEnricher).
9//!
10//! ```text
11//! source theory F(S) = StripEnrichment(Layout)(S) -- the forgetful U
12//! target theory G(S) = AddEnrichment(Layout, π)(S) -- a section of U
13//! complement = layout fibre (per-vertex witness)
14//! ```
15//!
16//! ## What this protolens is, and is not
17//!
18//! The [`Protolens`](panproto_lens::Protolens) returned here is the **schema-level description**
19//! of the parse/decorate/emit relationship: it documents which
20//! constraint sorts belong to the layout fibre, which synthesis
21//! driver populates them, and what policy the put-direction uses.
22//! It composes with every other protolens in `panproto-lens` for
23//! reasoning about chain laws.
24//!
25//! The operational entry points for actually rendering or
26//! reconstructing source bytes are
27//! [`ParserRegistry::decorate`](crate::ParserRegistry::decorate),
28//! [`ParserRegistry::pretty_with_protocol`](crate::ParserRegistry::pretty_with_protocol),
29//! and [`ParserRegistry::emit_pretty_with_protocol`](crate::ParserRegistry::emit_pretty_with_protocol)
30//! — not [`Protolens::instantiate`](panproto_lens::Protolens::instantiate). The reason is that the parse-side
31//! walker invents fresh vertex IDs that the lens framework's
32//! `WInstance`-level get/put cannot align with the source schema's
33//! IDs; the underlying mismatch is intrinsic to grammars that
34//! consolidate tokens (e.g. lilypond's note pitches) at parse time.
35//! The schema-level descriptive content of the protolens remains
36//! useful for chain reasoning even when the lens framework's
37//! instance-level migration semantics are not the right fit.
38
39use std::sync::Arc;
40
41use panproto_gat::{EnrichmentKind, TheoryConstraint, TheoryEndofunctor, TheoryTransform};
42use panproto_lens::protolens::{ComplementConstructor, Protolens};
43
44use crate::layout_policy::{LayoutPolicy, policy_to_spec};
45
46/// Build a [`Protolens`] for the parse / decorate / emit relationship
47/// at `grammar` under the given `policy`.
48///
49/// The protolens's *target* transform invokes the registered
50/// [`LayoutEnricher`](panproto_lens::enrichment_registry::LayoutEnricher)
51/// — installed automatically by
52/// [`ParserRegistry::register`](crate::ParserRegistry::register) for
53/// every protocol it accepts. The *complement constructor* records
54/// that the discarded fibre is `EnrichmentKind::Layout` keyed by the
55/// grammar name.
56#[must_use]
57pub fn parse_emit_protolens(grammar: &str, policy: &LayoutPolicy) -> Protolens {
58 let enricher: Arc<str> = Arc::from(grammar);
59 Protolens {
60 name: panproto_gat::Name::from(format!("parse_emit/{grammar}")),
61 source: TheoryEndofunctor {
62 name: Arc::from("strip_layout"),
63 precondition: TheoryConstraint::Unconstrained,
64 transform: TheoryTransform::StripEnrichment(EnrichmentKind::Layout),
65 },
66 target: TheoryEndofunctor {
67 name: Arc::from(format!("decorate/{grammar}")),
68 precondition: TheoryConstraint::Unconstrained,
69 transform: TheoryTransform::AddEnrichment {
70 kind: EnrichmentKind::Layout,
71 enricher: Arc::clone(&enricher),
72 policy: policy_to_spec(policy),
73 },
74 },
75 complement_constructor: ComplementConstructor::Enrichment {
76 kind: EnrichmentKind::Layout,
77 enricher,
78 },
79 }
80}