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}