1use all_the_same::all_the_same;
4use derive_more::From;
5use serde::{Deserialize, Serialize};
6use tsify::Tsify;
7use uuid::Uuid;
8use wasm_bindgen::prelude::*;
9
10use catlog::dbl::model::{DblModel as _, DiscreteDblModel, FgDblModel, MutDblModel};
11use catlog::dbl::model_diagram as diagram;
12use catlog::dbl::model_morphism::DiscreteDblModelMapping;
13use catlog::one::FgCategory;
14use catlog::zero::{
15 Mapping, MutMapping, NameLookup, NameSegment, Namespace, QualifiedLabel, QualifiedName,
16};
17use notebook_types::current::*;
18
19use super::model::{DblModel, DblModelBox};
20use super::notation::*;
21use super::result::JsResult;
22use super::theory::DblTheory;
23
24#[derive(From)]
26pub enum DblModelDiagramBox {
27 Discrete(diagram::DblModelDiagram<DiscreteDblModelMapping, DiscreteDblModel>),
29 }
31
32#[wasm_bindgen]
34pub struct DblModelDiagram {
35 #[wasm_bindgen(skip)]
37 pub diagram: DblModelDiagramBox,
38 ob_namespace: Namespace,
39}
40
41impl DblModelDiagram {
42 pub fn new(theory: &DblTheory) -> Self {
44 let model = DblModel::new(theory);
45 let diagram = match model.model {
46 DblModelBox::Discrete(model) => {
47 let mapping = Default::default();
48 diagram::DblModelDiagram(mapping, model).into()
49 }
50 _ => panic!("Diagrams only implemented for discrete double theories"),
51 };
52 Self {
53 diagram,
54 ob_namespace: Namespace::new_for_uuid(),
55 }
56 }
57
58 pub fn add_ob(&mut self, decl: &DiagramObDecl) -> Result<(), String> {
60 all_the_same!(match &mut self.diagram {
61 DblModelDiagramBox::[Discrete](diagram) => {
62 let (mapping, model) = diagram.into();
63 let ob_type: QualifiedName = Elaborator.elab(&decl.ob_type)?;
64 if let Some(over) = decl.over.as_ref().map(|ob| Elaborator.elab(ob)).transpose()? {
65 mapping.assign_ob(decl.id.into(), over);
66 }
67 model.add_ob(decl.id.into(), ob_type);
68 }
69 });
70 if !decl.name.is_empty() {
71 self.ob_namespace.set_label(decl.id, decl.name.as_str().into());
72 }
73 Ok(())
74 }
75
76 pub fn add_mor(&mut self, decl: &DiagramMorDecl) -> Result<(), String> {
78 all_the_same!(match &mut self.diagram {
79 DblModelDiagramBox::[Discrete](diagram) => {
80 let (mapping, model) = diagram.into();
81 let mor_type = Elaborator.elab(&decl.mor_type)?;
82 model.make_mor(decl.id.into(), mor_type);
83 if let Some(dom) = decl.dom.as_ref().map(|ob| Elaborator.elab(ob)).transpose()? {
84 model.set_dom(decl.id.into(), dom);
85 }
86 if let Some(cod) = decl.cod.as_ref().map(|ob| Elaborator.elab(ob)).transpose()? {
87 model.set_cod(decl.id.into(), cod);
88 }
89 if let Some(over) = decl.over.as_ref().map(|mor| Elaborator.elab(mor)).transpose()? {
90 mapping.assign_mor(decl.id.into(), over);
91 }
92 }
93 });
94 if decl.name.is_empty() {
95 Ok(())
96 } else {
97 Err("Indexing morphisms in diagrams cannot be labeled".into())
99 }
100 }
101}
102
103#[wasm_bindgen]
104impl DblModelDiagram {
105 #[wasm_bindgen(js_name = "getDom")]
107 pub fn get_dom(&self, id: &QualifiedName) -> Option<Ob> {
108 all_the_same!(match &self.diagram {
109 DblModelDiagramBox::[Discrete](diagram) => {
110 let (_, model) = diagram.into();
111 model.get_dom(id).map(|ob| Quoter.quote(ob))
112 }
113 })
114 }
115
116 #[wasm_bindgen(js_name = "getCod")]
118 pub fn get_cod(&self, id: &QualifiedName) -> Option<Ob> {
119 all_the_same!(match &self.diagram {
120 DblModelDiagramBox::[Discrete](diagram) => {
121 let (_, model) = diagram.into();
122 model.get_cod(id).map(|ob| Quoter.quote(ob))
123 }
124 })
125 }
126
127 #[wasm_bindgen(js_name = "getObOver")]
129 pub fn get_ob_over(&self, id: &QualifiedName) -> Option<Ob> {
130 all_the_same!(match &self.diagram {
131 DblModelDiagramBox::[Discrete](diagram) => {
132 let (mapping, _) = diagram.into();
133 mapping.0.ob_generator_map.apply_to_ref(id).map(|ob| Quoter.quote(&ob))
134 }
135 })
136 }
137
138 #[wasm_bindgen(js_name = "getMorOver")]
140 pub fn get_mor_over(&self, id: &QualifiedName) -> Option<Mor> {
141 all_the_same!(match &self.diagram {
142 DblModelDiagramBox::[Discrete](diagram) => {
143 let (mapping, _) = diagram.into();
144 mapping.0.mor_generator_map.apply_to_ref(id).map(|mor| Quoter.quote(&mor))
145 }
146 })
147 }
148
149 #[wasm_bindgen(js_name = "obType")]
151 pub fn ob_type(&self, ob: Ob) -> Result<ObType, String> {
152 all_the_same!(match &self.diagram {
153 DblModelDiagramBox::[Discrete](diagram) => {
154 let (_, model) = diagram.into();
155 Ok(Quoter.quote(&model.ob_type(&Elaborator.elab(&ob)?)))
156 }
157 })
158 }
159
160 #[wasm_bindgen(js_name = "morType")]
162 pub fn mor_type(&self, mor: Mor) -> Result<MorType, String> {
163 all_the_same!(match &self.diagram {
164 DblModelDiagramBox::[Discrete](diagram) => {
165 let (_, model) = diagram.into();
166 Ok(Quoter.quote(&model.mor_type(&Elaborator.elab(&mor)?)))
167 }
168 })
169 }
170
171 #[wasm_bindgen(js_name = "obGenerators")]
173 pub fn ob_generators(&self) -> Vec<QualifiedName> {
174 let mut ob_gens: Vec<_> = all_the_same!(match &self.diagram {
175 DblModelDiagramBox::[Discrete](diagram) => {
176 let (_, model) = diagram.into();
177 model.ob_generators().collect()
178 }
179 });
180 ob_gens.sort();
181 ob_gens
182 }
183
184 #[wasm_bindgen(js_name = "morGenerators")]
186 pub fn mor_generators(&self) -> Vec<QualifiedName> {
187 let mut mor_gens: Vec<_> = all_the_same!(match &self.diagram {
188 DblModelDiagramBox::[Discrete](diagram) => {
189 let (_, model) = diagram.into();
190 model.mor_generators().collect()
191 }
192 });
193 mor_gens.sort();
194 mor_gens
195 }
196
197 #[wasm_bindgen(js_name = "obGeneratorsWithType")]
199 pub fn ob_generators_with_type(&self, ob_type: ObType) -> Result<Vec<QualifiedName>, String> {
200 all_the_same!(match &self.diagram {
201 DblModelDiagramBox::[Discrete](diagram) => {
202 let (_, model) = diagram.into();
203 let ob_type = Elaborator.elab(&ob_type)?;
204 Ok(model.ob_generators_with_type(&ob_type).collect())
205 }
206 })
207 }
208
209 #[wasm_bindgen(js_name = "morGeneratorsWithType")]
211 pub fn mor_generators_with_type(
212 &self,
213 mor_type: MorType,
214 ) -> Result<Vec<QualifiedName>, String> {
215 all_the_same!(match &self.diagram {
216 DblModelDiagramBox::[Discrete](diagram) => {
217 let (_, model) = diagram.into();
218 let mor_type = Elaborator.elab(&mor_type)?;
219 Ok(model.mor_generators_with_type(&mor_type).collect())
220 }
221 })
222 }
223
224 #[wasm_bindgen(js_name = "obGeneratorLabel")]
226 pub fn ob_generator_label(&self, id: &QualifiedName) -> Option<QualifiedLabel> {
227 self.ob_namespace.label(id)
228 }
229
230 #[wasm_bindgen(js_name = "obGeneratorWithLabel")]
232 pub fn ob_generator_with_label(&self, label: &QualifiedLabel) -> NameLookup {
233 self.ob_namespace.name_with_label(label)
234 }
235
236 #[wasm_bindgen]
238 pub fn judgments(&self) -> Vec<DiagramJudgment> {
239 let ob_decls = self.ob_declarations().into_iter().map(DiagramJudgment::Object);
240 let mor_decls = self.mor_declarations().into_iter().map(DiagramJudgment::Morphism);
241 ob_decls.chain(mor_decls).collect()
242 }
243
244 fn ob_declarations(&self) -> Vec<DiagramObDecl> {
246 all_the_same!(match &self.diagram {
247 DblModelDiagramBox::[Discrete](diagram) => {
248 let (mapping, model) = diagram.into();
249 let decls = model.ob_generators().map(|x| {
250 let maybe_label = self.ob_namespace.label(&x);
251 DiagramObDecl {
252 name: maybe_label.map(|label| label.to_string()).unwrap_or_default(),
253 id: expect_single_uuid(&x),
254 ob_type: Quoter.quote(&model.ob_generator_type(&x)),
255 over: mapping.0.ob_generator_map.get(&x).map(|ob| Quoter.quote(ob))
256 }
257 });
258 decls.collect()
259 }
260 })
261 }
262
263 fn mor_declarations(&self) -> Vec<DiagramMorDecl> {
265 all_the_same!(match &self.diagram {
266 DblModelDiagramBox::[Discrete](diagram) => {
267 let (mapping, model) = diagram.into();
268 let decls = model.mor_generators().map(|f| {
269 DiagramMorDecl {
270 name: "".into(),
271 id: expect_single_uuid(&f),
272 mor_type: Quoter.quote(&model.mor_generator_type(&f)),
273 over: mapping.0.mor_generator_map.get(&f).map(|mor| Quoter.quote(mor)),
274 dom: model.get_dom(&f).map(|ob| Quoter.quote(ob)),
275 cod: model.get_cod(&f).map(|ob| Quoter.quote(ob)),
276 }
277 });
278 decls.collect()
279 }
280 })
281 }
282
283 #[wasm_bindgen(js_name = "inferMissingFrom")]
285 pub fn infer_missing_from(&mut self, model: &DblModel) -> Result<(), String> {
286 all_the_same!(match &mut self.diagram {
287 DblModelDiagramBox::[Discrete](diagram) => {
288 let model = (&model.model).try_into().map_err(
289 |_| "Type of model should match type of diagram")?;
290 diagram.infer_missing_from(model);
291 }
292 });
293
294 let mut nanon = 0;
296 for id in self.ob_generators() {
297 if self.ob_namespace.label(&id).is_none() {
298 nanon += 1;
299 self.ob_namespace.set_label(expect_single_uuid(&id), nanon.into());
300 }
301 }
302
303 Ok(())
304 }
305
306 #[wasm_bindgen(js_name = "validateIn")]
308 pub fn validate_in(&self, model: &DblModel) -> Result<ModelDiagramValidationResult, String> {
309 let result = all_the_same!(match &self.diagram {
310 DblModelDiagramBox::[Discrete](diagram) => {
311 let model = (&model.model).try_into().map_err(
312 |_| "Type of model should match type of diagram")?;
313 diagram.validate_in(model)
314 }
315 });
316 Ok(ModelDiagramValidationResult(result.map_err(|errs| errs.into()).into()))
317 }
318}
319
320fn expect_single_uuid(name: &QualifiedName) -> Uuid {
323 match name.only() {
324 Some(NameSegment::Uuid(uuid)) => uuid,
325 _ => panic!("Only names that are single UUIDs are currently supported in notebook types"),
326 }
327}
328
329#[derive(Serialize, Deserialize, Tsify)]
331#[tsify(into_wasm_abi, from_wasm_abi)]
332pub struct ModelDiagramValidationResult(
333 pub JsResult<(), Vec<diagram::InvalidDiscreteDblModelDiagram>>,
334);
335
336#[wasm_bindgen(js_name = "elaborateDiagram")]
338pub fn elaborate_diagram(
339 judgments: Vec<DiagramJudgment>,
340 theory: &DblTheory,
341) -> Result<DblModelDiagram, String> {
342 let mut diagram = DblModelDiagram::new(theory);
343 for judgment in judgments {
344 match judgment {
345 DiagramJudgment::Object(decl) => diagram.add_ob(&decl)?,
346 DiagramJudgment::Morphism(decl) => diagram.add_mor(&decl)?,
347 }
348 }
349 Ok(diagram)
350}
351
352#[cfg(test)]
353mod tests {
354 use uuid::Uuid;
355
356 use super::*;
357 use crate::model::tests::sch_walking_attr;
358 use crate::theories::*;
359
360 #[test]
361 fn diagram_schema() {
362 let th = ThSchema::new().theory();
363 let [attr, entity, attr_type] = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
364 let model = sch_walking_attr(&th, [attr, entity, attr_type]);
365
366 let mut diagram = DblModelDiagram::new(&th);
367 let [x, y, var] = [Uuid::now_v7(), Uuid::now_v7(), Uuid::now_v7()];
368 assert!(
369 diagram
370 .add_ob(&DiagramObDecl {
371 name: "var".into(),
372 id: var,
373 ob_type: ObType::Basic("AttrType".into()),
374 over: Some(Ob::Basic(attr_type.to_string()))
375 })
376 .is_ok()
377 );
378 let [a, b] = [Uuid::now_v7(), Uuid::now_v7()];
379 for (name, indiv, f) in [("x", x, a), ("y", y, b)] {
380 assert!(
381 diagram
382 .add_ob(&DiagramObDecl {
383 name: name.into(),
384 id: indiv,
385 ob_type: ObType::Basic("Entity".into()),
386 over: Some(Ob::Basic(entity.to_string())),
387 })
388 .is_ok()
389 );
390 assert!(
391 diagram
392 .add_mor(&DiagramMorDecl {
393 name: "".into(),
394 id: f,
395 mor_type: MorType::Basic("Attr".into()),
396 dom: Some(Ob::Basic(indiv.to_string())),
397 cod: Some(Ob::Basic(var.to_string())),
398 over: Some(Mor::Basic(attr.to_string())),
399 })
400 .is_ok()
401 );
402 }
403 assert_eq!(diagram.get_dom(&a.into()), Some(Ob::Basic(x.into())));
404 assert_eq!(diagram.get_cod(&a.into()), Some(Ob::Basic(var.into())));
405 assert_eq!(diagram.get_ob_over(&x.into()), Some(Ob::Basic(entity.into())));
406 assert_eq!(diagram.get_mor_over(&a.into()), Some(Mor::Basic(attr.into())));
407 assert_eq!(diagram.ob_generator_label(&var.into()), Some("var".into()));
408 assert_eq!(diagram.ob_generator_with_label(&"var".into()), NameLookup::Unique(var.into()));
409 assert_eq!(diagram.ob_generators().len(), 3);
410 assert_eq!(diagram.mor_generators().len(), 2);
411 assert_eq!(diagram.judgments().len(), 5);
412 assert_eq!(diagram.validate_in(&model).unwrap().0, JsResult::Ok(()));
413 }
414}