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}