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