catlog/tt/
wd.rs

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