catlog/tt/
wd.rs

1//! Extract wiring diagrams from record types.
2
3use super::{eval::*, theory::*, toplevel::*, util::*, val::*};
4use crate::wd::UWD;
5use crate::zero::QualifiedName;
6
7/// Extracts an undirected wiring diagram from a record type.
8///
9/// Returns a UWD when the given type is a record; otherwise, returns `None`.
10/// The UWD has a box for each field that is itself of record type.
11///
12/// Because record types do not have an explicit notion of interface (say by
13/// distinguishing "public" and "private" fields), there is ambiguity about how
14/// to choose the interfaces (ports) for outer and inner boxes. Two approaches
15/// are reasonable. In the maximalist approach, *every* field of `Object` type
16/// contributes a port. In the minimalist approach, only those fields used in a
17/// specialization contribute a port. We take the minimalist approach because
18/// the purpose of this feature is to get a visual overview of a composition,
19/// which is best achieved by minimizing clutter.
20///
21/// A deeper problem is that specializations of a nested record type can refer
22/// to fields of arbitrary depth. In this function, any specializations more
23/// than one level deep are ignored. To capture these, one might look for a
24/// "nested UWD" data structure.
25pub fn record_to_uwd(ty: &TyV) -> Option<UWD<ObType, QualifiedName>> {
26    let TyV_::Record(record_v) = &**ty else {
27        return None;
28    };
29
30    let toplevel = Toplevel::default();
31    let eval = Evaluator::empty(&toplevel);
32    let (tm_n, eval) = eval.bind_self(ty.clone());
33    let tm_v = eval.eta_neu(&tm_n, ty);
34
35    let mut uwd = UWD::empty();
36
37    // First pass: add a box for each field that is itself of record type.
38    for (field_name, (field_label, _)) in record_v.fields.iter() {
39        let field_ty = eval.field_ty(ty, &tm_v, *field_name);
40        let TyV_::Record(r) = &&*field_ty else {
41            continue;
42        };
43        uwd.add_box(*field_name, *field_label);
44
45        // Add a port to the box for each specialization of the record type.
46        for (port_name, (port_label, entry)) in r.specializations.entries() {
47            let DtryEntry::File(spec_type) = entry else {
48                // Specialization is allowed at arbitrary depth, but only those at
49                // depth one can be expressed in a UWD.
50                continue;
51            };
52            let TyV_::Sing(ty, tm) = &**spec_type else {
53                continue;
54            };
55            let (TyV_::Object(ob_type), TmV_::Neu(n, _)) = (&**ty, &**tm) else {
56                continue;
57            };
58            let qual_name = n.to_qualified_name();
59            uwd.add_port(*field_name, *port_name, *port_label, ob_type.clone());
60            uwd.set(*field_name, *port_name, qual_name);
61        }
62    }
63
64    // Second pass: add further ports corresponding to fields that now exist as
65    // junctions, due to the first pass.
66    for (field_name, (field_label, _)) in record_v.fields.iter() {
67        let field_ty = eval.field_ty(ty, &tm_v, *field_name);
68        match &&*field_ty {
69            // Add outer port for each top-level field that is a junction.
70            TyV_::Object(ob_type) => {
71                let qual_name = QualifiedName::single(*field_name);
72                if uwd.has_junction(&qual_name) {
73                    uwd.add_outer_port(*field_name, *field_label, ob_type.clone());
74                    uwd.set_outer(*field_name, qual_name);
75                }
76            }
77            // Add port to box for each sub-field that is a junction.
78            TyV_::Record(r) => {
79                let tm_v = eval.proj(&tm_v, *field_name, *field_label);
80                for (port_name, (port_label, _)) in r.fields.iter() {
81                    if uwd.has_port(*field_name, *port_name) {
82                        continue;
83                    }
84                    let qual_name: QualifiedName = [*field_name, *port_name].into();
85                    if uwd.has_junction(&qual_name) {
86                        let port_ty = eval.field_ty(&field_ty, &tm_v, *port_name);
87                        let TyV_::Object(ob_type) = &*port_ty else {
88                            continue;
89                        };
90                        uwd.add_port(*field_name, *port_name, *port_label, ob_type.clone());
91                        uwd.set(*field_name, *port_name, qual_name);
92                    }
93                }
94            }
95            _ => {}
96        }
97    }
98
99    Some(uwd)
100}