diff --git a/packages/catlog-wasm/src/model.rs b/packages/catlog-wasm/src/model.rs index 7dae0666c..8ea21705c 100644 --- a/packages/catlog-wasm/src/model.rs +++ b/packages/catlog-wasm/src/model.rs @@ -321,7 +321,7 @@ pub struct DblModel { /// The elaborated type for the model. #[wasm_bindgen(skip)] - pub ty: Option<(tt::stx::TyS, tt::val::TyV)>, + pub ty: Option<(tt::stx::BaseTyS, tt::val::BaseTyV)>, /// The namespace for the objects. #[wasm_bindgen(skip)] diff --git a/packages/catlog/examples/tt/notebook/klausmeier/dec_model.json b/packages/catlog/examples/tt/notebook/klausmeier/dec_model.json new file mode 100644 index 000000000..5af4fe3da --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/dec_model.json @@ -0,0 +1,510 @@ +{ + "name": "DEC", + "notebook": { + "cellContents": { + "1a374345-df5f-5182-9c83-66b3c4ad1b8e": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "e8308841-9bc2-5bd9-8558-1d3fbd9a1071", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "add_d0d0", + "tag": "morphism" + }, + "id": "1a374345-df5f-5182-9c83-66b3c4ad1b8e", + "tag": "formal" + }, + "342172c2-69d5-53f2-b243-3d66e80b4fe6": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "8347d8cf-c755-523a-b260-db827bc05af7", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "mult_d0d0", + "tag": "morphism" + }, + "id": "342172c2-69d5-53f2-b243-3d66e80b4fe6", + "tag": "formal" + }, + "3b197e62-d1e4-5d2d-82e7-95c1a6a7704c": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "1184aae2-c2e9-5a8a-8c1b-c9adc42c3dd4", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "sub_d01", + "tag": "morphism" + }, + "id": "3b197e62-d1e4-5d2d-82e7-95c1a6a7704c", + "tag": "formal" + }, + "54a45aa8-9399-5927-8faa-4e549f01bbe8": { + "content": { + "cod": { + "content": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "883176d2-85a3-5047-a6cb-e1eaa6baafb8", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "partial_1", + "tag": "morphism" + }, + "id": "54a45aa8-9399-5927-8faa-4e549f01bbe8", + "tag": "formal" + }, + "5533d80f-74d3-5872-a210-19f5b87965c0": { + "content": { + "cod": { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "70fe0637-480c-5fd3-9b15-c8b0343ab5f9", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "partial_0", + "tag": "morphism" + }, + "id": "5533d80f-74d3-5872-a210-19f5b87965c0", + "tag": "formal" + }, + "607509ed-f2ef-599c-8f44-67e465a1b7c8": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "3bbb63e3-45cd-50a7-835e-200dbaf249d8", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "square_d0", + "tag": "morphism" + }, + "id": "607509ed-f2ef-599c-8f44-67e465a1b7c8", + "tag": "formal" + }, + "6ba914cf-1e47-5d29-b8fa-79cea7d7e1b8": { + "content": { + "cod": { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "707a689d-3dd0-5a6d-807d-0872d6f437ba", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "mult_00", + "tag": "morphism" + }, + "id": "6ba914cf-1e47-5d29-b8fa-79cea7d7e1b8", + "tag": "formal" + }, + "72fad82e-f2b6-53bc-a3e2-4560b18fdc22": { + "content": { + "id": "5b62880f-6702-5485-9605-78a964901750", + "name": "DualForm0", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "72fad82e-f2b6-53bc-a3e2-4560b18fdc22", + "tag": "formal" + }, + "73cf5adb-0b77-5019-a2e4-8238d1fd064b": { + "content": { + "id": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "name": "Form1", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "73cf5adb-0b77-5019-a2e4-8238d1fd064b", + "tag": "formal" + }, + "877c3499-d8e5-5551-a191-174d2f6ed0dd": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "55b1c6ab-28b3-5554-a387-237cea9a8db8", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "sub_d0d0", + "tag": "morphism" + }, + "id": "877c3499-d8e5-5551-a191-174d2f6ed0dd", + "tag": "formal" + }, + "8c0383fd-0b88-52e0-873a-f9f7814916a0": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "442d7941-0b1a-52d2-83ea-7566b3302d18", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "lapl_d0", + "tag": "morphism" + }, + "id": "8c0383fd-0b88-52e0-873a-f9f7814916a0", + "tag": "formal" + }, + "98c0911b-b628-5cb5-93d9-f516627e6245": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "3a4f308f-ba91-55af-b87d-629f48b206ec", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "partial_d0", + "tag": "morphism" + }, + "id": "98c0911b-b628-5cb5-93d9-f516627e6245", + "tag": "formal" + }, + "accfd5e9-9df2-549a-b6ff-d97445461dcb": { + "content": { + "cod": { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "03fdb89b-edd8-5013-9d67-b6ea37e2291c", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "wedge_00", + "tag": "morphism" + }, + "id": "accfd5e9-9df2-549a-b6ff-d97445461dcb", + "tag": "formal" + }, + "dbe66955-0b78-55e9-8654-dc3bc048bedd": { + "content": { + "cod": { + "content": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "tag": "Basic" + }, + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "7fd9ae1e-0dc8-5dcf-ba8c-09b3b9d799e6", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "wedge_10", + "tag": "morphism" + }, + "id": "dbe66955-0b78-55e9-8654-dc3bc048bedd", + "tag": "formal" + }, + "e4b5d611-b095-5e3a-b3e1-76a88b4becbe": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "tag": "Basic" + }, + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "15a851ab-9d76-568d-941b-f2141845e792", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "lie_1d0", + "tag": "morphism" + }, + "id": "e4b5d611-b095-5e3a-b3e1-76a88b4becbe", + "tag": "formal" + }, + "efca3f7e-c21e-5252-ab9d-5762ce0cef20": { + "content": { + "cod": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "dom": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "id": "3a1b19df-9345-585a-b673-93383680e0ff", + "morType": { + "content": "Multihom", + "tag": "Basic" + }, + "name": "mult_0d0", + "tag": "morphism" + }, + "id": "efca3f7e-c21e-5252-ab9d-5762ce0cef20", + "tag": "formal" + }, + "fddce294-699f-50d7-b13f-595870968434": { + "content": { + "id": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "name": "Form0", + "obType": { + "content": "Object", + "tag": "Basic" + }, + "tag": "object" + }, + "id": "fddce294-699f-50d7-b13f-595870968434", + "tag": "formal" + } + }, + "cellOrder": [ + "fddce294-699f-50d7-b13f-595870968434", + "73cf5adb-0b77-5019-a2e4-8238d1fd064b", + "72fad82e-f2b6-53bc-a3e2-4560b18fdc22", + "8c0383fd-0b88-52e0-873a-f9f7814916a0", + "5533d80f-74d3-5872-a210-19f5b87965c0", + "54a45aa8-9399-5927-8faa-4e549f01bbe8", + "98c0911b-b628-5cb5-93d9-f516627e6245", + "607509ed-f2ef-599c-8f44-67e465a1b7c8", + "1a374345-df5f-5182-9c83-66b3c4ad1b8e", + "3b197e62-d1e4-5d2d-82e7-95c1a6a7704c", + "877c3499-d8e5-5551-a191-174d2f6ed0dd", + "6ba914cf-1e47-5d29-b8fa-79cea7d7e1b8", + "342172c2-69d5-53f2-b243-3d66e80b4fe6", + "efca3f7e-c21e-5252-ab9d-5762ce0cef20", + "e4b5d611-b095-5e3a-b3e1-76a88b4becbe", + "accfd5e9-9df2-549a-b6ff-d97445461dcb", + "dbe66955-0b78-55e9-8654-dc3bc048bedd" + ] + }, + "theory": "multicategory", + "type": "model", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/hydrodynamics.json b/packages/catlog/examples/tt/notebook/klausmeier/hydrodynamics.json new file mode 100644 index 000000000..77aa672b6 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/hydrodynamics.json @@ -0,0 +1,457 @@ +{ + "instanceOf": { + "_id": "dec_model", + "_server": "catcolab.org", + "_version": null, + "type": "instance-of" + }, + "name": "Hydrodynamics", + "notebook": { + "cellContents": { + "106a6abd-e027-55d4-96ca-df15f575f482": { + "content": { + "id": "d91b49f2-5b1c-5419-b04f-62b74aabc526", + "lhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "ea1e412c-4f98-5826-ae11-73d616b455ae", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "3a4f308f-ba91-55af-b87d-629f48b206ec", + "tag": "Basic" + } + }, + "tag": "App" + }, + "name": "dt_w", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "b1c8325a-de26-5e0d-bf0e-0d9e7b1d6fbf", + "tag": "Generator" + }, + { + "content": "3ab01ae1-7f00-5109-b6bc-1d4e0d05a8a6", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "e8308841-9bc2-5bd9-8558-1d3fbd9a1071", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "106a6abd-e027-55d4-96ca-df15f575f482", + "tag": "formal" + }, + "1fe18bb5-699d-598f-bebf-f83df924759d": { + "content": { + "id": "e514acf8-4d3e-57e7-ad75-2d0244044889", + "name": "n", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "1fe18bb5-699d-598f-bebf-f83df924759d", + "tag": "formal" + }, + "20683357-ac91-5ef8-8015-5e3252f6ad65": { + "content": { + "id": "3e813cda-2117-5bdf-b792-3b2c8ed6cf2b", + "lhs": { + "content": "cd84f055-bf0d-5df7-bee6-d1ba424a25d1", + "tag": "Generator" + }, + "name": "x0", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "ea1e412c-4f98-5826-ae11-73d616b455ae", + "tag": "Generator" + }, + { + "content": "e4484d37-8168-5710-92f2-7763561cf694", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "1184aae2-c2e9-5a8a-8c1b-c9adc42c3dd4", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "20683357-ac91-5ef8-8015-5e3252f6ad65", + "tag": "formal" + }, + "32ddb0e8-1f7c-5e8f-bc61-7bce6ba40ed3": { + "content": { + "id": "cd84f055-bf0d-5df7-bee6-d1ba424a25d1", + "name": "x0", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "32ddb0e8-1f7c-5e8f-bc61-7bce6ba40ed3", + "tag": "formal" + }, + "3ce4f25a-ca15-5293-b421-8d993395ad74": { + "content": { + "id": "3bfa0def-532d-528e-b101-6785c6ceeefd", + "name": "x4", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "3ce4f25a-ca15-5293-b421-8d993395ad74", + "tag": "formal" + }, + "4eec6679-5ef8-5280-a3ec-4ba69b9edbb9": { + "content": { + "id": "36fa1566-b334-5517-929a-71ca3fb3f4f7", + "name": "dX", + "over": { + "content": "b20812f2-5c13-5b5b-b22e-4d6116b70cfb", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "4eec6679-5ef8-5280-a3ec-4ba69b9edbb9", + "tag": "formal" + }, + "626865e9-c3ca-57a3-9d2b-ebdb272f1e3b": { + "content": { + "id": "b5d25f13-4513-50d5-be7f-3d14b35a6536", + "name": "x1", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "626865e9-c3ca-57a3-9d2b-ebdb272f1e3b", + "tag": "formal" + }, + "64317784-ab2c-5a2c-b8a7-3a9f0398563d": { + "content": { + "id": "3ab01ae1-7f00-5109-b6bc-1d4e0d05a8a6", + "name": "x5", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "64317784-ab2c-5a2c-b8a7-3a9f0398563d", + "tag": "formal" + }, + "75e02274-df1b-52f6-a5b4-6ce1f440cf16": { + "content": { + "id": "a40b2b97-958b-5590-a1c4-ca4a4e2a1a48", + "lhs": { + "content": "3bfa0def-532d-528e-b101-6785c6ceeefd", + "tag": "Generator" + }, + "name": "x4", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "36fa1566-b334-5517-929a-71ca3fb3f4f7", + "tag": "Generator" + }, + { + "content": "ea1e412c-4f98-5826-ae11-73d616b455ae", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "15a851ab-9d76-568d-941b-f2141845e792", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "75e02274-df1b-52f6-a5b4-6ce1f440cf16", + "tag": "formal" + }, + "76ed6707-49e0-51c3-a7fc-596e15290107": { + "content": { + "id": "ea1e412c-4f98-5826-ae11-73d616b455ae", + "name": "w", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "76ed6707-49e0-51c3-a7fc-596e15290107", + "tag": "formal" + }, + "778c841a-5e5a-525a-84d8-2a4ce600c05b": { + "content": { + "id": "e7ea8f5a-1cee-5ed3-acd7-6cc2eacf4938", + "lhs": { + "content": "b1c8325a-de26-5e0d-bf0e-0d9e7b1d6fbf", + "tag": "Generator" + }, + "name": "x3", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "cd84f055-bf0d-5df7-bee6-d1ba424a25d1", + "tag": "Generator" + }, + { + "content": "d2f4cedc-9a9c-5dcb-9191-bbe22b19c047", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "55b1c6ab-28b3-5554-a387-237cea9a8db8", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "778c841a-5e5a-525a-84d8-2a4ce600c05b", + "tag": "formal" + }, + "794a91df-b9d8-5de6-9701-404bee687d42": { + "content": { + "id": "e4484d37-8168-5710-92f2-7763561cf694", + "name": "a", + "over": { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "794a91df-b9d8-5de6-9701-404bee687d42", + "tag": "formal" + }, + "921c7498-6735-5657-927d-d1e073833250": { + "content": { + "id": "d2f4cedc-9a9c-5dcb-9191-bbe22b19c047", + "name": "x2", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "921c7498-6735-5657-927d-d1e073833250", + "tag": "formal" + }, + "9bc3cde3-c0ce-553c-aca5-78db66ddc1ab": { + "content": { + "id": "32b7e9b6-3be2-5efc-837b-9ddcc294758f", + "name": "k", + "over": { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "9bc3cde3-c0ce-553c-aca5-78db66ddc1ab", + "tag": "formal" + }, + "aed296df-42b8-523c-93a1-8ead9b476bdb": { + "content": { + "id": "b1c8325a-de26-5e0d-bf0e-0d9e7b1d6fbf", + "name": "x3", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "aed296df-42b8-523c-93a1-8ead9b476bdb", + "tag": "formal" + }, + "c218d84b-1230-5240-8dc3-034107307805": { + "content": { + "id": "99131db7-a212-5332-ba6f-60c01b9b15e6", + "lhs": { + "content": "3ab01ae1-7f00-5109-b6bc-1d4e0d05a8a6", + "tag": "Generator" + }, + "name": "x5", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "32b7e9b6-3be2-5efc-837b-9ddcc294758f", + "tag": "Generator" + }, + { + "content": "3bfa0def-532d-528e-b101-6785c6ceeefd", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "3a1b19df-9345-585a-b673-93383680e0ff", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "c218d84b-1230-5240-8dc3-034107307805", + "tag": "formal" + }, + "daa0f4cb-b2a9-5bb7-93ba-2c509cc66b5d": { + "content": { + "id": "249b9f4b-757e-5172-b314-9882256ca9f9", + "lhs": { + "content": "b5d25f13-4513-50d5-be7f-3d14b35a6536", + "tag": "Generator" + }, + "name": "x1", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "e514acf8-4d3e-57e7-ad75-2d0244044889", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "3bbb63e3-45cd-50a7-835e-200dbaf249d8", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "daa0f4cb-b2a9-5bb7-93ba-2c509cc66b5d", + "tag": "formal" + }, + "f4c7a514-5705-5e31-9ec0-fd4c529e9184": { + "content": { + "id": "08301ffc-4f50-51de-a30a-bc5392735faa", + "lhs": { + "content": "d2f4cedc-9a9c-5dcb-9191-bbe22b19c047", + "tag": "Generator" + }, + "name": "x2", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "ea1e412c-4f98-5826-ae11-73d616b455ae", + "tag": "Generator" + }, + { + "content": "b5d25f13-4513-50d5-be7f-3d14b35a6536", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "8347d8cf-c755-523a-b260-db827bc05af7", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "f4c7a514-5705-5e31-9ec0-fd4c529e9184", + "tag": "formal" + } + }, + "cellOrder": [ + "794a91df-b9d8-5de6-9701-404bee687d42", + "9bc3cde3-c0ce-553c-aca5-78db66ddc1ab", + "4eec6679-5ef8-5280-a3ec-4ba69b9edbb9", + "76ed6707-49e0-51c3-a7fc-596e15290107", + "1fe18bb5-699d-598f-bebf-f83df924759d", + "32ddb0e8-1f7c-5e8f-bc61-7bce6ba40ed3", + "626865e9-c3ca-57a3-9d2b-ebdb272f1e3b", + "921c7498-6735-5657-927d-d1e073833250", + "aed296df-42b8-523c-93a1-8ead9b476bdb", + "3ce4f25a-ca15-5293-b421-8d993395ad74", + "64317784-ab2c-5a2c-b8a7-3a9f0398563d", + "20683357-ac91-5ef8-8015-5e3252f6ad65", + "daa0f4cb-b2a9-5bb7-93ba-2c509cc66b5d", + "f4c7a514-5705-5e31-9ec0-fd4c529e9184", + "778c841a-5e5a-525a-84d8-2a4ce600c05b", + "75e02274-df1b-52f6-a5b4-6ce1f440cf16", + "c218d84b-1230-5240-8dc3-034107307805", + "106a6abd-e027-55d4-96ca-df15f575f482" + ] + }, + "type": "instance", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/klausmeier.json b/packages/catlog/examples/tt/notebook/klausmeier/klausmeier.json new file mode 100644 index 000000000..a308b496b --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/klausmeier.json @@ -0,0 +1,85 @@ +{ + "instanceOf": { + "_id": "dec_model", + "_server": "catcolab.org", + "_version": null, + "type": "instance-of" + }, + "name": "Klausmeier", + "notebook": { + "cellContents": { + "04346fd8-836f-5f2d-ac8d-ed4c2db7de9d": { + "content": { + "id": "2efa1069-f315-5d31-bbed-4ac4bbafb42a", + "lhs": { + "content": "0ef9dd0c-e099-5897-b2bf-213af44f8f4a.e514acf8-4d3e-57e7-ad75-2d0244044889", + "tag": "Generator" + }, + "name": "glue_n", + "rhs": { + "content": "217c4878-87ad-560b-a992-c66db09212a2.471460c5-a2ee-5e44-8c57-8034ad370593", + "tag": "Generator" + }, + "tag": "equation" + }, + "id": "04346fd8-836f-5f2d-ac8d-ed4c2db7de9d", + "tag": "formal" + }, + "6f756822-6698-5ef3-b054-7e2142d4cd60": { + "content": { + "id": "0ef9dd0c-e099-5897-b2bf-213af44f8f4a", + "instance": { + "_id": "hydrodynamics", + "_server": "catcolab.org", + "_version": null, + "type": "instantiation" + }, + "name": "hydro", + "tag": "import" + }, + "id": "6f756822-6698-5ef3-b054-7e2142d4cd60", + "tag": "formal" + }, + "f3a29761-7bf0-50e6-b164-6a2d19ab981c": { + "content": { + "id": "217c4878-87ad-560b-a992-c66db09212a2", + "instance": { + "_id": "phytodynamics", + "_server": "catcolab.org", + "_version": null, + "type": "instantiation" + }, + "name": "phyto", + "tag": "import" + }, + "id": "f3a29761-7bf0-50e6-b164-6a2d19ab981c", + "tag": "formal" + }, + "fc199e1f-e2b2-59db-bdb8-0a35235315c7": { + "content": { + "id": "f4b19c42-0541-5050-9425-f7c4242ea2b9", + "lhs": { + "content": "0ef9dd0c-e099-5897-b2bf-213af44f8f4a.ea1e412c-4f98-5826-ae11-73d616b455ae", + "tag": "Generator" + }, + "name": "glue_w", + "rhs": { + "content": "217c4878-87ad-560b-a992-c66db09212a2.4bf26d23-ad3c-5cec-9c9a-d49c35bd0f64", + "tag": "Generator" + }, + "tag": "equation" + }, + "id": "fc199e1f-e2b2-59db-bdb8-0a35235315c7", + "tag": "formal" + } + }, + "cellOrder": [ + "6f756822-6698-5ef3-b054-7e2142d4cd60", + "f3a29761-7bf0-50e6-b164-6a2d19ab981c", + "04346fd8-836f-5f2d-ac8d-ed4c2db7de9d", + "fc199e1f-e2b2-59db-bdb8-0a35235315c7" + ] + }, + "type": "instance", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/klausmeier/phytodynamics.json b/packages/catlog/examples/tt/notebook/klausmeier/phytodynamics.json new file mode 100644 index 000000000..85fe0b532 --- /dev/null +++ b/packages/catlog/examples/tt/notebook/klausmeier/phytodynamics.json @@ -0,0 +1,372 @@ +{ + "instanceOf": { + "_id": "dec_model", + "_server": "catcolab.org", + "_version": null, + "type": "instance-of" + }, + "name": "Phytodynamics", + "notebook": { + "cellContents": { + "041949ed-aee8-57f4-ae2f-2a4051c4ec87": { + "content": { + "id": "4bf26d23-ad3c-5cec-9c9a-d49c35bd0f64", + "name": "w", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "041949ed-aee8-57f4-ae2f-2a4051c4ec87", + "tag": "formal" + }, + "53c2d96e-a94f-5f48-ae82-a1c47121b386": { + "content": { + "id": "41ef7859-fcee-55ac-ab8c-e2f4c4af0f2c", + "lhs": { + "content": "c1ef64cf-8968-5e41-8d64-40949d25daf2", + "tag": "Generator" + }, + "name": "y2", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "c46c610a-611e-5ebb-8276-304cd3ef5e99", + "tag": "Generator" + }, + { + "content": "471460c5-a2ee-5e44-8c57-8034ad370593", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "3a1b19df-9345-585a-b673-93383680e0ff", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "53c2d96e-a94f-5f48-ae82-a1c47121b386", + "tag": "formal" + }, + "7707cc47-3162-5b28-aa52-ba7cfff37008": { + "content": { + "id": "b93fbd14-b77c-507f-bac1-1908fc1cd224", + "lhs": { + "content": "ad814f83-f647-5a9e-983a-f6942e4790f3", + "tag": "Generator" + }, + "name": "y3", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "5d85d291-b5f7-510f-967d-c259b7cdcb63", + "tag": "Generator" + }, + { + "content": "c1ef64cf-8968-5e41-8d64-40949d25daf2", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "55b1c6ab-28b3-5554-a387-237cea9a8db8", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "7707cc47-3162-5b28-aa52-ba7cfff37008", + "tag": "formal" + }, + "8bec0a75-bca8-59a0-aa9c-122daddc5c94": { + "content": { + "id": "c1ef64cf-8968-5e41-8d64-40949d25daf2", + "name": "y2", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "8bec0a75-bca8-59a0-aa9c-122daddc5c94", + "tag": "formal" + }, + "a212d5e0-4c8e-5925-aa68-ed568075a1d9": { + "content": { + "id": "3c4fd918-4b55-5723-b3ee-81109a668daf", + "name": "y0", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "a212d5e0-4c8e-5925-aa68-ed568075a1d9", + "tag": "formal" + }, + "a5abff61-28bd-5239-a17d-038c85907d5a": { + "content": { + "id": "ad814f83-f647-5a9e-983a-f6942e4790f3", + "name": "y3", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "a5abff61-28bd-5239-a17d-038c85907d5a", + "tag": "formal" + }, + "b6faab41-0f79-514a-892d-2683651640c4": { + "content": { + "id": "db4d3b25-0fd0-5180-b315-31a4903712a0", + "lhs": { + "content": "5d85d291-b5f7-510f-967d-c259b7cdcb63", + "tag": "Generator" + }, + "name": "y1", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "4bf26d23-ad3c-5cec-9c9a-d49c35bd0f64", + "tag": "Generator" + }, + { + "content": "3c4fd918-4b55-5723-b3ee-81109a668daf", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "8347d8cf-c755-523a-b260-db827bc05af7", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "b6faab41-0f79-514a-892d-2683651640c4", + "tag": "formal" + }, + "bf6410f3-4f35-5749-a47a-91a6d3926da4": { + "content": { + "id": "b694063c-4d37-5992-8ca7-8c1648d40b16", + "name": "y4", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "bf6410f3-4f35-5749-a47a-91a6d3926da4", + "tag": "formal" + }, + "c7f92d49-62bb-5bc8-b8ba-c97eae09ca39": { + "content": { + "id": "5d85d291-b5f7-510f-967d-c259b7cdcb63", + "name": "y1", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "c7f92d49-62bb-5bc8-b8ba-c97eae09ca39", + "tag": "formal" + }, + "d3a94d9f-5605-5f66-ba85-db20ae529407": { + "content": { + "id": "2f4eb787-2df9-5469-9518-52308b1d1370", + "lhs": { + "content": "3c4fd918-4b55-5723-b3ee-81109a668daf", + "tag": "Generator" + }, + "name": "y0", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "471460c5-a2ee-5e44-8c57-8034ad370593", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "3bbb63e3-45cd-50a7-835e-200dbaf249d8", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "d3a94d9f-5605-5f66-ba85-db20ae529407", + "tag": "formal" + }, + "e3d8d25e-650d-5a79-b732-0d7a017580d1": { + "content": { + "id": "c6606495-3b90-56a4-b871-8ed684b123df", + "lhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "4bf26d23-ad3c-5cec-9c9a-d49c35bd0f64", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "3a4f308f-ba91-55af-b87d-629f48b206ec", + "tag": "Basic" + } + }, + "tag": "App" + }, + "name": "dt_w", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "ad814f83-f647-5a9e-983a-f6942e4790f3", + "tag": "Generator" + }, + { + "content": "b694063c-4d37-5992-8ca7-8c1648d40b16", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "e8308841-9bc2-5bd9-8558-1d3fbd9a1071", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "e3d8d25e-650d-5a79-b732-0d7a017580d1", + "tag": "formal" + }, + "e63d3629-4296-5dfc-a9a0-9a8cd6ef4f17": { + "content": { + "id": "471460c5-a2ee-5e44-8c57-8034ad370593", + "name": "n", + "over": { + "content": "5b62880f-6702-5485-9605-78a964901750", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "e63d3629-4296-5dfc-a9a0-9a8cd6ef4f17", + "tag": "formal" + }, + "e6882460-5932-5cbc-b34b-e6c82f5f4082": { + "content": { + "id": "b0a4c980-c481-5869-9dbd-1bf5d13a5bba", + "lhs": { + "content": "b694063c-4d37-5992-8ca7-8c1648d40b16", + "tag": "Generator" + }, + "name": "y4", + "rhs": { + "content": { + "arg": { + "content": { + "modality": "SymmetricList", + "terms": [ + { + "content": "471460c5-a2ee-5e44-8c57-8034ad370593", + "tag": "Generator" + } + ] + }, + "tag": "List" + }, + "mor": { + "content": "442d7941-0b1a-52d2-83ea-7566b3302d18", + "tag": "Basic" + } + }, + "tag": "App" + }, + "tag": "equation" + }, + "id": "e6882460-5932-5cbc-b34b-e6c82f5f4082", + "tag": "formal" + }, + "fff789e3-eca2-5013-9fe2-dea4b38480c2": { + "content": { + "id": "c46c610a-611e-5ebb-8276-304cd3ef5e99", + "name": "m", + "over": { + "content": "0b11fe33-a54c-5ce3-90b4-c6fb951beb92", + "tag": "Basic" + }, + "tag": "generator" + }, + "id": "fff789e3-eca2-5013-9fe2-dea4b38480c2", + "tag": "formal" + } + }, + "cellOrder": [ + "fff789e3-eca2-5013-9fe2-dea4b38480c2", + "e63d3629-4296-5dfc-a9a0-9a8cd6ef4f17", + "041949ed-aee8-57f4-ae2f-2a4051c4ec87", + "a212d5e0-4c8e-5925-aa68-ed568075a1d9", + "c7f92d49-62bb-5bc8-b8ba-c97eae09ca39", + "8bec0a75-bca8-59a0-aa9c-122daddc5c94", + "a5abff61-28bd-5239-a17d-038c85907d5a", + "bf6410f3-4f35-5749-a47a-91a6d3926da4", + "d3a94d9f-5605-5f66-ba85-db20ae529407", + "b6faab41-0f79-514a-892d-2683651640c4", + "53c2d96e-a94f-5f48-ae82-a1c47121b386", + "7707cc47-3162-5b28-aa52-ba7cfff37008", + "e6882460-5932-5cbc-b34b-e6c82f5f4082", + "e3d8d25e-650d-5a79-b732-0d7a017580d1" + ] + }, + "type": "instance", + "version": "2" +} diff --git a/packages/catlog/examples/tt/notebook/sch_weighted_graph.json b/packages/catlog/examples/tt/notebook/sch_weighted_graph.json index cd94e101d..426485da9 100644 --- a/packages/catlog/examples/tt/notebook/sch_weighted_graph.json +++ b/packages/catlog/examples/tt/notebook/sch_weighted_graph.json @@ -1 +1,108 @@ -{"name":"Weighted graph","notebook":{"cellContents":{"01997d96-c72a-72f8-be4c-f19e6f4873d2":{"tag":"formal","id":"01997d96-c72a-72f8-be4c-f19e6f4873d2","content":{"tag":"object","id":"01997d96-c72a-72f8-be4c-ec4490c264b7","name":"E","obType":{"tag":"Basic","content":"Entity"}}},"01997d96-d225-76ea-a1d2-bbcd290a586e":{"tag":"formal","id":"01997d96-d225-76ea-a1d2-bbcd290a586e","content":{"tag":"object","id":"01997d96-d224-71f8-80f2-b98ef8b2899a","name":"V","obType":{"tag":"Basic","content":"Entity"}}},"01997d96-e5e2-73df-8a3e-096efcc1e54e":{"tag":"formal","id":"01997d96-e5e2-73df-8a3e-096efcc1e54e","content":{"tag":"morphism","id":"01997d96-e5e1-743d-8bf7-8a8ad24f080e","name":"weight","morType":{"tag":"Basic","content":"Attr"},"dom":{"tag":"Basic","content":"01997d96-c72a-72f8-be4c-ec4490c264b7"},"cod":{"tag":"Basic","content":"01997d96-f476-745e-adbd-780d79ef5c31"}}},"01997d96-f476-745e-adbd-7ee1400d1674":{"tag":"formal","id":"01997d96-f476-745e-adbd-7ee1400d1674","content":{"tag":"object","id":"01997d96-f476-745e-adbd-780d79ef5c31","name":"Weight","obType":{"tag":"Basic","content":"AttrType"}}},"01997d97-3020-756c-9b2f-88513943dc39":{"tag":"formal","id":"01997d97-3020-756c-9b2f-88513943dc39","content":{"tag":"morphism","id":"01997d97-3020-756c-9b2f-85cbd3a4410c","name":"src","morType":{"tag":"Hom","content":{"tag":"Basic","content":"Entity"}},"dom":{"tag":"Basic","content":"01997d96-c72a-72f8-be4c-ec4490c264b7"},"cod":{"tag":"Basic","content":"01997d96-d224-71f8-80f2-b98ef8b2899a"}}},"01997d97-4d0d-73e1-82b0-27efdcea4d4b":{"tag":"formal","id":"01997d97-4d0d-73e1-82b0-27efdcea4d4b","content":{"tag":"morphism","id":"01997d97-4d0d-73e1-82b0-2384c982ec3b","name":"tgt","morType":{"tag":"Hom","content":{"tag":"Basic","content":"Entity"}},"dom":{"tag":"Basic","content":"01997d96-c72a-72f8-be4c-ec4490c264b7"},"cod":{"tag":"Basic","content":"01997d96-d224-71f8-80f2-b98ef8b2899a"}}}},"cellOrder":["01997d96-c72a-72f8-be4c-f19e6f4873d2","01997d96-d225-76ea-a1d2-bbcd290a586e","01997d96-f476-745e-adbd-7ee1400d1674","01997d96-e5e2-73df-8a3e-096efcc1e54e","01997d97-3020-756c-9b2f-88513943dc39","01997d97-4d0d-73e1-82b0-27efdcea4d4b"]},"theory":"simple-schema","type":"model","version":"1"} \ No newline at end of file +{ + "name": "Weighted graph", + "notebook": { + "cellContents": { + "01997d96-c72a-72f8-be4c-f19e6f4873d2": { + "tag": "formal", + "id": "01997d96-c72a-72f8-be4c-f19e6f4873d2", + "content": { + "tag": "object", + "id": "01997d96-c72a-72f8-be4c-ec4490c264b7", + "name": "E", + "obType": { "tag": "Basic", "content": "Entity" } + } + }, + "01997d96-d225-76ea-a1d2-bbcd290a586e": { + "tag": "formal", + "id": "01997d96-d225-76ea-a1d2-bbcd290a586e", + "content": { + "tag": "object", + "id": "01997d96-d224-71f8-80f2-b98ef8b2899a", + "name": "V", + "obType": { "tag": "Basic", "content": "Entity" } + } + }, + "01997d96-e5e2-73df-8a3e-096efcc1e54e": { + "tag": "formal", + "id": "01997d96-e5e2-73df-8a3e-096efcc1e54e", + "content": { + "tag": "morphism", + "id": "01997d96-e5e1-743d-8bf7-8a8ad24f080e", + "name": "weight", + "morType": { "tag": "Basic", "content": "Attr" }, + "dom": { + "tag": "Basic", + "content": "01997d96-c72a-72f8-be4c-ec4490c264b7" + }, + "cod": { + "tag": "Basic", + "content": "01997d96-f476-745e-adbd-780d79ef5c31" + } + } + }, + "01997d96-f476-745e-adbd-7ee1400d1674": { + "tag": "formal", + "id": "01997d96-f476-745e-adbd-7ee1400d1674", + "content": { + "tag": "object", + "id": "01997d96-f476-745e-adbd-780d79ef5c31", + "name": "Weight", + "obType": { "tag": "Basic", "content": "AttrType" } + } + }, + "01997d97-3020-756c-9b2f-88513943dc39": { + "tag": "formal", + "id": "01997d97-3020-756c-9b2f-88513943dc39", + "content": { + "tag": "morphism", + "id": "01997d97-3020-756c-9b2f-85cbd3a4410c", + "name": "src", + "morType": { + "tag": "Hom", + "content": { "tag": "Basic", "content": "Entity" } + }, + "dom": { + "tag": "Basic", + "content": "01997d96-c72a-72f8-be4c-ec4490c264b7" + }, + "cod": { + "tag": "Basic", + "content": "01997d96-d224-71f8-80f2-b98ef8b2899a" + } + } + }, + "01997d97-4d0d-73e1-82b0-27efdcea4d4b": { + "tag": "formal", + "id": "01997d97-4d0d-73e1-82b0-27efdcea4d4b", + "content": { + "tag": "morphism", + "id": "01997d97-4d0d-73e1-82b0-2384c982ec3b", + "name": "tgt", + "morType": { + "tag": "Hom", + "content": { "tag": "Basic", "content": "Entity" } + }, + "dom": { + "tag": "Basic", + "content": "01997d96-c72a-72f8-be4c-ec4490c264b7" + }, + "cod": { + "tag": "Basic", + "content": "01997d96-d224-71f8-80f2-b98ef8b2899a" + } + } + } + }, + "cellOrder": [ + "01997d96-c72a-72f8-be4c-f19e6f4873d2", + "01997d96-d225-76ea-a1d2-bbcd290a586e", + "01997d96-f476-745e-adbd-7ee1400d1674", + "01997d96-e5e2-73df-8a3e-096efcc1e54e", + "01997d97-3020-756c-9b2f-88513943dc39", + "01997d97-4d0d-73e1-82b0-27efdcea4d4b" + ] + }, + "theory": "simple-schema", + "type": "model", + "version": "1" +} diff --git a/packages/catlog/examples/tt/notebook/sir_petri.json b/packages/catlog/examples/tt/notebook/sir_petri.json index aea520347..448c83a2b 100644 --- a/packages/catlog/examples/tt/notebook/sir_petri.json +++ b/packages/catlog/examples/tt/notebook/sir_petri.json @@ -1 +1,153 @@ -{"name":"SIR","notebook":{"cellContents":{"019c34ca-5c0e-77af-88e3-c7ae9e1936cc":{"content":{"id":"019c34ca-5c0e-77af-88e3-c2d4487cadaf","name":"S","obType":{"content":"Object","tag":"Basic"},"tag":"object"},"id":"019c34ca-5c0e-77af-88e3-c7ae9e1936cc","tag":"formal"},"019c34ca-8090-7566-9db3-49f54b5d7eae":{"content":{"id":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","name":"I","obType":{"content":"Object","tag":"Basic"},"tag":"object"},"id":"019c34ca-8090-7566-9db3-49f54b5d7eae","tag":"formal"},"019c34ca-83b0-70f9-a861-663b422d3109":{"content":{"id":"019c34ca-83b0-70f9-a861-61ad91bdd5b6","name":"R","obType":{"content":"Object","tag":"Basic"},"tag":"object"},"id":"019c34ca-83b0-70f9-a861-663b422d3109","tag":"formal"},"019c34ca-92a8-702b-b09b-f711a18563b8":{"content":{"cod":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"},{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"dom":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-5c0e-77af-88e3-c2d4487cadaf","tag":"Basic"},{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"id":"019c34ca-92a8-702b-b09b-f303b34fddde","morType":{"content":{"content":"Object","tag":"Basic"},"tag":"Hom"},"name":"infect","tag":"morphism"},"id":"019c34ca-92a8-702b-b09b-f711a18563b8","tag":"formal"},"019c34ca-a808-7119-8682-626850b293e2":{"content":{"cod":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-83b0-70f9-a861-61ad91bdd5b6","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"dom":{"content":{"ob":{"content":{"modality":"SymmetricList","objects":[{"content":"019c34ca-8090-7566-9db3-45d5fd4f8ffd","tag":"Basic"}]},"tag":"List"},"op":{"content":"tensor","tag":"Basic"}},"tag":"App"},"id":"019c34ca-a808-7119-8682-5d964079256e","morType":{"content":{"content":"Object","tag":"Basic"},"tag":"Hom"},"name":"recover","tag":"morphism"},"id":"019c34ca-a808-7119-8682-626850b293e2","tag":"formal"}},"cellOrder":["019c34ca-5c0e-77af-88e3-c7ae9e1936cc","019c34ca-8090-7566-9db3-49f54b5d7eae","019c34ca-83b0-70f9-a861-663b422d3109","019c34ca-92a8-702b-b09b-f711a18563b8","019c34ca-a808-7119-8682-626850b293e2"]},"theory":"petri-net","type":"model","version":"1"} \ No newline at end of file +{ + "name": "SIR", + "notebook": { + "cellContents": { + "019c34ca-5c0e-77af-88e3-c7ae9e1936cc": { + "content": { + "id": "019c34ca-5c0e-77af-88e3-c2d4487cadaf", + "name": "S", + "obType": { "content": "Object", "tag": "Basic" }, + "tag": "object" + }, + "id": "019c34ca-5c0e-77af-88e3-c7ae9e1936cc", + "tag": "formal" + }, + "019c34ca-8090-7566-9db3-49f54b5d7eae": { + "content": { + "id": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "name": "I", + "obType": { "content": "Object", "tag": "Basic" }, + "tag": "object" + }, + "id": "019c34ca-8090-7566-9db3-49f54b5d7eae", + "tag": "formal" + }, + "019c34ca-83b0-70f9-a861-663b422d3109": { + "content": { + "id": "019c34ca-83b0-70f9-a861-61ad91bdd5b6", + "name": "R", + "obType": { "content": "Object", "tag": "Basic" }, + "tag": "object" + }, + "id": "019c34ca-83b0-70f9-a861-663b422d3109", + "tag": "formal" + }, + "019c34ca-92a8-702b-b09b-f711a18563b8": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + }, + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { "content": "tensor", "tag": "Basic" } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-5c0e-77af-88e3-c2d4487cadaf", + "tag": "Basic" + }, + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { "content": "tensor", "tag": "Basic" } + }, + "tag": "App" + }, + "id": "019c34ca-92a8-702b-b09b-f303b34fddde", + "morType": { + "content": { "content": "Object", "tag": "Basic" }, + "tag": "Hom" + }, + "name": "infect", + "tag": "morphism" + }, + "id": "019c34ca-92a8-702b-b09b-f711a18563b8", + "tag": "formal" + }, + "019c34ca-a808-7119-8682-626850b293e2": { + "content": { + "cod": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-83b0-70f9-a861-61ad91bdd5b6", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { "content": "tensor", "tag": "Basic" } + }, + "tag": "App" + }, + "dom": { + "content": { + "ob": { + "content": { + "modality": "SymmetricList", + "objects": [ + { + "content": "019c34ca-8090-7566-9db3-45d5fd4f8ffd", + "tag": "Basic" + } + ] + }, + "tag": "List" + }, + "op": { "content": "tensor", "tag": "Basic" } + }, + "tag": "App" + }, + "id": "019c34ca-a808-7119-8682-5d964079256e", + "morType": { + "content": { "content": "Object", "tag": "Basic" }, + "tag": "Hom" + }, + "name": "recover", + "tag": "morphism" + }, + "id": "019c34ca-a808-7119-8682-626850b293e2", + "tag": "formal" + } + }, + "cellOrder": [ + "019c34ca-5c0e-77af-88e3-c7ae9e1936cc", + "019c34ca-8090-7566-9db3-49f54b5d7eae", + "019c34ca-83b0-70f9-a861-663b422d3109", + "019c34ca-92a8-702b-b09b-f711a18563b8", + "019c34ca-a808-7119-8682-626850b293e2" + ] + }, + "theory": "petri-net", + "type": "model", + "version": "1" +} diff --git a/packages/catlog/examples/tt/text/test_base.dbltt b/packages/catlog/examples/tt/text/test_base.dbltt index ffcd774d1..9631e8118 100644 --- a/packages/catlog/examples/tt/text/test_base.dbltt +++ b/packages/catlog/examples/tt/text/test_base.dbltt @@ -1,8 +1,8 @@ set_theory ThCategory -type u := Unit +model u := Unit -type u2 := u +model u2 := u #/ The unique element of Unit def t : u := 'tt diff --git a/packages/catlog/examples/tt/text/test_base.dbltt.snapshot b/packages/catlog/examples/tt/text/test_base.dbltt.snapshot index f91eeace8..63b48b6b5 100644 --- a/packages/catlog/examples/tt/text/test_base.dbltt.snapshot +++ b/packages/catlog/examples/tt/text/test_base.dbltt.snapshot @@ -1,10 +1,10 @@ set_theory ThCategory #/ result: set theory to ThCategory -type u := Unit +model u := Unit #/ declared: u -type u2 := u +model u2 := u #/ declared: u2 def t : u := 'tt diff --git a/packages/catlog/examples/tt/text/test_discrete_theories.dbltt b/packages/catlog/examples/tt/text/test_discrete_theories.dbltt index a7aab4e0b..fafd9d927 100644 --- a/packages/catlog/examples/tt/text/test_discrete_theories.dbltt +++ b/packages/catlog/examples/tt/text/test_discrete_theories.dbltt @@ -1,6 +1,6 @@ set_theory ThSchema -type WeightedGraph := [ +model WeightedGraph := [ V : Entity, E : Entity, Weight : AttrType, @@ -11,13 +11,13 @@ type WeightedGraph := [ generate WeightedGraph -type EntityArr := [ +model EntityArr := [ dom : Entity, cod : Entity, arr : (Hom Entity)[dom, cod] ] -type Graph1 := [ +model Graph1 := [ V : Entity, E : Entity, src : EntityArr & [ .dom := E, .cod := V ], @@ -36,13 +36,13 @@ chk [G : Graph1] (G.src : EntityArr & [ .dom := G.V ] ) norm [x : [a : Entity, b : @sing a]] x.b #(should_fail) -type Graph1 := [ +model Graph1 := [ V : Entity, E : Entity, src : EntityArr & [ .dom := E, .dom := V ] ] -type Graph := [ +model Graph := [ V : Entity, E : Entity, src : (Hom Entity)[E, V], @@ -67,7 +67,7 @@ def reverse1[G : Graph] : Graph := [ norm [G : Graph] (reverse[G]).E syn [G : Graph] (reverse[G]).src -type ReflexiveGraph := [ +model ReflexiveGraph := [ V : Entity, E : Entity, src : (Hom Entity)[E, V], @@ -79,7 +79,7 @@ def edge_id[G : ReflexiveGraph] : (Hom Entity)[G.E, G.E] := @id G.E def edge_src[G : ReflexiveGraph] : (Hom Entity)[G.E, G.E] := G.src * G.refl -type Graph2 := [ +model Graph2 := [ V : Entity, g1 : Graph & [ .V := V], g2 : Graph & [ .V := V] @@ -91,7 +91,7 @@ norm [g : Graph2] g.g1.V generate Graph2 -type ProfunctorGraph := [ +model ProfunctorGraph := [ g1 : Graph, g2 : Graph, het : (Hom Entity)[g1.V, g2.V] @@ -99,7 +99,7 @@ type ProfunctorGraph := [ generate ProfunctorGraph -type WeightedGraph2 := [ +model WeightedGraph2 := [ V : Entity, g1 : WeightedGraph & [ .V := V ], g2 : WeightedGraph & [ .V := V ] @@ -110,22 +110,22 @@ generate WeightedGraph2 set_theory ThCategory #(should_fail) -type Set := [ +model Set := [ A : Entity, ] -type Set := [ +model Set := [ A : Object ] -type DDS := [ +model DDS := [ X : Object, φ : (Hom Object)[X, X] ] set_theory ThSignedCategory -type NegFeedback := [ +model NegFeedback := [ X : Object, Y : Object, f : (Hom Object)[X, Y], @@ -135,7 +135,7 @@ type NegFeedback := [ generate NegFeedback #(should_fail) -type NegFeedback1 := [ +model NegFeedback1 := [ X : Object, Y : Object, f : (Hom Object)[@hole, Y], diff --git a/packages/catlog/examples/tt/text/test_discrete_theories.dbltt.snapshot b/packages/catlog/examples/tt/text/test_discrete_theories.dbltt.snapshot index 7e9b26e3b..36470b8a2 100644 --- a/packages/catlog/examples/tt/text/test_discrete_theories.dbltt.snapshot +++ b/packages/catlog/examples/tt/text/test_discrete_theories.dbltt.snapshot @@ -1,7 +1,7 @@ set_theory ThSchema #/ result: set theory to ThSchema -type WeightedGraph := [ +model WeightedGraph := [ V : Entity, E : Entity, Weight : AttrType, @@ -20,14 +20,14 @@ generate WeightedGraph #/ tgt : E -> V : Hom Entity #/ weight : E -> Weight : Attr -type EntityArr := [ +model EntityArr := [ dom : Entity, cod : Entity, arr : (Hom Entity)[dom, cod] ] #/ declared: EntityArr -type Graph1 := [ +model Graph1 := [ V : Entity, E : Entity, src : EntityArr & [ .dom := E, .cod := V ], @@ -59,7 +59,7 @@ norm [x : [a : Entity, b : @sing a]] x.b #/ result: x.a #(should_fail) -type Graph1 := [ +model Graph1 := [ V : Entity, E : Entity, src : EntityArr & [ .dom := E, .dom := V ] @@ -73,7 +73,7 @@ type Graph1 := [ #/ 42| src : EntityArr & [ .dom := E, .dom := V ] #/ 42| ^^^^^^^^^ -type Graph := [ +model Graph := [ V : Entity, E : Entity, src : (Hom Entity)[E, V], @@ -115,7 +115,7 @@ norm [G : Graph] (reverse[G]).E syn [G : Graph] (reverse[G]).src #/ result: reverse[G].src : (Hom Entity)[G.E, G.V] -type ReflexiveGraph := [ +model ReflexiveGraph := [ V : Entity, E : Entity, src : (Hom Entity)[E, V], @@ -130,7 +130,7 @@ def edge_id[G : ReflexiveGraph] : (Hom Entity)[G.E, G.E] := @id G.E def edge_src[G : ReflexiveGraph] : (Hom Entity)[G.E, G.E] := G.src * G.refl #/ declared: edge_src -type Graph2 := [ +model Graph2 := [ V : Entity, g1 : Graph & [ .V := V], g2 : Graph & [ .V := V] @@ -153,7 +153,7 @@ generate Graph2 #/ g2.src : g2.E -> V : Hom Entity #/ g2.tgt : g2.E -> V : Hom Entity -type ProfunctorGraph := [ +model ProfunctorGraph := [ g1 : Graph, g2 : Graph, het : (Hom Entity)[g1.V, g2.V] @@ -172,7 +172,7 @@ generate ProfunctorGraph #/ g2.tgt : g2.E -> g2.V : Hom Entity #/ het : g1.V -> g2.V : Hom Entity -type WeightedGraph2 := [ +model WeightedGraph2 := [ V : Entity, g1 : WeightedGraph & [ .V := V ], g2 : WeightedGraph & [ .V := V ] @@ -197,7 +197,7 @@ set_theory ThCategory #/ result: set theory to ThCategory #(should_fail) -type Set := [ +model Set := [ A : Entity, ] #/ declared: Set @@ -207,12 +207,12 @@ type Set := [ #/ 114| A : Entity, #/ 114| ^^^^^^ -type Set := [ +model Set := [ A : Object ] #/ declared: Set -type DDS := [ +model DDS := [ X : Object, φ : (Hom Object)[X, X] ] @@ -221,7 +221,7 @@ type DDS := [ set_theory ThSignedCategory #/ result: set theory to ThSignedCategory -type NegFeedback := [ +model NegFeedback := [ X : Object, Y : Object, f : (Hom Object)[X, Y], @@ -237,7 +237,7 @@ generate NegFeedback #/ g : Y -> X : Negative #(should_fail) -type NegFeedback1 := [ +model NegFeedback1 := [ X : Object, Y : Object, f : (Hom Object)[@hole, Y], diff --git a/packages/catlog/examples/tt/text/test_equality.dbltt b/packages/catlog/examples/tt/text/test_equality.dbltt index 43c9004c7..e36b24b61 100644 --- a/packages/catlog/examples/tt/text/test_equality.dbltt +++ b/packages/catlog/examples/tt/text/test_equality.dbltt @@ -1,6 +1,6 @@ set_theory ThSchema -type CommutativeSquare := [ +model CommutativeSquare := [ NW : Entity, NE : Entity, SW : Entity, diff --git a/packages/catlog/examples/tt/text/test_equality.dbltt.snapshot b/packages/catlog/examples/tt/text/test_equality.dbltt.snapshot index 9bbb7b089..4ab53a6dd 100644 --- a/packages/catlog/examples/tt/text/test_equality.dbltt.snapshot +++ b/packages/catlog/examples/tt/text/test_equality.dbltt.snapshot @@ -1,7 +1,7 @@ set_theory ThSchema #/ result: set theory to ThSchema -type CommutativeSquare := [ +model CommutativeSquare := [ NW : Entity, NE : Entity, SW : Entity, diff --git a/packages/catlog/examples/tt/text/test_instances.dbltt b/packages/catlog/examples/tt/text/test_instances.dbltt new file mode 100644 index 000000000..99d7967ff --- /dev/null +++ b/packages/catlog/examples/tt/text/test_instances.dbltt @@ -0,0 +1,64 @@ +set_theory ThSchema + +model WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] + +instance WalkingEdge : WeightedGraph := [ + E := [e] +] + +instance I : WeightedGraph := [ + V := [v1, v2], + Weight := [w], + we : WalkingEdge, + wf : WalkingEdge, + src(we.e) := v1, + tgt(we.e) := v2, + src(wf.e) := v2, + tgt(wf.e) := v1, + weight(we.e) := w, + weight(wf.e) := w +] + +instance I2 : WeightedGraph := [ + V := [v1, v2], + Weight := [w], + we : WalkingEdge, + wf : WalkingEdge, + src := [we.e := v1, wf.e := v2], + tgt := [we.e := v2, wf.e := v1], + weight := [we.e := w, wf.e := w] +] + +instance I3 : WeightedGraph := [ + E := [e1, e2] +] + +instance SelfNamed : WeightedGraph := [ + V := [self, v2], + E := [e], + src(e) := self, + tgt(e) := v2 +] + +#(should_fail) +syn I.E + +model OtherSchema := [ + W : Entity +] + +instance OtherInst : OtherSchema := [ + W := [w0] +] + +#(should_fail) +instance MismatchedImport : WeightedGraph := [ + bad : OtherInst +] \ No newline at end of file diff --git a/packages/catlog/examples/tt/text/test_instances.dbltt.snapshot b/packages/catlog/examples/tt/text/test_instances.dbltt.snapshot new file mode 100644 index 000000000..5dd355161 --- /dev/null +++ b/packages/catlog/examples/tt/text/test_instances.dbltt.snapshot @@ -0,0 +1,127 @@ +set_theory ThSchema +#/ result: set theory to ThSchema + +model WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] +#/ declared: WeightedGraph + +instance WalkingEdge : WeightedGraph := [ + E := [e] +] +#/ declared: WalkingEdge +#/ instance generators: +#/ e : E + +instance I : WeightedGraph := [ + V := [v1, v2], + Weight := [w], + we : WalkingEdge, + wf : WalkingEdge, + src(we.e) := v1, + tgt(we.e) := v2, + src(wf.e) := v2, + tgt(wf.e) := v1, + weight(we.e) := w, + weight(wf.e) := w +] +#/ declared: I +#/ instance generators: +#/ v1 : V +#/ v2 : V +#/ w : Weight +#/ we.e : E +#/ wf.e : E +#/ instance equations: +#/ src(we.e) == v1 +#/ tgt(we.e) == v2 +#/ src(wf.e) == v2 +#/ tgt(wf.e) == v1 +#/ weight(we.e) == w +#/ weight(wf.e) == w + +instance I2 : WeightedGraph := [ + V := [v1, v2], + Weight := [w], + we : WalkingEdge, + wf : WalkingEdge, + src := [we.e := v1, wf.e := v2], + tgt := [we.e := v2, wf.e := v1], + weight := [we.e := w, wf.e := w] +] +#/ declared: I2 +#/ instance generators: +#/ v1 : V +#/ v2 : V +#/ w : Weight +#/ we.e : E +#/ wf.e : E +#/ instance equations: +#/ src(we.e) == v1 +#/ src(wf.e) == v2 +#/ tgt(we.e) == v2 +#/ tgt(wf.e) == v1 +#/ weight(we.e) == w +#/ weight(wf.e) == w + +instance I3 : WeightedGraph := [ + E := [e1, e2] +] +#/ declared: I3 +#/ instance generators: +#/ e1 : E +#/ e2 : E + +instance SelfNamed : WeightedGraph := [ + V := [self, v2], + E := [e], + src(e) := self, + tgt(e) := v2 +] +#/ declared: SelfNamed +#/ instance generators: +#/ self : V +#/ v2 : V +#/ e : E +#/ instance equations: +#/ src(e) == self +#/ tgt(e) == v2 + +#(should_fail) +syn I.E +#/ result: ?0 : ?1 +#/ expected errors: +#/ error[elab]: cannot project a field out of an instance; an instance is eliminated by mapping out of it, not by projection +#/ --> examples/tt/text/test_instances.dbltt:51:5 +#/ 51| syn I.E +#/ 51| ^^^ + +model OtherSchema := [ + W : Entity +] +#/ declared: OtherSchema + +instance OtherInst : OtherSchema := [ + W := [w0] +] +#/ declared: OtherInst +#/ instance generators: +#/ w0 : W + +#(should_fail) +instance MismatchedImport : WeightedGraph := [ + bad : OtherInst +] +#/ declared: MismatchedImport +#/ instance has no generators or equations +#/ expected errors: +#/ error[elab]: cannot import OtherInst: it is an instance of a different model than the enclosing instance +#/ --> examples/tt/text/test_instances.dbltt:63:5 +#/ 63| bad : OtherInst +#/ 63| ^^^^^^^^^^^^^^^ + diff --git a/packages/catlog/examples/tt/text/test_klausmeier.dbltt b/packages/catlog/examples/tt/text/test_klausmeier.dbltt new file mode 100644 index 000000000..c4cdd88bf --- /dev/null +++ b/packages/catlog/examples/tt/text/test_klausmeier.dbltt @@ -0,0 +1,87 @@ +set_theory ThMulticategory + +#/ The signature of a fragment of the discrete exterior calculus (DEC), as a +#/ multicategory presentation. Translated from Matt's notebook-elaborated +#/ Klausmeier examples (examples/tt/notebook/klausmeier). +model DEC := [ + Form0 : Object, + Form1 : Object, + DualForm0 : Object, + lapl_d0 : Multihom[[DualForm0], DualForm0], + partial_0 : Multihom[[Form0], Form0], + partial_1 : Multihom[[Form1], Form1], + partial_d0 : Multihom[[DualForm0], DualForm0], + square_d0 : Multihom[[DualForm0], DualForm0], + add_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + sub_d01 : Multihom[[DualForm0, Form0], DualForm0], + sub_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + mult_00 : Multihom[[Form0, Form0], Form0], + mult_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + mult_0d0 : Multihom[[Form0, DualForm0], DualForm0], + lie_1d0 : Multihom[[Form1, DualForm0], DualForm0], + wedge_00 : Multihom[[Form0, Form0], Form0], + wedge_10 : Multihom[[Form1, Form0], Form1] +] + +#/ Faithful rendering: named intermediate generators (one per notebook cell), +#/ each defined by a fiber equation, mirroring the diagram structure. + +#/ (n is a DualForm0 here -- Matt's draft mistakenly placed it in Form0, but +#/ square_d0/mult_d0d0 demand a DualForm0, and the Klausmeier gluing below +#/ equates it with Phytodynamics' DualForm0 n.) +instance Hydrodynamics : DEC := [ + Form0 := [a, k], + Form1 := [dX], + DualForm0 := [w, n, x0, x1, x2, x3, x4, x5], + x0 := sub_d01([w, a]), + x1 := square_d0([n]), + x2 := mult_d0d0([w, x1]), + x3 := sub_d0d0([x0, x2]), + x4 := lie_1d0([dX, w]), + x5 := mult_0d0([k, x4]), + partial_d0([w]) := add_d0d0([x3, x5]) +] + +instance Phytodynamics : DEC := [ + Form0 := [m], + DualForm0 := [n, w, y0, y1, y2, y3, y4], + y0 := square_d0([n]), + y1 := mult_d0d0([w, y0]), + y2 := mult_0d0([m, n]), + y3 := sub_d0d0([y1, y2]), + y4 := lapl_d0([n]), + #/ NOTE: Matt's draft binds partial_d0([w]) here; for the plant-biomass + #/ equation this is likely partial_d0([n]) -- confirm against the notebook. + partial_d0([w]) := add_d0d0([y3, y4]) +] + +#/ The full Klausmeier model glues the two sub-instances along their shared +#/ water (w) and plant-density (n) fields. +instance Klausmeier : DEC := [ + hydro : Hydrodynamics, + phyto : Phytodynamics, + hydro.n := phyto.n, + hydro.w := phyto.w +] + +#/ Inlined rendering: the same equations with intermediates substituted, +#/ exercising deeply-nested multihom applications. + +instance HydrodynamicsInline : DEC := [ + Form0 := [a, k], + Form1 := [dX], + DualForm0 := [w, n], + partial_d0([w]) := add_d0d0([ + sub_d0d0([sub_d01([w, a]), mult_d0d0([w, square_d0([n])])]), + mult_0d0([k, lie_1d0([dX, w])]) + ]) +] + +instance PhytodynamicsInline : DEC := [ + Form0 := [m], + DualForm0 := [n, w], + partial_d0([w]) := add_d0d0([ + sub_d0d0([mult_d0d0([w, square_d0([n])]), mult_0d0([m, n])]), + lapl_d0([n]) + ]) +] diff --git a/packages/catlog/examples/tt/text/test_klausmeier.dbltt.snapshot b/packages/catlog/examples/tt/text/test_klausmeier.dbltt.snapshot new file mode 100644 index 000000000..7a572c931 --- /dev/null +++ b/packages/catlog/examples/tt/text/test_klausmeier.dbltt.snapshot @@ -0,0 +1,167 @@ +set_theory ThMulticategory +#/ result: set theory to ThMulticategory + +model DEC := [ + Form0 : Object, + Form1 : Object, + DualForm0 : Object, + lapl_d0 : Multihom[[DualForm0], DualForm0], + partial_0 : Multihom[[Form0], Form0], + partial_1 : Multihom[[Form1], Form1], + partial_d0 : Multihom[[DualForm0], DualForm0], + square_d0 : Multihom[[DualForm0], DualForm0], + add_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + sub_d01 : Multihom[[DualForm0, Form0], DualForm0], + sub_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + mult_00 : Multihom[[Form0, Form0], Form0], + mult_d0d0 : Multihom[[DualForm0, DualForm0], DualForm0], + mult_0d0 : Multihom[[Form0, DualForm0], DualForm0], + lie_1d0 : Multihom[[Form1, DualForm0], DualForm0], + wedge_00 : Multihom[[Form0, Form0], Form0], + wedge_10 : Multihom[[Form1, Form0], Form1] +] +#/ declared: DEC + +instance Hydrodynamics : DEC := [ + Form0 := [a, k], + Form1 := [dX], + DualForm0 := [w, n, x0, x1, x2, x3, x4, x5], + x0 := sub_d01([w, a]), + x1 := square_d0([n]), + x2 := mult_d0d0([w, x1]), + x3 := sub_d0d0([x0, x2]), + x4 := lie_1d0([dX, w]), + x5 := mult_0d0([k, x4]), + partial_d0([w]) := add_d0d0([x3, x5]) +] +#/ declared: Hydrodynamics +#/ instance generators: +#/ a : Form0 +#/ k : Form0 +#/ dX : Form1 +#/ w : DualForm0 +#/ n : DualForm0 +#/ x0 : DualForm0 +#/ x1 : DualForm0 +#/ x2 : DualForm0 +#/ x3 : DualForm0 +#/ x4 : DualForm0 +#/ x5 : DualForm0 +#/ instance equations: +#/ x0 == sub_d01([w, a]) +#/ x1 == square_d0([n]) +#/ x2 == mult_d0d0([w, x1]) +#/ x3 == sub_d0d0([x0, x2]) +#/ x4 == lie_1d0([dX, w]) +#/ x5 == mult_0d0([k, x4]) +#/ partial_d0([w]) == add_d0d0([x3, x5]) + +instance Phytodynamics : DEC := [ + Form0 := [m], + DualForm0 := [n, w, y0, y1, y2, y3, y4], + y0 := square_d0([n]), + y1 := mult_d0d0([w, y0]), + y2 := mult_0d0([m, n]), + y3 := sub_d0d0([y1, y2]), + y4 := lapl_d0([n]), + #/ NOTE: Matt's draft binds partial_d0([w]) here; for the plant-biomass + #/ equation this is likely partial_d0([n]) -- confirm against the notebook. + partial_d0([w]) := add_d0d0([y3, y4]) +] +#/ declared: Phytodynamics +#/ instance generators: +#/ m : Form0 +#/ n : DualForm0 +#/ w : DualForm0 +#/ y0 : DualForm0 +#/ y1 : DualForm0 +#/ y2 : DualForm0 +#/ y3 : DualForm0 +#/ y4 : DualForm0 +#/ instance equations: +#/ y0 == square_d0([n]) +#/ y1 == mult_d0d0([w, y0]) +#/ y2 == mult_0d0([m, n]) +#/ y3 == sub_d0d0([y1, y2]) +#/ y4 == lapl_d0([n]) +#/ partial_d0([w]) == add_d0d0([y3, y4]) + +instance Klausmeier : DEC := [ + hydro : Hydrodynamics, + phyto : Phytodynamics, + hydro.n := phyto.n, + hydro.w := phyto.w +] +#/ declared: Klausmeier +#/ instance generators: +#/ hydro.a : Form0 +#/ hydro.k : Form0 +#/ hydro.dX : Form1 +#/ hydro.w : DualForm0 +#/ hydro.n : DualForm0 +#/ hydro.x0 : DualForm0 +#/ hydro.x1 : DualForm0 +#/ hydro.x2 : DualForm0 +#/ hydro.x3 : DualForm0 +#/ hydro.x4 : DualForm0 +#/ hydro.x5 : DualForm0 +#/ phyto.m : Form0 +#/ phyto.n : DualForm0 +#/ phyto.w : DualForm0 +#/ phyto.y0 : DualForm0 +#/ phyto.y1 : DualForm0 +#/ phyto.y2 : DualForm0 +#/ phyto.y3 : DualForm0 +#/ phyto.y4 : DualForm0 +#/ instance equations: +#/ hydro.x0 == sub_d01([hydro.w, hydro.a]) +#/ hydro.x1 == square_d0([hydro.n]) +#/ hydro.x2 == mult_d0d0([hydro.w, hydro.x1]) +#/ hydro.x3 == sub_d0d0([hydro.x0, hydro.x2]) +#/ hydro.x4 == lie_1d0([hydro.dX, hydro.w]) +#/ hydro.x5 == mult_0d0([hydro.k, hydro.x4]) +#/ partial_d0([hydro.w]) == add_d0d0([hydro.x3, hydro.x5]) +#/ phyto.y0 == square_d0([phyto.n]) +#/ phyto.y1 == mult_d0d0([phyto.w, phyto.y0]) +#/ phyto.y2 == mult_0d0([phyto.m, phyto.n]) +#/ phyto.y3 == sub_d0d0([phyto.y1, phyto.y2]) +#/ phyto.y4 == lapl_d0([phyto.n]) +#/ partial_d0([phyto.w]) == add_d0d0([phyto.y3, phyto.y4]) +#/ hydro.n == phyto.n +#/ hydro.w == phyto.w + +instance HydrodynamicsInline : DEC := [ + Form0 := [a, k], + Form1 := [dX], + DualForm0 := [w, n], + partial_d0([w]) := add_d0d0([ + sub_d0d0([sub_d01([w, a]), mult_d0d0([w, square_d0([n])])]), + mult_0d0([k, lie_1d0([dX, w])]) + ]) +] +#/ declared: HydrodynamicsInline +#/ instance generators: +#/ a : Form0 +#/ k : Form0 +#/ dX : Form1 +#/ w : DualForm0 +#/ n : DualForm0 +#/ instance equations: +#/ partial_d0([w]) == add_d0d0([sub_d0d0([sub_d01([w, a]), mult_d0d0([w, square_d0([n])])]), mult_0d0([k, lie_1d0([dX, w])])]) + +instance PhytodynamicsInline : DEC := [ + Form0 := [m], + DualForm0 := [n, w], + partial_d0([w]) := add_d0d0([ + sub_d0d0([mult_d0d0([w, square_d0([n])]), mult_0d0([m, n])]), + lapl_d0([n]) + ]) +] +#/ declared: PhytodynamicsInline +#/ instance generators: +#/ m : Form0 +#/ n : DualForm0 +#/ w : DualForm0 +#/ instance equations: +#/ partial_d0([w]) == add_d0d0([sub_d0d0([mult_d0d0([w, square_d0([n])]), mult_0d0([m, n])]), lapl_d0([n])]) + diff --git a/packages/catlog/examples/tt/text/test_modal_instances.dbltt b/packages/catlog/examples/tt/text/test_modal_instances.dbltt new file mode 100644 index 000000000..e32bef9ec --- /dev/null +++ b/packages/catlog/examples/tt/text/test_modal_instances.dbltt @@ -0,0 +1,83 @@ +set_theory ThMulticategory + +model SigMonoid := [ + M : Object, + op : Multihom[[M, M], M], + unit : Multihom[[], M] +] + +model SigRig := [ + R : Object, + Add : SigMonoid & [ .M := R ], + Mul : SigMonoid & [ .M := R ] +] + +instance Z2 : SigMonoid := [ + M := [x], + op([x,x]) := unit([]), + op([x,unit([])]) := x, + op([unit([]),x]) := x +] + +instance Z2freeZ2 : SigMonoid := [ + a : Z2, + b : Z2 +] + +instance klein : SigMonoid := [ + p : Z2freeZ2, + op([p.a.x,p.b.x]) := op([p.b.x,p.a.x]), + op([op([p.a.x,p.b.x]),op([p.a.x,p.b.x])]) := unit([]) +] + +instance Z2TheRig : SigRig := [ + Add.op([Mul.unit([]),Mul.unit([])]) := Add.unit([]), + Add.op([Mul.unit([]),Add.unit([])]) := Mul.unit([]), + Add.op([Add.unit([]),Mul.unit([])]) := Mul.unit([]), + Mul.op([Add.unit([]),Add.unit([])]) := Add.unit([]), + Mul.op([Mul.unit([]), Add.unit([])]) := Add.unit([]), + Mul.op([Add.unit([]), Mul.unit([])]) := Add.unit([]) +] + +set_theory ThSymMonoidalCategory + +model HMW := [ + H : Object, + M : Object, + W : Object, + returns_w : (Hom Object)[H, W], + returns_d : (Hom Object)[H, @tensor []], + fight_h : (Hom Object) [@tensor [H,M], H], + fight_m : (Hom Object) [@tensor [H,M], M], + fight_mm : (Hom Object)[@tensor [H, M], @tensor [M, M]] +] + +instance hm : HMW := [ + H := [hercules,odysseus,beowulf], + M := [hydra,polyphemus,grendel,dragon], + W := [megara, penelope], + fight_h(@tensor [odysseus,polyphemus]) := odysseus, + fight_h(@tensor [beowulf,grendel]) := beowulf, + fight_m(@tensor [beowulf,dragon]) := dragon, + fight_mm(@tensor [hercules,hydra]) := @tensor [hydra,hydra], + returns_w(hercules) := megara, + returns_w(odysseus) := penelope, + returns_d(beowulf) := @tensor [] +] +model Chain := [ + X : Object, + Y : Object, + s : (Hom Object)[X, Y], + t : (Hom Object)[@tensor [Y, Y], X] +] + +instance chain : Chain := [ + X := [x0], + t(@tensor [s(x0), s(x0)]) := x0 +] + +norm [chain] x0 + +norm [chain] s(x0) + +norm [chain] t(@tensor [s(x0), s(x0)]) diff --git a/packages/catlog/examples/tt/text/test_modal_instances.dbltt.snapshot b/packages/catlog/examples/tt/text/test_modal_instances.dbltt.snapshot new file mode 100644 index 000000000..0d29c57e9 --- /dev/null +++ b/packages/catlog/examples/tt/text/test_modal_instances.dbltt.snapshot @@ -0,0 +1,157 @@ +set_theory ThMulticategory +#/ result: set theory to ThMulticategory + +model SigMonoid := [ + M : Object, + op : Multihom[[M, M], M], + unit : Multihom[[], M] +] +#/ declared: SigMonoid + +model SigRig := [ + R : Object, + Add : SigMonoid & [ .M := R ], + Mul : SigMonoid & [ .M := R ] +] +#/ declared: SigRig + +instance Z2 : SigMonoid := [ + M := [x], + op([x,x]) := unit([]), + op([x,unit([])]) := x, + op([unit([]),x]) := x +] +#/ declared: Z2 +#/ instance generators: +#/ x : M +#/ instance equations: +#/ op([x, x]) == unit([]) +#/ op([x, unit([])]) == x +#/ op([unit([]), x]) == x + +instance Z2freeZ2 : SigMonoid := [ + a : Z2, + b : Z2 +] +#/ declared: Z2freeZ2 +#/ instance generators: +#/ a.x : M +#/ b.x : M +#/ instance equations: +#/ op([a.x, a.x]) == unit([]) +#/ op([a.x, unit([])]) == a.x +#/ op([unit([]), a.x]) == a.x +#/ op([b.x, b.x]) == unit([]) +#/ op([b.x, unit([])]) == b.x +#/ op([unit([]), b.x]) == b.x + +instance klein : SigMonoid := [ + p : Z2freeZ2, + op([p.a.x,p.b.x]) := op([p.b.x,p.a.x]), + op([op([p.a.x,p.b.x]),op([p.a.x,p.b.x])]) := unit([]) +] +#/ declared: klein +#/ instance generators: +#/ p.a.x : M +#/ p.b.x : M +#/ instance equations: +#/ op([p.a.x, p.a.x]) == unit([]) +#/ op([p.a.x, unit([])]) == p.a.x +#/ op([unit([]), p.a.x]) == p.a.x +#/ op([p.b.x, p.b.x]) == unit([]) +#/ op([p.b.x, unit([])]) == p.b.x +#/ op([unit([]), p.b.x]) == p.b.x +#/ op([p.a.x, p.b.x]) == op([p.b.x, p.a.x]) +#/ op([op([p.a.x, p.b.x]), op([p.a.x, p.b.x])]) == unit([]) + +instance Z2TheRig : SigRig := [ + Add.op([Mul.unit([]),Mul.unit([])]) := Add.unit([]), + Add.op([Mul.unit([]),Add.unit([])]) := Mul.unit([]), + Add.op([Add.unit([]),Mul.unit([])]) := Mul.unit([]), + Mul.op([Add.unit([]),Add.unit([])]) := Add.unit([]), + Mul.op([Mul.unit([]), Add.unit([])]) := Add.unit([]), + Mul.op([Add.unit([]), Mul.unit([])]) := Add.unit([]) +] +#/ declared: Z2TheRig +#/ instance equations: +#/ Add.op([Mul.unit([]), Mul.unit([])]) == Add.unit([]) +#/ Add.op([Mul.unit([]), Add.unit([])]) == Mul.unit([]) +#/ Add.op([Add.unit([]), Mul.unit([])]) == Mul.unit([]) +#/ Mul.op([Add.unit([]), Add.unit([])]) == Add.unit([]) +#/ Mul.op([Mul.unit([]), Add.unit([])]) == Add.unit([]) +#/ Mul.op([Add.unit([]), Mul.unit([])]) == Add.unit([]) + +set_theory ThSymMonoidalCategory +#/ result: set theory to ThSymMonoidalCategory + +model HMW := [ + H : Object, + M : Object, + W : Object, + returns_w : (Hom Object)[H, W], + returns_d : (Hom Object)[H, @tensor []], + fight_h : (Hom Object) [@tensor [H,M], H], + fight_m : (Hom Object) [@tensor [H,M], M], + fight_mm : (Hom Object)[@tensor [H, M], @tensor [M, M]] +] +#/ declared: HMW + +instance hm : HMW := [ + H := [hercules,odysseus,beowulf], + M := [hydra,polyphemus,grendel,dragon], + W := [megara, penelope], + fight_h(@tensor [odysseus,polyphemus]) := odysseus, + fight_h(@tensor [beowulf,grendel]) := beowulf, + fight_m(@tensor [beowulf,dragon]) := dragon, + fight_mm(@tensor [hercules,hydra]) := @tensor [hydra,hydra], + returns_w(hercules) := megara, + returns_w(odysseus) := penelope, + returns_d(beowulf) := @tensor [] +] +#/ declared: hm +#/ instance generators: +#/ hercules : H +#/ odysseus : H +#/ beowulf : H +#/ hydra : M +#/ polyphemus : M +#/ grendel : M +#/ dragon : M +#/ megara : W +#/ penelope : W +#/ instance equations: +#/ fight_h(@tensor [odysseus, polyphemus]) == odysseus +#/ fight_h(@tensor [beowulf, grendel]) == beowulf +#/ fight_m(@tensor [beowulf, dragon]) == dragon +#/ fight_mm(@tensor [hercules, hydra]) == @tensor [hydra, hydra] +#/ returns_w(hercules) == megara +#/ returns_w(odysseus) == penelope +#/ returns_d(beowulf) == @tensor [] + +model Chain := [ + X : Object, + Y : Object, + s : (Hom Object)[X, Y], + t : (Hom Object)[@tensor [Y, Y], X] +] +#/ declared: Chain + +instance chain : Chain := [ + X := [x0], + t(@tensor [s(x0), s(x0)]) := x0 +] +#/ declared: chain +#/ instance generators: +#/ x0 : X +#/ instance equations: +#/ t(@tensor [s(x0), s(x0)]) == x0 + +norm [chain] x0 +#/ result: x0 + +norm [chain] s(x0) +#/ result: s @ x0 + +norm [chain] t(@tensor [s(x0), s(x0)]) +#/ result: (@tensor [s, s] ; t) @ @tensor [x0, x0] + diff --git a/packages/catlog/examples/tt/text/test_modal_theories.dbltt b/packages/catlog/examples/tt/text/test_modal_theories.dbltt index 945873511..fcdf0f59d 100644 --- a/packages/catlog/examples/tt/text/test_modal_theories.dbltt +++ b/packages/catlog/examples/tt/text/test_modal_theories.dbltt @@ -1,6 +1,6 @@ set_theory ThMulticategory -type SigMonoid := [ +model SigMonoid := [ M : Object, op : Multihom[[M, M], M], unit : Multihom[[], M] @@ -8,7 +8,7 @@ type SigMonoid := [ generate SigMonoid -type SigRig := [ +model SigRig := [ R : Object, Add : SigMonoid & [ .M := R ], Mul : SigMonoid & [ .M := R ] @@ -18,7 +18,7 @@ generate SigRig set_theory ThSymMonoidalCategory -type SIR := [ +model SIR := [ S : Object, I : Object, R : Object, @@ -30,7 +30,7 @@ generate SIR syn [P: SIR] (@tensor [P.S, P.I]) -type SIRV := [ +model SIRV := [ Unvax : SIR, V : Object, vaccinate : (Hom Object)[Unvax.S, V] @@ -39,7 +39,7 @@ type SIRV := [ generate SIRV #(should_fail) -type BadOpApp := [ +model BadOpApp := [ X : Object, f : (Hom Object)[@tensor X, X], ] diff --git a/packages/catlog/examples/tt/text/test_modal_theories.dbltt.snapshot b/packages/catlog/examples/tt/text/test_modal_theories.dbltt.snapshot index 2e33aadfe..cf1f3cdf0 100644 --- a/packages/catlog/examples/tt/text/test_modal_theories.dbltt.snapshot +++ b/packages/catlog/examples/tt/text/test_modal_theories.dbltt.snapshot @@ -1,7 +1,7 @@ set_theory ThMulticategory #/ result: set theory to ThMulticategory -type SigMonoid := [ +model SigMonoid := [ M : Object, op : Multihom[[M, M], M], unit : Multihom[[], M] @@ -14,7 +14,7 @@ generate SigMonoid #/ op : [M, M] -> M : Multihom #/ unit : [] -> M : Multihom -type SigRig := [ +model SigRig := [ R : Object, Add : SigMonoid & [ .M := R ], Mul : SigMonoid & [ .M := R ] @@ -32,7 +32,7 @@ generate SigRig set_theory ThSymMonoidalCategory #/ result: set theory to ThSymMonoidalCategory -type SIR := [ +model SIR := [ S : Object, I : Object, R : Object, @@ -52,7 +52,7 @@ generate SIR syn [P: SIR] (@tensor [P.S, P.I]) #/ result: @tensor [P.S, P.I] : Object -type SIRV := [ +model SIRV := [ Unvax : SIR, V : Object, vaccinate : (Hom Object)[Unvax.S, V] @@ -70,7 +70,7 @@ generate SIRV #/ vaccinate : Unvax.S -> V : Hom Object #(should_fail) -type BadOpApp := [ +model BadOpApp := [ X : Object, f : (Hom Object)[@tensor X, X], ] diff --git a/packages/catlog/examples/tt/text/test_tabulators.dbltt b/packages/catlog/examples/tt/text/test_tabulators.dbltt index 0c21e395c..8031d3a8d 100644 --- a/packages/catlog/examples/tt/text/test_tabulators.dbltt +++ b/packages/catlog/examples/tt/text/test_tabulators.dbltt @@ -1,6 +1,6 @@ set_theory ThCategoryLinks -type SIR := [ +model SIR := [ S : Object, I : Object, R : Object, @@ -11,7 +11,7 @@ type SIR := [ generate SIR -type Endo := [ +model Endo := [ A : Object, f : (Hom Object)[A,A], ] @@ -19,12 +19,12 @@ type Endo := [ generate Endo #(should_fail) -type LinkFlows := [ +model LinkFlows := [ E : Endo, l : Link[@tab E.f,@tab E.f] ] -type WalkingLink := [ +model WalkingLink := [ x : Object, y : Object, z : Object, @@ -32,7 +32,7 @@ type WalkingLink := [ l : Link[z,@tab f] ] -type SI := [ +model SI := [ S : Object, I : Object, inf : WalkingLink & [.x := S, .y := I, .z := I] @@ -51,7 +51,7 @@ chk [sf : SI] (sf.inf : WalkingLink & [.x := sf.I]) def act_on_link[d : SI] : Link[d.S,@tab d.inf.f] := d.inf.f * d.inf.l -type Triangle := [ +model Triangle := [ x : Object, y : Object, z : Object, diff --git a/packages/catlog/examples/tt/text/test_tabulators.dbltt.snapshot b/packages/catlog/examples/tt/text/test_tabulators.dbltt.snapshot index b33a77fa5..6f9233caf 100644 --- a/packages/catlog/examples/tt/text/test_tabulators.dbltt.snapshot +++ b/packages/catlog/examples/tt/text/test_tabulators.dbltt.snapshot @@ -1,7 +1,7 @@ set_theory ThCategoryLinks #/ result: set theory to ThCategoryLinks -type SIR := [ +model SIR := [ S : Object, I : Object, R : Object, @@ -20,7 +20,7 @@ generate SIR #/ rec : I -> R : Hom Object #/ _ : I -> inf : Link -type Endo := [ +model Endo := [ A : Object, f : (Hom Object)[A,A], ] @@ -32,7 +32,7 @@ generate Endo #/ f : A -> A : Hom Object #(should_fail) -type LinkFlows := [ +model LinkFlows := [ E : Endo, l : Link[@tab E.f,@tab E.f] ] @@ -44,7 +44,7 @@ type LinkFlows := [ #/ 24| l : Link[@tab E.f,@tab E.f] #/ 24| ^^^^^^^^ -type WalkingLink := [ +model WalkingLink := [ x : Object, y : Object, z : Object, @@ -53,7 +53,7 @@ type WalkingLink := [ ] #/ declared: WalkingLink -type SI := [ +model SI := [ S : Object, I : Object, inf : WalkingLink & [.x := S, .y := I, .z := I] @@ -95,7 +95,7 @@ chk [sf : SI] (sf.inf : WalkingLink & [.x := sf.I]) def act_on_link[d : SI] : Link[d.S,@tab d.inf.f] := d.inf.f * d.inf.l #/ declared: act_on_link -type Triangle := [ +model Triangle := [ x : Object, y : Object, z : Object, diff --git a/packages/catlog/examples/tt/text/test_uwd.dbltt b/packages/catlog/examples/tt/text/test_uwd.dbltt index 5b117f41c..66900c271 100644 --- a/packages/catlog/examples/tt/text/test_uwd.dbltt +++ b/packages/catlog/examples/tt/text/test_uwd.dbltt @@ -1,13 +1,13 @@ set_theory ThSignedCategory -type PredPrey := [ +model PredPrey := [ Pred : Object, Prey : Object, eats : Negative[Pred, Prey], feeds : (Hom Object)[Prey, Pred] ] -type TwoLevelFoodChain := [ +model TwoLevelFoodChain := [ Grass : Object, Rabbit : Object, Fox : Object, @@ -17,7 +17,7 @@ type TwoLevelFoodChain := [ uwd TwoLevelFoodChain -type TwoPredSystem := [ +model TwoPredSystem := [ main: PredPrey, secondary: PredPrey & [ .Prey := main.Prey ] ] diff --git a/packages/catlog/examples/tt/text/test_uwd.dbltt.snapshot b/packages/catlog/examples/tt/text/test_uwd.dbltt.snapshot index 7020118e2..1bb72f461 100644 --- a/packages/catlog/examples/tt/text/test_uwd.dbltt.snapshot +++ b/packages/catlog/examples/tt/text/test_uwd.dbltt.snapshot @@ -1,7 +1,7 @@ set_theory ThSignedCategory #/ result: set theory to ThSignedCategory -type PredPrey := [ +model PredPrey := [ Pred : Object, Prey : Object, eats : Negative[Pred, Prey], @@ -9,7 +9,7 @@ type PredPrey := [ ] #/ declared: PredPrey -type TwoLevelFoodChain := [ +model TwoLevelFoodChain := [ Grass : Object, Rabbit : Object, Fox : Object, @@ -24,7 +24,7 @@ uwd TwoLevelFoodChain #/ level1 [Prey : Object := Grass, Pred : Object := Rabbit], #/ level2 [Prey : Object := Rabbit, Pred : Object := Fox] -type TwoPredSystem := [ +model TwoPredSystem := [ main: PredPrey, secondary: PredPrey & [ .Prey := main.Prey ] ] diff --git a/packages/catlog/src/dbl/discrete/mod.rs b/packages/catlog/src/dbl/discrete/mod.rs index 10636e856..4dd02d3f2 100644 --- a/packages/catlog/src/dbl/discrete/mod.rs +++ b/packages/catlog/src/dbl/discrete/mod.rs @@ -2,10 +2,12 @@ pub mod model; pub mod model_diagram; +pub mod model_instance; pub mod model_morphism; pub mod theory; pub use model::*; pub use model_diagram::*; +pub use model_instance::*; pub use model_morphism::*; pub use theory::*; diff --git a/packages/catlog/src/dbl/discrete/model.rs b/packages/catlog/src/dbl/discrete/model.rs index 15f6e374d..f81391147 100644 --- a/packages/catlog/src/dbl/discrete/model.rs +++ b/packages/catlog/src/dbl/discrete/model.rs @@ -7,8 +7,8 @@ use derivative::Derivative; use super::theory::DiscreteDblTheory; use crate::dbl::{category::*, model::*, theory::DblTheory}; use crate::one::{fp_category::QualifiedFpCategory, *}; -use crate::tt::util::pretty::*; use crate::validate::{self, Validate}; +use crate::zero::pretty::*; use crate::zero::*; /// A finitely presented model of a discrete double theory. diff --git a/packages/catlog/src/dbl/discrete/model_instance.rs b/packages/catlog/src/dbl/discrete/model_instance.rs new file mode 100644 index 000000000..2aed6f7dd --- /dev/null +++ b/packages/catlog/src/dbl/discrete/model_instance.rs @@ -0,0 +1,34 @@ +//! Instances of models of a discrete double theory. + +use crate::dbl::model_instance::{DblModelInstance, HasInstanceTerm, InstanceTerm}; +use crate::one::QualifiedPath; +use crate::zero::QualifiedName; + +use super::model::DiscreteDblModel; + +/// A term in an instance of a discrete double model: a model morphism +/// applied to a single instance generator. +/// +/// Composition of model morphisms is reflected inside [`path`](Self::path) +/// itself, not by nesting term constructors, so every term has the +/// flat canonical shape `path(base)`. When `path` is the identity, the +/// term denotes `base` directly; its `Id` vertex must agree with the +/// fiber of `base` in the surrounding instance. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DiscreteInstanceTerm { + /// Model morphism applied to `base`. + pub path: QualifiedPath, + /// The instance generator at the root of the term. + pub base: QualifiedName, +} + +impl InstanceTerm for DiscreteInstanceTerm { + type Mor = QualifiedPath; +} + +impl HasInstanceTerm for DiscreteDblModel { + type Term = DiscreteInstanceTerm; +} + +/// An instance of a model of a discrete double theory. +pub type DiscreteDblModelInstance = DblModelInstance; diff --git a/packages/catlog/src/dbl/discrete_tabulator/model.rs b/packages/catlog/src/dbl/discrete_tabulator/model.rs index 1c9b65744..bf8759596 100644 --- a/packages/catlog/src/dbl/discrete_tabulator/model.rs +++ b/packages/catlog/src/dbl/discrete_tabulator/model.rs @@ -7,8 +7,8 @@ use derive_more::From; use super::theory::*; use crate::dbl::{category::*, model::*, theory::DblTheory}; -use crate::tt::util::pretty::*; use crate::validate::{self, Validate}; +use crate::zero::pretty::*; use crate::{one::*, zero::*}; /// Object in a model of a discrete tabulator theory. diff --git a/packages/catlog/src/dbl/discrete_tabulator/theory.rs b/packages/catlog/src/dbl/discrete_tabulator/theory.rs index c276f76eb..ef105d195 100644 --- a/packages/catlog/src/dbl/discrete_tabulator/theory.rs +++ b/packages/catlog/src/dbl/discrete_tabulator/theory.rs @@ -8,7 +8,7 @@ use ref_cast::RefCast; use crate::dbl::{category::*, graph::ProedgeGraph, tree::DblTree}; use crate::one::{Graph, Path}; -use crate::tt::util::pretty::*; +use crate::zero::pretty::*; use crate::zero::*; /// Object type in a discrete tabulator theory. diff --git a/packages/catlog/src/dbl/mod.rs b/packages/catlog/src/dbl/mod.rs index 9bcde45ed..f4b17f42b 100644 --- a/packages/catlog/src/dbl/mod.rs +++ b/packages/catlog/src/dbl/mod.rs @@ -52,6 +52,7 @@ pub mod tree; pub mod model; pub mod model_diagram; +pub mod model_instance; pub mod model_morphism; pub mod theory; diff --git a/packages/catlog/src/dbl/modal/mod.rs b/packages/catlog/src/dbl/modal/mod.rs index fe527ba76..1fed83b2c 100644 --- a/packages/catlog/src/dbl/modal/mod.rs +++ b/packages/catlog/src/dbl/modal/mod.rs @@ -1,7 +1,9 @@ //! Doctrine of modal double theories. pub mod model; +pub mod model_instance; pub mod theory; pub use model::*; +pub use model_instance::*; pub use theory::*; diff --git a/packages/catlog/src/dbl/modal/model.rs b/packages/catlog/src/dbl/modal/model.rs index 11f3c9363..ebf71cbae 100644 --- a/packages/catlog/src/dbl/modal/model.rs +++ b/packages/catlog/src/dbl/modal/model.rs @@ -12,8 +12,8 @@ use ref_cast::RefCast; use super::theory::*; use crate::dbl::theory::DblTheoryKind; use crate::dbl::{graph::VDblGraph, model::*, theory::DblTheory}; -use crate::tt::util::pretty::*; use crate::validate::{self, Validate}; +use crate::zero::pretty::*; use crate::{one::computad::*, one::*, zero::*}; /// Object in a model of a modal double theory. diff --git a/packages/catlog/src/dbl/modal/model_instance.rs b/packages/catlog/src/dbl/modal/model_instance.rs new file mode 100644 index 000000000..9f34b0ea3 --- /dev/null +++ b/packages/catlog/src/dbl/modal/model_instance.rs @@ -0,0 +1,82 @@ +//! Instances of models of a modal double theory. + +use crate::dbl::model_instance::{DblModelInstance, HasInstanceTerm, InstanceTerm}; +use crate::dbl::theory::DblTheoryKind; +use crate::one::path::Path; +use crate::zero::QualifiedName; + +use super::model::{ModalDblModel, ModalMor, ModalOb}; +use super::theory::List; + +/// A term in an instance of a modal double model: a single model morphism +/// applied to a base built from instance generators. +/// +/// As in the discrete case, composition of model morphisms is reflected +/// inside [`mor`](Self::mor) itself — via [`ModalMor::Composite`] for +/// sequential composition and [`ModalMor::List`] for list-tupling — rather +/// than by nesting term constructors. A tree-shaped multicategory composite +/// such as `f([g(x), y])` therefore normalizes to *one* [`ModalMor`] applied +/// *once* to a base of bare generators; applications never nest. When `mor` +/// is the identity (`ModalMor::Composite(Path::Id(ob))`), the term denotes +/// `base` directly, and that `Id` object must agree with the fiber of `base` +/// in the surrounding instance. +/// +/// The only recursion that survives lives in [`base`](Self::base), through +/// [`ModalInstanceBase::List`] (mirroring [`ModalOb::List`]) and +/// [`ModalInstanceBase::ObApp`] (mirroring [`ModalOb::App`]): this is what +/// lets a generator over a nested list object be written inline as e.g. +/// `[x, y]`, or an element of a product object as `@tensor [x, y]`. A base +/// holds only generators, lists, and object-operation applications — never a +/// morphism — so the "no application inside an application" invariant is +/// enforced structurally. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ModalInstanceTerm { + /// Model morphism applied to `base`. + pub mor: ModalMor, + /// The base of instance generators at the root of the term. + pub base: ModalInstanceBase, +} + +/// The base of a [`ModalInstanceTerm`]: instance generators, tupled into +/// lists to match list-shaped fibers. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ModalInstanceBase { + /// A single instance generator. + Generator(QualifiedName), + /// A list of bases in a [list modality](List), living over a + /// [list object](super::model::ModalOb::List). + List(List, Vec), + /// An object operation applied to a base, e.g. `@tensor [x, y]`, living + /// over an [object-operation application](super::model::ModalOb::App). + /// Object operations are functorial actions on objects, not morphisms, so + /// this stays in the base rather than in the term's morphism. + ObApp(QualifiedName, Box), +} + +impl InstanceTerm for ModalInstanceTerm { + type Mor = ModalMor; +} + +impl HasInstanceTerm for ModalDblModel { + type Term = ModalInstanceTerm; +} + +/// An instance of a model of a modal double theory. +pub type ModalDblModelInstance = DblModelInstance>; + +/// If `mor` is an identity morphism (`Composite(Path::Id(ob))`), returns the +/// object it is the identity on. +/// +/// Instance-term normalization uses this to keep terms in their flat normal +/// form: an identity `mor` means the term denotes its [`base`](ModalInstanceTerm::base) +/// directly, so a nested application whose argument is a pure base of +/// generators need not introduce a `Composite`/`List` wrapper. +pub fn modal_mor_as_identity(mor: &ModalMor) -> Option<&ModalOb> { + match mor { + ModalMor::Composite(path) => match path.as_ref() { + Path::Id(ob) => Some(ob), + _ => None, + }, + _ => None, + } +} diff --git a/packages/catlog/src/dbl/model.rs b/packages/catlog/src/dbl/model.rs index aa087b97f..f859e1b00 100644 --- a/packages/catlog/src/dbl/model.rs +++ b/packages/catlog/src/dbl/model.rs @@ -43,7 +43,7 @@ use tsify::Tsify; use super::theory::DblTheory; use crate::one::{Category, FgCategory, InvalidPathEq, Path}; -use crate::tt::util::pretty::*; +use crate::zero::pretty::*; use crate::zero::{Namespace, QualifiedName}; pub use super::discrete::model::*; @@ -308,6 +308,18 @@ pub enum InvalidDblModel { /// No link provided for instantiation cell, or wrong type of link. InvalidLink(QualifiedName), + + /// Reference to an undefined generator or import in an instance term. + FiberElement(QualifiedName), + + /// Instance term has an invalid fiber type: an application to an + /// argument over the wrong object, or an equation between elements + /// with inconvertible fiber types. + FiberType(QualifiedName), + + /// Imported instance has a codomain model different from the enclosing + /// instance's. + ImportCodomain(QualifiedName), } /// A failure of an equation in a model of a double theory to be well defined. @@ -355,4 +367,7 @@ pub enum Feature { ComplexMorType, /// Equation between one or more undefined morphisms. PartialEquation, + /// Application of a composite morphism in an instance term. Nested + /// applications express the same thing. + CompositeApplication, } diff --git a/packages/catlog/src/dbl/model_instance.rs b/packages/catlog/src/dbl/model_instance.rs new file mode 100644 index 000000000..ff8748c59 --- /dev/null +++ b/packages/catlog/src/dbl/model_instance.rs @@ -0,0 +1,101 @@ +//! Instances of models of a double theory. +//! +//! An **instance** of a model (see [Carlson-Patterson 2025](https://arxiv.org/abs/2510.08861)) +//! is here presented via +//! a set of generators living over each object of the model, together with +//! equations between terms built from morphisms of the model applied to +//! those generators. Crucially, the +//! lift targets and lift morphisms forced by the discrete-opfibration +//! condition may be used in equations but are *not* materialized as explicit generators. +//! +//! The term language is left to each doctrine to define, via the +//! [`InstanceTerm`] trait and the [`HasInstanceTerm`] extension on +//! [`DblModel`]. Discrete doctrines need only bare generators +//! and morphism applications; modal doctrines additionally allow list +//! terms to feed list-shaped morphism domains. +//! +//! For the related but distinct notion of a *diagram* in a model — a +//! morphism into the model from a free model — see +//! [`model_diagram`](super::model_diagram). + +use std::rc::Rc; + +use super::model::DblModel; +use crate::zero::{Column, HashColumn, MutMapping, QualifiedName}; + +/// A term in the language of an instance of some model. +/// +/// Each doctrine implements its own concrete term type. The associated +/// [`Mor`](Self::Mor) type ties the term language to a particular +/// model's morphism type. +pub trait InstanceTerm { + /// The type of morphisms from the associated model. + type Mor; +} + +/// A [`DblModel`] that has an associated term language for instances. +/// +/// Each doctrine that wants to support [`DblModelInstance`] declares its +/// term type here. +pub trait HasInstanceTerm: DblModel { + /// The kind of term used to express equations in instances of this + /// model. + type Term: InstanceTerm; +} + +/// An instance of a model: a fibered set of generators plus equations +/// between terms in the model's instance-term language. +/// +/// Owns the generator-to-fiber assignment and the equations, but does +/// not own the model itself (held behind an [`Rc`], matching how models +/// reference their theories). +pub struct DblModelInstance { + model: Rc, + /// For each instance generator, the model object it lives over. + /// Multiple generators may share a fiber. + fibers: HashColumn, + /// Equations between terms, asserted to hold in this instance. + equations: Vec<(M::Term, M::Term)>, +} + +impl DblModelInstance { + /// Creates an empty instance over the given model. + pub fn new(model: Rc) -> Self { + Self { + model, + fibers: Default::default(), + equations: Vec::new(), + } + } + + /// The model this is an instance of. + pub fn model(&self) -> &Rc { + &self.model + } + + /// Adds a generator living over the given object of the model. + pub fn add_generator(&mut self, name: QualifiedName, fiber: M::Ob) { + self.fibers.set(name, fiber); + } + + /// The model object that `name` lives over, if `name` is a generator + /// of this instance. + pub fn fiber_of(&self, name: &QualifiedName) -> Option<&M::Ob> { + self.fibers.get(name) + } + + /// Iterates over the instance generators and their fibers. + pub fn generators(&self) -> impl Iterator { + self.fibers.iter() + } + + /// Adds an equation between two terms. + pub fn add_equation(&mut self, lhs: M::Term, rhs: M::Term) { + self.equations.push((lhs, rhs)); + } + + /// Iterates over the equations of this instance. + pub fn equations(&self) -> impl Iterator { + self.equations.iter() + } +} diff --git a/packages/catlog/src/tt/batch.rs b/packages/catlog/src/tt/batch.rs index 42be88292..51ff0f84e 100644 --- a/packages/catlog/src/tt/batch.rs +++ b/packages/catlog/src/tt/batch.rs @@ -11,8 +11,19 @@ use scopeguard::guard; use tattle::display::SourceInfo; use tattle::{Reporter, declare_error}; -use super::{text_elab::*, theory::std_theories, toplevel::*}; -use crate::zero::NameSegment; +use super::{ + modelgen::{ModelInstance, instance_from_def}, + text_elab::*, + theory::std_theories, + toplevel::*, +}; +use crate::dbl::discrete::DiscreteInstanceTerm; +use crate::dbl::modal::{ + ModalInstanceBase, ModalInstanceTerm, ModalMor, ModalOb, modal_mor_as_identity, +}; +use crate::dbl::model_instance::{DblModelInstance, HasInstanceTerm}; +use crate::one::path::Path; +use crate::zero::{NameSegment, Namespace}; declare_error!(TOP_ERROR, "top", "an error at the top-level"); @@ -64,6 +75,41 @@ impl BatchOutput { } } + fn instance_summary(&self, instance: &ModelInstance, ns: &Namespace) { + if let BatchOutput::Snapshot(out) = self { + let mut out = out.borrow_mut(); + match instance { + ModelInstance::Discrete(instance) => write_instance_summary( + &mut out, + instance, + ns, + |fiber| ns.label_string(fiber), + |tm| format_instance_term(tm, ns), + ), + ModelInstance::ModalUnital(instance) => write_instance_summary( + &mut out, + instance, + ns, + |ob| format_modal_ob(ob, ns), + |tm| format_modal_instance_term(tm, ns), + ), + ModelInstance::ModalNonUnital(instance) => write_instance_summary( + &mut out, + instance, + ns, + |ob| format_modal_ob(ob, ns), + |tm| format_modal_instance_term(tm, ns), + ), + } + } + } + + fn instance_error(&self, msg: &str) { + if let BatchOutput::Snapshot(out) = self { + writeln!(out.borrow_mut(), "#/ instance generation failed: {msg}").unwrap(); + } + } + fn got_result(&self, result: &str) { match self { BatchOutput::Snapshot(out) => { @@ -179,8 +225,18 @@ pub fn elaborate(src: &str, path: &str, output: &BatchOutput) -> io::Result { + let is_instance = matches!(&top_decl, TopDecl::Instance(_)); toplevel.declarations.insert(name_segment, top_decl); output.declared(name_segment); + if is_instance + && let Some(TopDecl::Instance(def)) = + toplevel.declarations.get(&name_segment) + { + match instance_from_def(&toplevel, &def.theory.definition, def) { + Ok((instance, ns)) => output.instance_summary(&instance, &ns), + Err(msg) => output.instance_error(&msg), + } + } } TopElabResult::Output(s) => { output.got_result(&s); @@ -242,3 +298,141 @@ fn snapshot_examples() { } assert!(succeeded); } + +/// Render an instance term for snapshot output as `f(g(base))`, with +/// `f` the outermost (last-applied) model morphism in the path. +pub(in crate::tt) fn format_instance_term(tm: &DiscreteInstanceTerm, ns: &Namespace) -> String { + let mut s = ns.label_string(&tm.base); + if let Path::Seq(edges) = &tm.path { + for mor in edges.iter() { + s = format!("{}({})", ns.label_string(mor), s); + } + } + s +} + +/// Writes the generators and equations of an instance, using the given +/// per-doctrine formatters for fibers and equation terms. +pub(in crate::tt) fn write_instance_summary( + out: &mut String, + instance: &DblModelInstance, + ns: &Namespace, + fmt_ob: impl Fn(&M::Ob) -> String, + fmt_term: impl Fn(&M::Term) -> String, +) { + let gens: Vec<_> = instance.generators().collect(); + let eqns: Vec<_> = instance.equations().collect(); + if gens.is_empty() && eqns.is_empty() { + writeln!(out, "#/ instance has no generators or equations").unwrap(); + return; + } + if !gens.is_empty() { + writeln!(out, "#/ instance generators:").unwrap(); + for (name, fiber) in &gens { + writeln!(out, "#/ {} : {}", ns.label_string(name), fmt_ob(fiber)).unwrap(); + } + } + if !eqns.is_empty() { + writeln!(out, "#/ instance equations:").unwrap(); + for (lhs, rhs) in &eqns { + writeln!(out, "#/ {} == {}", fmt_term(lhs), fmt_term(rhs)).unwrap(); + } + } +} + +/// Renders a modal object for snapshot output: generators by name, object +/// operations as `op(inner)`, and lists as `[a, b, …]`. +pub(in crate::tt) fn format_modal_ob(ob: &ModalOb, ns: &Namespace) -> String { + match ob { + ModalOb::Generator(name) => ns.label_string(name), + ModalOb::App(inner, op) => format!("{op}({})", format_modal_ob(inner, ns)), + ModalOb::List(_, obs) => { + let inner: Vec<_> = obs.iter().map(|ob| format_modal_ob(ob, ns)).collect(); + format!("[{}]", inner.join(", ")) + } + } +} + +/// An applicative rendering of a modal instance term, reconstructed from its +/// flat `(mor, base)` normal form so it prints back in surface syntax. +enum Rendered { + Gen(String), + App(String, Box), + ObApp(String, Box), + List(Vec), +} + +impl Rendered { + fn render(&self) -> String { + match self { + Rendered::Gen(name) => name.clone(), + Rendered::App(name, inner) => format!("{name}({})", inner.render()), + Rendered::ObApp(op, inner) => format!("@{op} {}", inner.render()), + Rendered::List(items) => { + let inner: Vec<_> = items.iter().map(Rendered::render).collect(); + format!("[{}]", inner.join(", ")) + } + } + } +} + +/// Renders a modal instance term as e.g. `op([x, unit([])])`, re-interleaving +/// the morphism with its base (the inverse of the flattening done during +/// extraction). +pub(in crate::tt) fn format_modal_instance_term(tm: &ModalInstanceTerm, ns: &Namespace) -> String { + apply_mor(&tm.mor, base_rendered(&tm.base, ns), ns).render() +} + +fn base_rendered(base: &ModalInstanceBase, ns: &Namespace) -> Rendered { + match base { + ModalInstanceBase::Generator(name) => Rendered::Gen(ns.label_string(name)), + ModalInstanceBase::List(_, bases) => { + Rendered::List(bases.iter().map(|b| base_rendered(b, ns)).collect()) + } + ModalInstanceBase::ObApp(op, inner) => { + Rendered::ObApp(format!("{op}"), Box::new(base_rendered(inner, ns))) + } + } +} + +/// Applies a model morphism to an already-rendered argument, undoing the +/// `Composite`/`List` tupling introduced by normalization: a `Composite` path +/// folds its morphisms outermost-last, and a list morphism zips into a list +/// argument. +fn apply_mor(mor: &ModalMor, arg: Rendered, ns: &Namespace) -> Rendered { + if modal_mor_as_identity(mor).is_some() { + return arg; + } + match mor { + ModalMor::Generator(name) => Rendered::App(ns.label_string(name), Box::new(arg)), + ModalMor::App(_, op) => Rendered::App(format!("{op}"), Box::new(arg)), + // The functorial action of an object operation: it applies to an + // `@op [..]` base, and the lifted morphisms act on the operation's + // content, so we push them inside the existing wrapper rather than + // adding another. + ModalMor::HomApp(path, _op) => match arg { + Rendered::ObApp(op_name, inner) => { + Rendered::ObApp(op_name, Box::new(apply_path(path, *inner, ns))) + } + // Should not arise: a hom operation applies to an object-op base. + other => other, + }, + ModalMor::Composite(path) => apply_path(path, arg, ns), + ModalMor::List(_, mors) => match arg { + Rendered::List(items) if items.len() == mors.len() => { + Rendered::List(mors.iter().zip(items).map(|(m, a)| apply_mor(m, a, ns)).collect()) + } + // Should not arise: a list morphism always applies to a list base. + other => other, + }, + } +} + +/// Applies a path of morphisms to a rendered argument, folding outermost-last +/// (so `[m1, m2]` renders as `m2(m1(arg))`). +fn apply_path(path: &Path, arg: Rendered, ns: &Namespace) -> Rendered { + match path { + Path::Id(_) => arg, + Path::Seq(edges) => edges.iter().fold(arg, |acc, mor| apply_mor(mor, acc, ns)), + } +} diff --git a/packages/catlog/src/tt/context.rs b/packages/catlog/src/tt/context.rs index 0e313d478..1be6f6593 100644 --- a/packages/catlog/src/tt/context.rs +++ b/packages/catlog/src/tt/context.rs @@ -18,21 +18,47 @@ pub struct VarInContext { /// /// We allow the type to be null as a hack for the `self` variable before we /// know the type of the `self` variable. - pub ty: Option, + pub ty: Option, +} + +/// Each *fiber* variable in context — a generator or sub-instance import +/// introduced inside an instance body — with its label and fiber type. +#[derive(Constructor)] +pub struct FiberVarInContext { + /// The name of the fiber variable. + pub name: VarName, + /// The label for the fiber variable. + pub label: LabelSegment, + /// The fiber type of the variable. + pub ty: FiberTyV, } /// The variable context during elaboration. +/// +/// Carries two scopes: the **base** context (`env`/`scope`) of ordinary +/// terms typed by [`BaseTyV`], and a separate **fiber** context +/// (`fiber_env`/`fiber_scope`) of instance generators and sub-instance +/// imports typed by [`FiberTyV`]. The fiber scope is populated only while +/// elaborating an instance body; the two never alias, so neither lookup +/// can see the other's variables. See [`crate::tt::toplevel`] for why the +/// two worlds are distinct. pub struct Context { - /// Stores the value of each of the variables in context. + /// Stores the value of each of the base variables in context. pub env: Env, - /// Stores the names and types of each of the variables in context. + /// Stores the names and types of each of the base variables in context. pub scope: Vec, + /// Stores the value of each fiber variable in context. + pub fiber_env: FiberEnv, + /// Stores the names and fiber types of each fiber variable in context. + pub fiber_scope: Vec, } /// A checkpoint that we can return the context to. pub struct ContextCheckpoint { env: Env, scope: usize, + fiber_env: FiberEnv, + fiber_scope: usize, } impl Default for Context { @@ -44,7 +70,12 @@ impl Default for Context { impl Context { /// Create an empty context. pub fn new() -> Self { - Self { env: Env::Nil, scope: Vec::new() } + Self { + env: Env::Nil, + scope: Vec::new(), + fiber_env: FiberEnv::Nil, + fiber_scope: Vec::new(), + } } /// Create a checkpoint from the current state of the context. @@ -52,6 +83,8 @@ impl Context { ContextCheckpoint { env: self.env.clone(), scope: self.scope.len(), + fiber_env: self.fiber_env.clone(), + fiber_scope: self.fiber_scope.len(), } } @@ -59,15 +92,17 @@ impl Context { pub fn reset_to(&mut self, c: ContextCheckpoint) { self.env = c.env; self.scope.truncate(c.scope); + self.fiber_env = c.fiber_env; + self.fiber_scope.truncate(c.fiber_scope); } - /// Add a new variable to scope (note: does not add it to the environment). - pub fn push_scope(&mut self, name: VarName, label: LabelSegment, ty: Option) { + /// Add a new base variable to scope (note: does not add it to the environment). + pub fn push_scope(&mut self, name: VarName, label: LabelSegment, ty: Option) { self.scope.push(VarInContext::new(name, label, ty)) } - /// Lookup a variable by name. - pub fn lookup(&self, name: VarName) -> Option<(BwdIdx, LabelSegment, Option)> { + /// Lookup a base variable by name. + pub fn lookup(&self, name: VarName) -> Option<(BwdIdx, LabelSegment, Option)> { self.scope .iter() .rev() @@ -75,4 +110,20 @@ impl Context { .find(|(_, v)| v.name == name) .map(|(i, v)| (i.into(), v.label, v.ty.clone())) } + + /// Add a new fiber variable to scope (note: does not add it to the + /// fiber environment). + pub fn push_fiber(&mut self, name: VarName, label: LabelSegment, ty: FiberTyV) { + self.fiber_scope.push(FiberVarInContext::new(name, label, ty)) + } + + /// Lookup a fiber variable by name. + pub fn lookup_fiber(&self, name: VarName) -> Option<(BwdIdx, LabelSegment, FiberTyV)> { + self.fiber_scope + .iter() + .rev() + .enumerate() + .find(|(_, v)| v.name == name) + .map(|(i, v)| (i.into(), v.label, v.ty.clone())) + } } diff --git a/packages/catlog/src/tt/eval.rs b/packages/catlog/src/tt/eval.rs index bce90d26f..cc7aa481e 100644 --- a/packages/catlog/src/tt/eval.rs +++ b/packages/catlog/src/tt/eval.rs @@ -39,7 +39,7 @@ impl<'a> Evaluator<'a> { Self { env, ..self.clone() } } - fn eval_record(&self, fields: &Row) -> RecordV { + fn eval_record(&self, fields: &Row) -> RecordV { RecordV::new(self.env.clone(), fields.clone(), Dtry::empty()) } @@ -47,25 +47,27 @@ impl<'a> Evaluator<'a> { /// /// Assumes that the type syntax is well-formed and well-scoped with respect /// to self.env. - pub fn eval_ty(&self, ty: &TyS) -> TyV { + pub fn eval_ty(&self, ty: &BaseTyS) -> BaseTyV { match &**ty { - TyS_::TopVar(tv) => self.toplevel.declarations.get(tv).unwrap().clone().unwrap_ty().val, - TyS_::Object(ot) => TyV::object(ot.clone()), - TyS_::Morphism(pt, dom, cod) => { - TyV::morphism(pt.clone(), self.eval_tm(dom), self.eval_tm(cod)) + BaseTyS_::TopVar(tv) => match self.toplevel.declarations.get(tv).unwrap() { + TopDecl::Type(t) => t.val.clone(), + _ => panic!("top-level {tv} should be a type declaration"), + }, + BaseTyS_::Object(ot) => BaseTyV::object(ot.clone()), + BaseTyS_::Morphism(pt, dom, cod) => { + BaseTyV::morphism(pt.clone(), self.eval_tm(dom), self.eval_tm(cod)) } - TyS_::Record(r) => TyV::record(self.eval_record(r)), - TyS_::Sing(ty_s, tm_s) => TyV::sing(self.eval_ty(ty_s), self.eval_tm(tm_s)), - TyS_::Id(ty_s, tm_s1, tm_s2) => { - TyV::id(self.eval_ty(ty_s), self.eval_tm(tm_s1), self.eval_tm(tm_s2)) + BaseTyS_::Record(r) => BaseTyV::record(self.eval_record(r)), + BaseTyS_::Sing(ty_s, tm_s) => BaseTyV::sing(self.eval_ty(ty_s), self.eval_tm(tm_s)), + BaseTyS_::Id(ty_s, tm_s1, tm_s2) => { + BaseTyV::id(self.eval_ty(ty_s), self.eval_tm(tm_s1), self.eval_tm(tm_s2)) } - TyS_::Specialize(ty_s, specializations) => { + BaseTyS_::Specialize(ty_s, specializations) => { specializations.iter().fold(self.eval_ty(ty_s), |ty_v, (path, s)| { ty_v.add_specialization(path, self.eval_ty(s)) }) } - TyS_::Unit => TyV::unit(), - TyS_::Meta(mv) => TyV::meta(*mv), + BaseTyS_::Meta(mv) => BaseTyV::meta(*mv), } } @@ -73,45 +75,43 @@ impl<'a> Evaluator<'a> { /// /// Assumes that the term syntax is well-formed and well-scoped with respect /// to self.env. - pub fn eval_tm(&self, tm: &TmS) -> TmV { + pub fn eval_tm(&self, tm: &BaseTmS) -> BaseTmV { match &**tm { - TmS_::TopVar(tv) => { - self.toplevel.declarations.get(tv).unwrap().clone().unwrap_const().val - } - TmS_::TopApp(tv, args_s) => { + BaseTmS_::TopApp(tv, args_s) => { let env = Env::nil().extend_by(args_s.iter().map(|arg_s| self.eval_tm(arg_s))); let def = self.toplevel.declarations.get(tv).unwrap().clone().unwrap_def(); self.with_env(env).eval_tm(&def.body) } - TmS_::Var(i, _, _) => self.env.get(**i).cloned().unwrap(), - TmS_::Cons(fields) => TmV::cons(fields.map(|tm| self.eval_tm(tm))), - TmS_::Proj(tm, field, label) => self.proj(&self.eval_tm(tm), *field, *label), - TmS_::Tt => TmV::tt(), - TmS_::Id(x) => TmV::id(self.eval_tm(x)), - TmS_::Tab(mor) => TmV::tab(self.eval_tm(mor)), - TmS_::Compose(f, g) => TmV::compose(self.eval_tm(f), self.eval_tm(g)), - TmS_::ObApp(name, x) => TmV::app(*name, self.eval_tm(x)), - TmS_::List(elems) => TmV::list(elems.iter().map(|tm| self.eval_tm(tm)).collect()), - TmS_::Meta(mv) => TmV::meta(*mv), + BaseTmS_::Var(i, _, _) => self.env.get(**i).cloned().unwrap(), + BaseTmS_::Cons(fields) => BaseTmV::cons(fields.map(|tm| self.eval_tm(tm))), + BaseTmS_::Proj(tm, field, label) => self.proj(&self.eval_tm(tm), *field, *label), + BaseTmS_::Id(x) => BaseTmV::id(self.eval_tm(x)), + BaseTmS_::Tab(mor) => BaseTmV::tab(self.eval_tm(mor)), + BaseTmS_::Compose(f, g) => BaseTmV::compose(self.eval_tm(f), self.eval_tm(g)), + BaseTmS_::ObApp(name, x) => BaseTmV::app(*name, self.eval_tm(x)), + BaseTmS_::List(elems) => { + BaseTmV::list(elems.iter().map(|tm| self.eval_tm(tm)).collect()) + } + BaseTmS_::Meta(mv) => BaseTmV::meta(*mv), } } /// Compute the projection of a field from a term value. - pub fn proj(&self, tm: &TmV, field_name: FieldName, field_label: LabelSegment) -> TmV { + pub fn proj(&self, tm: &BaseTmV, field_name: FieldName, field_label: LabelSegment) -> BaseTmV { match &**tm { - TmV_::Neu(n, ty) => TmV::neu( + BaseTmV_::Neu(n, ty) => BaseTmV::neu( TmN::proj(n.clone(), field_name, field_label), self.field_ty(ty, tm, field_name), ), - TmV_::Cons(fields) => fields.get(field_name).cloned().unwrap(), - _ => panic!(), + BaseTmV_::Cons(fields) => fields.get(field_name).cloned().unwrap(), + _ => unreachable!("projected field {field_name} from a non-record term value"), } } /// Evaluate the type of the field `field_name` of `val : ty`. - pub fn field_ty(&self, ty: &TyV, val: &TmV, field_name: FieldName) -> TyV { + pub fn field_ty(&self, ty: &BaseTyV, val: &BaseTmV, field_name: FieldName) -> BaseTyV { match &**ty { - TyV_::Record(r) => { + BaseTyV_::Record(r) => { let field_ty_s = r.fields.get(field_name).unwrap(); let orig_field_ty = self.with_env(r.env.snoc(val.clone())).eval_ty(field_ty_s); match r.specializations.entry(&field_name) { @@ -125,9 +125,9 @@ impl<'a> Evaluator<'a> { } /// Bind a new neutral of type `ty`. - pub fn bind_neu(&self, name: VarName, label: LabelSegment, ty: TyV) -> (TmN, Self) { + pub fn bind_neu(&self, name: VarName, label: LabelSegment, ty: BaseTyV) -> (TmN, Self) { let n = TmN::var(self.scope_length.into(), name, label); - let v = TmV::neu(n.clone(), ty); + let v = BaseTmV::neu(n.clone(), ty); ( n, Self { @@ -139,7 +139,7 @@ impl<'a> Evaluator<'a> { } /// Bind a variable called "self" to `ty`. - pub fn bind_self(&self, ty: TyV) -> (TmN, Self) { + pub fn bind_self(&self, ty: BaseTyV) -> (TmN, Self) { self.bind_neu("self".into(), "self".into(), ty) } @@ -148,27 +148,27 @@ impl<'a> Evaluator<'a> { /// This is a *section* of eval, in that `self.eval_ty(self.quote_ty(ty_v)) == ty_v` /// but it is not necessarily true that `self.quote_ty(self.eval_ty(ty_s)) == ty_v`. /// - /// This is used for displaying [TyV] to the user in type errors, and for + /// This is used for displaying [BaseTyV] to the user in type errors, and for /// creating syntax that can be re-evaluated in other contexts. In theory this /// could be used for conversion checking, but it's more efficient to implement /// that directly, and it's better to *not* do eta-expansion for user-facing /// messages or for syntax that is meant to be re-evaluated. - pub fn quote_ty(&self, ty: &TyV) -> TyS { + pub fn quote_ty(&self, ty: &BaseTyV) -> BaseTyS { match &**ty { - TyV_::Object(object_type) => TyS::object(object_type.clone()), - TyV_::Morphism(morphism_type, dom, cod) => { - TyS::morphism(morphism_type.clone(), self.quote_tm(dom), self.quote_tm(cod)) + BaseTyV_::Object(object_type) => BaseTyS::object(object_type.clone()), + BaseTyV_::Morphism(morphism_type, dom, cod) => { + BaseTyS::morphism(morphism_type.clone(), self.quote_tm(dom), self.quote_tm(cod)) } - TyV_::Record(r) => { + BaseTyV_::Record(r) => { let r_eval = self.with_env(r.env.clone()).bind_self(ty.clone()).1; let fields = r .fields .map(|ty_s| self.bind_self(ty.clone()).1.quote_ty(&r_eval.eval_ty(ty_s))); - let record_ty_s = TyS::record(fields); + let record_ty_s = BaseTyS::record(fields); if r.specializations.is_empty() { record_ty_s } else { - TyS::specialize( + BaseTyS::specialize( record_ty_s, r.specializations .flatten() @@ -186,39 +186,39 @@ impl<'a> Evaluator<'a> { ) } } - TyV_::Sing(ty, tm) => TyS::sing(self.quote_ty(ty), self.quote_tm(tm)), - TyV_::Id(ty, tm1, tm2) => { - TyS::id(self.quote_ty(ty), self.quote_tm(tm1), self.quote_tm(tm2)) + BaseTyV_::Sing(ty, tm) => BaseTyS::sing(self.quote_ty(ty), self.quote_tm(tm)), + BaseTyV_::Id(ty, tm1, tm2) => { + BaseTyS::id(self.quote_ty(ty), self.quote_tm(tm1), self.quote_tm(tm2)) } - TyV_::Unit => TyS::unit(), - TyV_::Meta(mv) => TyS::meta(*mv), + BaseTyV_::Meta(mv) => BaseTyS::meta(*mv), } } /// Produce term syntax from a neutral term. /// /// The documentation for [Evaluator::quote_ty] is also applicable here. - pub fn quote_neu(&self, n: &TmN) -> TmS { + pub fn quote_neu(&self, n: &TmN) -> BaseTmS { match &**n { - TmN_::Var(i, name, label) => TmS::var(i.as_bwd(self.scope_length), *name, *label), - TmN_::Proj(tm, field, label) => TmS::proj(self.quote_neu(tm), *field, *label), + TmN_::Var(i, name, label) => BaseTmS::var(i.as_bwd(self.scope_length), *name, *label), + TmN_::Proj(tm, field, label) => BaseTmS::proj(self.quote_neu(tm), *field, *label), } } /// Produce term syntax from a term value. /// /// The documentation for [Evaluator::quote_ty] is also applicable here. - pub fn quote_tm(&self, tm: &TmV) -> TmS { + pub fn quote_tm(&self, tm: &BaseTmV) -> BaseTmS { match &**tm { - TmV_::Neu(n, _) => self.quote_neu(n), - TmV_::App(name, x) => TmS::ob_app(*name, self.quote_tm(x)), - TmV_::List(elems) => TmS::list(elems.iter().map(|tm| self.quote_tm(tm)).collect()), - TmV_::Cons(fields) => TmS::cons(fields.map(|tm| self.quote_tm(tm))), - TmV_::Tt => TmS::tt(), - TmV_::Id(x) => TmS::id(self.quote_tm(x)), - TmV_::Tab(mor) => TmS::tab(self.quote_tm(mor)), - TmV_::Compose(f, g) => TmS::compose(self.quote_tm(f), self.quote_tm(g)), - TmV_::Meta(mv) => TmS::meta(*mv), + BaseTmV_::Neu(n, _) => self.quote_neu(n), + BaseTmV_::App(name, x) => BaseTmS::ob_app(*name, self.quote_tm(x)), + BaseTmV_::List(elems) => { + BaseTmS::list(elems.iter().map(|tm| self.quote_tm(tm)).collect()) + } + BaseTmV_::Cons(fields) => BaseTmS::cons(fields.map(|tm| self.quote_tm(tm))), + BaseTmV_::Id(x) => BaseTmS::id(self.quote_tm(x)), + BaseTmV_::Tab(mor) => BaseTmS::tab(self.quote_tm(mor)), + BaseTmV_::Compose(f, g) => BaseTmS::compose(self.quote_tm(f), self.quote_tm(g)), + BaseTmV_::Meta(mv) => BaseTmS::meta(*mv), } } @@ -226,7 +226,7 @@ impl<'a> Evaluator<'a> { /// /// This is true iff `ty1` is convertible with `ty2`, and an eta-expanded /// neutral of type `ty1` is an element of `ty2`. - pub fn subtype<'b>(&self, ty1: &TyV, ty2: &TyV) -> Result<(), D<'b>> { + pub fn subtype<'b>(&self, ty1: &BaseTyV, ty2: &BaseTyV) -> Result<(), D<'b>> { self.convertible_ty(ty1, ty2)?; let (n, _) = self.bind_self(ty1.clone()); let v = self.eta_neu(&n, ty1); @@ -241,20 +241,19 @@ impl<'a> Evaluator<'a> { /// /// Example: if `a : Entity` and `b : Entity` are neutrals, then `a` is not an /// element of `@sing b`, but `a` is an element of `@sing a`. - pub fn element_of<'b>(&self, tm: &TmV, ty: &TyV) -> Result<(), D<'b>> { + pub fn element_of<'b>(&self, tm: &BaseTmV, ty: &BaseTyV) -> Result<(), D<'b>> { match &**ty { - TyV_::Object(_) => Ok(()), - TyV_::Morphism(_, _, _) => Ok(()), - TyV_::Record(r) => { + BaseTyV_::Object(_) => Ok(()), + BaseTyV_::Morphism(_, _, _) => Ok(()), + BaseTyV_::Record(r) => { for (name, (label, _)) in r.fields.iter() { self.element_of(&self.proj(tm, *name, *label), &self.field_ty(ty, tm, *name))? } Ok(()) } - TyV_::Sing(_, x) => self.equal_tm(tm, x), - TyV_::Id(_, _, _) => Ok(()), - TyV_::Unit => Ok(()), - TyV_::Meta(_) => Ok(()), + BaseTyV_::Sing(_, x) => self.equal_tm(tm, x), + BaseTyV_::Id(_, _, _) => Ok(()), + BaseTyV_::Meta(_) => Ok(()), } } @@ -263,16 +262,16 @@ impl<'a> Evaluator<'a> { /// Ignores specializations: specializations are handled in [`Evaluator::subtype`]. /// /// On failure, returns a doc which describes the obstruction to convertibility. - pub fn convertible_ty<'b>(&self, ty1: &TyV, ty2: &TyV) -> Result<(), D<'b>> { + pub fn convertible_ty<'b>(&self, ty1: &BaseTyV, ty2: &BaseTyV) -> Result<(), D<'b>> { match (&**ty1, &**ty2) { - (TyV_::Object(ot1), TyV_::Object(ot2)) => { + (BaseTyV_::Object(ot1), BaseTyV_::Object(ot2)) => { if ot1 == ot2 { Ok(()) } else { Err(t(format!("object types {ot1} and {ot2} are not equal"))) } } - (TyV_::Morphism(mt1, dom1, cod1), TyV_::Morphism(mt2, dom2, cod2)) => { + (BaseTyV_::Morphism(mt1, dom1, cod1), BaseTyV_::Morphism(mt2, dom2, cod2)) => { if mt1 != mt2 { return Err(t(format!("morphism types {mt1} and {mt2} are not equal"))); } @@ -280,57 +279,57 @@ impl<'a> Evaluator<'a> { self.equal_tm(cod1, cod2).map_err(|d| t("could not convert codomains: ") + d)?; Ok(()) } - (TyV_::Record(r1), TyV_::Record(r2)) => { + (BaseTyV_::Record(r1), BaseTyV_::Record(r2)) => { let mut fields = IndexMap::new(); let mut self1 = self.clone(); for ((name, (label, field_ty1_s)), (_, (_, field_ty2_s))) in r1.fields.iter().zip(r2.fields.iter()) { - let v = TmV::cons(fields.clone().into()); + let v = BaseTmV::cons(fields.clone().into()); let field_ty1_v = self1.with_env(r1.env.snoc(v.clone())).eval_ty(field_ty1_s); let field_ty2_v = self1.with_env(r2.env.snoc(v.clone())).eval_ty(field_ty2_s); self1.convertible_ty(&field_ty1_v, &field_ty2_v)?; let (field_val, self_next) = self.bind_neu(*name, *label, field_ty1_v.clone()); self1 = self_next; - fields.insert(*name, (*label, TmV::neu(field_val, field_ty1_v))); + fields.insert(*name, (*label, BaseTmV::neu(field_val, field_ty1_v))); } Ok(()) } - (TyV_::Sing(ty1, _), _) => self.convertible_ty(ty1, ty2), - (_, TyV_::Sing(ty2, _)) => self.convertible_ty(ty1, ty2), - (TyV_::Unit, TyV_::Unit) => Ok(()), + (BaseTyV_::Sing(ty1, _), _) => self.convertible_ty(ty1, ty2), + (_, BaseTyV_::Sing(ty2, _)) => self.convertible_ty(ty1, ty2), _ => Err(t("tried to convert between types of different type constructors")), } } /// Performs eta-expansion of the neutral `n` at type `ty`. - pub fn eta_neu(&self, n: &TmN, ty: &TyV) -> TmV { + pub fn eta_neu(&self, n: &TmN, ty: &BaseTyV) -> BaseTmV { match &**ty { - TyV_::Object(_) => TmV::neu(n.clone(), ty.clone()), - TyV_::Morphism(_, _, _) => TmV::neu(n.clone(), ty.clone()), - TyV_::Record(r) => { + BaseTyV_::Object(_) => BaseTmV::neu(n.clone(), ty.clone()), + BaseTyV_::Morphism(_, _, _) => BaseTmV::neu(n.clone(), ty.clone()), + BaseTyV_::Record(r) => { let mut fields = Row::empty(); for (name, (label, _)) in r.fields.iter() { - let ty_v = self.field_ty(ty, &TmV::cons(fields.clone()), *name); + let ty_v = self.field_ty(ty, &BaseTmV::cons(fields.clone()), *name); let v = self.eta_neu(&TmN::proj(n.clone(), *name, *label), &ty_v); fields.insert(*name, *label, v); } - TmV::cons(fields) + BaseTmV::cons(fields) } - TyV_::Sing(_, x) => x.clone(), - TyV_::Id(_, _, _) => TmV::tt(), // Extensional equality at a 100% discount! - TyV_::Unit => TmV::tt(), - TyV_::Meta(_) => TmV::neu(n.clone(), ty.clone()), + BaseTyV_::Sing(_, x) => x.clone(), + BaseTyV_::Id(_, _, _) => BaseTmV::empty_cons(), /* Extensional equality at a 100% discount! */ + BaseTyV_::Meta(_) => BaseTmV::neu(n.clone(), ty.clone()), } } /// Performs eta-expansion of the term `v` at type `ty`. - pub fn eta(&self, v: &TmV, ty: Option<&TyV>) -> TmV { + pub fn eta(&self, v: &BaseTmV, ty: Option<&BaseTyV>) -> BaseTmV { match &**v { - TmV_::Neu(tm_n, ty_v) => self.eta_neu(tm_n, ty_v), - TmV_::App(name, x) => TmV::app(*name, self.eta(x, None)), - TmV_::List(elems) => TmV::list(elems.iter().map(|elem| self.eta(elem, None)).collect()), - TmV_::Cons(row) => { + BaseTmV_::Neu(tm_n, ty_v) => self.eta_neu(tm_n, ty_v), + BaseTmV_::App(name, x) => BaseTmV::app(*name, self.eta(x, None)), + BaseTmV_::List(elems) => { + BaseTmV::list(elems.iter().map(|elem| self.eta(elem, None)).collect()) + } + BaseTmV_::Cons(row) => { if let Some(ty) = ty { let row = row .iter() @@ -338,18 +337,17 @@ impl<'a> Evaluator<'a> { (*name, (*label, self.eta(field_v, Some(&self.field_ty(ty, v, *name))))) }) .collect(); - TmV::cons(row) + BaseTmV::cons(row) } // Is this right? Couldn't a cons be nested below top-level and so not get expanded right? else { v.clone() } } - TmV_::Tt => TmV::tt(), - TmV_::Id(x) => TmV::id(self.eta(x, None)), - TmV_::Tab(mor) => TmV::tab(self.eta(mor, None)), - TmV_::Compose(f, g) => TmV::compose(self.eta(f, None), self.eta(g, None)), - TmV_::Meta(_) => v.clone(), + BaseTmV_::Id(x) => BaseTmV::id(self.eta(x, None)), + BaseTmV_::Tab(mor) => BaseTmV::tab(self.eta(mor, None)), + BaseTmV_::Compose(f, g) => BaseTmV::compose(self.eta(f, None), self.eta(g, None)), + BaseTmV_::Meta(_) => v.clone(), } } @@ -360,7 +358,7 @@ impl<'a> Evaluator<'a> { /// Assumes that the type of tm1 is convertible with the type of tm2. First /// attempts to do conversion checking without eta-expansion (strict mode), /// and if that fails, does conversion checking with eta-expansion. - pub fn equal_tm<'b>(&self, tm1: &TmV, tm2: &TmV) -> Result<(), D<'b>> { + pub fn equal_tm<'b>(&self, tm1: &BaseTmV, tm2: &BaseTmV) -> Result<(), D<'b>> { if self.equal_tm_helper(tm1, tm2, true, true).is_err() { self.equal_tm_helper(tm1, tm2, false, false) } else { @@ -370,19 +368,19 @@ impl<'a> Evaluator<'a> { fn equal_tm_helper<'b>( &self, - tm1: &TmV, - tm2: &TmV, + tm1: &BaseTmV, + tm2: &BaseTmV, strict1: bool, strict2: bool, ) -> Result<(), D<'b>> { match (&**tm1, &**tm2) { - (TmV_::Neu(n1, ty1), _) if !strict1 => { + (BaseTmV_::Neu(n1, ty1), _) if !strict1 => { self.equal_tm_helper(&self.eta_neu(n1, ty1), tm2, true, strict2) } - (_, TmV_::Neu(n2, ty2)) if !strict2 => { + (_, BaseTmV_::Neu(n2, ty2)) if !strict2 => { self.equal_tm_helper(tm1, &self.eta_neu(n2, ty2), strict1, true) } - (TmV_::Neu(n1, _), TmV_::Neu(n2, _)) => { + (BaseTmV_::Neu(n1, _), BaseTmV_::Neu(n2, _)) => { if n1 == n2 { Ok(()) } else { @@ -393,29 +391,56 @@ impl<'a> Evaluator<'a> { ))) } } - (TmV_::Cons(fields1), TmV_::Cons(fields2)) => { + (BaseTmV_::Cons(fields1), BaseTmV_::Cons(fields2)) => { for ((_, (_, tm1)), (_, (_, tm2))) in fields1.iter().zip(fields2.iter()) { self.equal_tm_helper(tm1, tm2, strict1, strict2)? } Ok(()) } - (TmV_::Tt, TmV_::Tt) => Ok(()), - (TmV_::Meta(mv1), TmV_::Meta(mv2)) => { + (BaseTmV_::Meta(mv1), BaseTmV_::Meta(mv2)) => { if mv1 == mv2 { Ok(()) } else { Err(t(format!("Holes {} and {} are not equal.", mv1, mv2))) } } - (TmV_::Id(x1), TmV_::Id(x2)) => self.equal_tm_helper(x1, x2, strict1, strict2), - (TmV_::Compose(f1, g1), TmV_::Compose(f2, g2)) => { + (BaseTmV_::Id(x1), BaseTmV_::Id(x2)) => self.equal_tm_helper(x1, x2, strict1, strict2), + (BaseTmV_::Compose(f1, g1), BaseTmV_::Compose(f2, g2)) => { self.equal_tm_helper(f1, f2, strict1, strict2)?; self.equal_tm_helper(g1, g2, strict1, strict2) } - (TmV_::Tab(mor1), TmV_::Tab(mor2)) => { + (BaseTmV_::Tab(mor1), BaseTmV_::Tab(mor2)) => { self.equal_tm_helper(mor1, mor2, strict1, strict2) } - _ => Err(t(format!( + (BaseTmV_::List(es1), BaseTmV_::List(es2)) => { + if es1.len() != es2.len() { + return Err(t("lists have different lengths")); + } + for (e1, e2) in es1.iter().zip(es2.iter()) { + self.equal_tm_helper(e1, e2, strict1, strict2)?; + } + Ok(()) + } + (BaseTmV_::App(n1, a1), BaseTmV_::App(n2, a2)) => { + if n1 != n2 { + return Err(t(format!("object operations {n1} and {n2} are not equal"))); + } + self.equal_tm_helper(a1, a2, strict1, strict2) + } + // Constructor mismatch. Enumerating the left term's variants + // (rather than `_`) keeps this exhaustive, so a new `BaseTmV_` + // variant fails to compile here until its equality is defined. + ( + BaseTmV_::Neu(_, _) + | BaseTmV_::App(_, _) + | BaseTmV_::List(_) + | BaseTmV_::Cons(_) + | BaseTmV_::Id(_) + | BaseTmV_::Tab(_) + | BaseTmV_::Compose(_, _) + | BaseTmV_::Meta(_), + _, + ) => Err(t(format!( "failed to match terms {} and {}", self.quote_tm(tm1), self.quote_tm(tm2) @@ -425,34 +450,49 @@ impl<'a> Evaluator<'a> { fn can_specialize( &self, - ty: &TyV, - val: &TmV, + ty: &BaseTyV, + val: &BaseTmV, path: &[(FieldName, LabelSegment)], - field_ty: TyV, + field_ty: BaseTyV, ) -> Result<(), String> { assert!(!path.is_empty()); + let orig_field_ty = self.path_ty(ty, val, path)?; + self.subtype(&field_ty, &orig_field_ty).map_err(|msg| { + format!( + "{} is not a subtype of {}:\n... because {}", + self.quote_ty(&field_ty), + self.quote_ty(&orig_field_ty), + msg.pretty() + ) + }) + } - let TyV_::Record(r) = &**ty else { - return Err("cannot specialize a non-record type".into()); - }; - - let (field, path) = (path[0], &path[1..]); - if !r.fields.has(field.0) { - return Err(format!("no such field .{}", field.1)); - } - let orig_field_ty = self.field_ty(ty, val, field.0); - if path.is_empty() { - self.subtype(&field_ty, &orig_field_ty).map_err(|msg| { - format!( - "{} is not a subtype of {}:\n... because {}", - self.quote_ty(&field_ty), - self.quote_ty(&orig_field_ty), - msg.pretty() - ) - }) - } else { - self.can_specialize(&orig_field_ty, &self.proj(val, field.0, field.1), path, field_ty) + /// Walk `path` from the value `val` of record type `ty`, returning + /// the type of the field at the end of the path. + /// + /// An empty path returns `ty` unchanged. Each segment requires the + /// current type to be a record containing the named field. + pub fn path_ty( + &self, + ty: &BaseTyV, + val: &BaseTmV, + path: &[(FieldName, LabelSegment)], + ) -> Result { + let mut ty = ty.clone(); + let mut val = val.clone(); + for &(name, label) in path { + let BaseTyV_::Record(r) = &*ty.clone() else { + return Err(format!("expected a record type at .{label}")); + }; + if !r.fields.has(name) { + return Err(format!("no such field .{label}")); + } + let next_ty = self.field_ty(&ty, &val, name); + let next_val = self.proj(&val, name, label); + ty = next_ty; + val = next_val; } + Ok(ty) } /// Try to specialize the record `r` with the subtype `ty` at `path`. @@ -460,16 +500,114 @@ impl<'a> Evaluator<'a> { /// Precondition: `path` is non-empty. pub fn try_specialize( &self, - ty: &TyV, + ty: &BaseTyV, path: &[(FieldName, LabelSegment)], - field_ty: TyV, - ) -> Result { + field_ty: BaseTyV, + ) -> Result { let (self_var, _) = self.bind_self(ty.clone()); let self_val = self.eta_neu(&self_var, ty); self.can_specialize(ty, &self_val, path, field_ty.clone())?; - let TyV_::Record(r) = &**ty else { + let BaseTyV_::Record(r) = &**ty else { panic!("Input to `try_specialize` should be a record type") }; - Ok(TyV::record(r.add_specialization(path, field_ty))) + Ok(BaseTyV::record(r.add_specialization(path, field_ty))) + } + + // --- Fiber-world NbE and conversion --------------------------------- + // + // Fiber types/terms carry no closures or computation rules (every + // fiber term is neutral), so there is no fiber eval/quote: the + // elaborator builds [`FiberTyS`]/[`FiberTyV`] (and the term sorts) in + // parallel. What remains is conversion checking and field projection. + + /// The fiber type of field `field` of a fiber record type, if present. + /// + /// Only [`Over`](FiberTyV_::Over) generator fields are ever projected + /// (as `we.e`); their types are closed, so this is a plain lookup with + /// no environment. + pub fn fiber_field_ty(&self, ty: &FiberTyV, field: FieldName) -> Option { + match &**ty { + FiberTyV_::Record(r) => r.get(field).cloned(), + _ => None, + } + } + + /// Check that two fiber types are convertible. + pub fn convertible_fiber_ty<'b>(&self, ty1: &FiberTyV, ty2: &FiberTyV) -> Result<(), D<'b>> { + match (&**ty1, &**ty2) { + (FiberTyV_::Over(o1), FiberTyV_::Over(o2)) => self + .equal_tm(o1, o2) + .map_err(|d| t("over-types lie over different codomain objects: ") + d), + (FiberTyV_::Record(r1), FiberTyV_::Record(r2)) => { + if r1.iter().count() != r2.iter().count() { + return Err(t("instance records have differing shapes")); + } + for ((n1, (_, f1)), (n2, (_, f2))) in r1.iter().zip(r2.iter()) { + if n1 != n2 { + return Err(t(format!("instance field {n1} differs from {n2}"))); + } + self.convertible_fiber_ty(f1, f2)?; + } + Ok(()) + } + (FiberTyV_::Id(ty1, l1, r1), FiberTyV_::Id(ty2, l2, r2)) => { + self.convertible_fiber_ty(ty1, ty2)?; + self.equal_fiber_tm(l1, l2)?; + self.equal_fiber_tm(r1, r2) + } + _ => Err(t("tried to convert between fiber types of different constructors")), + } + } + + /// Check that two fiber terms are equal. Fiber terms are all neutral, + /// so this is structural. + pub fn equal_fiber_tm<'b>(&self, tm1: &FiberTmV, tm2: &FiberTmV) -> Result<(), D<'b>> { + match (&**tm1, &**tm2) { + (FiberTmV_::Var(i1, _, _), FiberTmV_::Var(i2, _, _)) => { + if i1 == i2 { + Ok(()) + } else { + Err(t("fiber variables are not equal")) + } + } + (FiberTmV_::Proj(t1, f1, _), FiberTmV_::Proj(t2, f2, _)) => { + if f1 != f2 { + return Err(t(format!("fiber projections {f1} and {f2} are not equal"))); + } + self.equal_fiber_tm(t1, t2) + } + (FiberTmV_::List(es1), FiberTmV_::List(es2)) => { + if es1.len() != es2.len() { + return Err(t("fiber lists have different lengths")); + } + for (e1, e2) in es1.iter().zip(es2.iter()) { + self.equal_fiber_tm(e1, e2)?; + } + Ok(()) + } + (FiberTmV_::ObApp(n1, a1), FiberTmV_::ObApp(n2, a2)) => { + if n1 != n2 { + return Err(t(format!("object operations {n1} and {n2} are not equal"))); + } + self.equal_fiber_tm(a1, a2) + } + (FiberTmV_::OverApp(p1, _, i1), FiberTmV_::OverApp(p2, _, i2)) => { + // Compare the morphism paths by name. + let names1 = p1.iter().map(|(n, _)| n).collect::>(); + let names2 = p2.iter().map(|(n, _)| n).collect::>(); + if names1 != names2 { + return Err(t("applied codomain morphisms are not equal")); + } + self.equal_fiber_tm(i1, i2) + } + (FiberTmV_::Meta(a), FiberTmV_::Meta(b)) => { + if a == b { + Ok(()) + } else { + Err(t(format!("Holes {a} and {b} are not equal."))) + } + } + _ => Err(t("fiber terms are not equal")), + } } } diff --git a/packages/catlog/src/tt/fiber_elab.rs b/packages/catlog/src/tt/fiber_elab.rs new file mode 100644 index 000000000..f822ec571 --- /dev/null +++ b/packages/catlog/src/tt/fiber_elab.rs @@ -0,0 +1,299 @@ +//! The fiber-elaboration core shared by the text and notebook elaborators. +//! +//! Both elaborators build instances the same way, by introducing fiber variables for +//! generators and sub-instance imports, fiber terms for elements, and `Id` +//! fields for equations. But they report errors through different channels: the +//! [text elaborator](super::text_elab) emits located messages through a +//! `Reporter`, while the [notebook elaborator](super::notebook_elab) collects +//! typed [`InvalidDblModel`](crate::dbl::model::InvalidDblModel) values +//! attributed to cell UUIDs. [`FiberElab`] carries the shared machinery as +//! provided methods, and [`FiberError`] is the neutral currency that each +//! implementor maps into its own channel via [`FiberElab::report_fiber`]. +//! +//! Everything here is `pub(in crate::tt)`. The module boundary enforced today +//! is the crate boundary of a prospective standalone `tt` crate. +//! +//! The *base* world has an analogous but so-far-unextracted duplication: the +//! notebook elaborator's composite-morphism typing (`mor_syn`), equation +//! formation (`equation_cell_ty`), and instantiation specialization each +//! parallel text-side logic, with the same split between `Reporter` strings +//! and typed `InvalidDblModel` values — and the notebook's base errors are +//! *coarser* (bare `DomType`/`CodType` with no detail). This trait plus its +//! neutral error enum is the template for a future `BaseElab`/`BaseError` +//! pass, worth doing when notebook model diagnostics need text-quality +//! precision; the same snapshot byte-stability constraint applies. + +use super::{context::*, eval::*, prelude::*, stx::*, theory::*, val::*}; + +/// Reserved name under which an instance's codomain model is bound as a +/// context variable. +/// +/// It contains a space, so the text elaborator's lexer — which restricts +/// identifiers to alphanumerics and `_` — can never produce it, and notebook +/// cell names are UUIDs; hence a user-declared generator, sub-instance, or +/// field can never shadow the codomain binding. Both elaborators bind the +/// codomain first in an empty context, so fiber values from either pipeline +/// root at the same de Bruijn level and their neutrals compare equal. +pub(in crate::tt) const CODOMAIN_BINDER: &str = "instance self"; + +/// Renders a codomain path for display in error messages. +pub(in crate::tt) fn path_str(path: &[(FieldName, LabelSegment)]) -> String { + path.iter().map(|(_, label)| label.to_string()).collect::>().join(".") +} + +/// An error in fiber elaboration. +/// +/// Unlike the validation enums in [`crate::dbl`] (`InvalidDblModel` and +/// friends), which are QualifiedName-keyed results serialized across the wasm +/// boundary, this enum is elaborator-internal plumbing: it exists only to be +/// mapped into each elaborator's reporting channel by +/// [`FiberElab::report_fiber`] and never escapes `tt` — hence no serde +/// derives. Payloads are pre-formatted strings because the text channel's +/// messages appear verbatim in committed snapshots.. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(in crate::tt) enum FiberError { + /// Reference to an unknown fiber element (generator or import). + UnknownElement(String), + /// Projection out of a fiber element that is not a sub-instance. + ProjNonRecord, + /// Projection of a generator absent from the sub-instance. + UnknownProj(String), + /// Object operation not present in the theory: (operation, theory name). + UnknownObOp(String, String), + /// Object operation applied to a non-element. + ObOpOnNonElement(String), + /// Fiber list element that is not an element over an object. + ListElementNotOver, + /// A hole in a notebook term (an unfilled editor slot). + MissingTerm, + /// Morphism-application argument that is not an element, with its + /// display name when the surface syntax provides one. + ArgNotElement(Option), + /// Codomain field used as a morphism that is not one, by path. + NotAMorphism(String), + /// Morphism applied to an argument over the wrong object. + ArgMismatch { + /// Display path of the morphism. + path: String, + /// The object the argument lies over (quoted). + got: String, + /// The morphism's domain object (quoted). + expected: String, + /// Pretty-printed equality failure. + detail: String, + }, + /// Fiber term whose type is inconvertible with the expected type. + WrongFiberType(String), + /// LHS of a `mor(arg) := target` clause that is not an element. + MappingLhsNotOver, + /// Equation between things that are not fiber elements. + EquationNotOver, + /// Equation sides with inconvertible fiber types. + InconvertibleEquationSides(String), + /// Import of an instance of a different model, by display name. + ImportCodomainMismatch(String), +} + +/// The fiber-elaboration core. +/// +/// Implementors supply context and theory access, meta generation, and an +/// error channel; the provided methods are the machinery both elaborators +/// share. Surface-syntax dispatch (f-notation, notebook instance terms, +/// diagram object references) stays with the implementors, whose arms +/// delegate here. +pub(in crate::tt) trait FiberElab { + /// The elaboration context. + fn ctx(&self) -> &Context; + + /// The elaboration context, mutably. + fn ctx_mut(&mut self) -> &mut Context; + + /// The theory elaboration happens in. + fn elab_theory(&self) -> &Theory; + + /// An evaluator for the current context. + fn evaluator(&self) -> Evaluator<'_>; + + /// A fresh metavariable. + fn fresh_meta(&mut self) -> MetaVar; + + /// Report a fiber-elaboration error through this elaborator's channel. + fn report_fiber(&mut self, err: FiberError); + + /// The definition of the ambient theory. + fn theory_def(&self) -> &TheoryDef { + &self.elab_theory().definition + } + + /// Introduce a fiber variable (a generator or sub-instance import) into + /// the fiber scope, returning its neutral value. + fn intro_fiber(&mut self, name: VarName, label: LabelSegment, ty: FiberTyV) -> FiberTmV { + let ctx = self.ctx_mut(); + let v = FiberTmV::var(ctx.fiber_scope.len().into(), name, label); + ctx.fiber_env = ctx.fiber_env.snoc(v.clone()); + ctx.push_fiber(name, label, ty); + v + } + + /// Look up a fiber variable by name, returning its syntax, value, and + /// fiber type. + fn lookup_fiber_tm(&self, name: VarName) -> Option<(FiberTmS, FiberTmV, FiberTyV)> { + let (i, label, ty) = self.ctx().lookup_fiber(name)?; + Some((FiberTmS::var(i, name, label), self.ctx().fiber_env.get(*i).unwrap().clone(), ty)) + } + + /// A fiber term standing in for a failed synthesis. + fn fiber_syn_hole(&mut self) -> (FiberTmS, FiberTmV, FiberTyV) { + let tm_m = self.fresh_meta(); + let obj_m = self.fresh_meta(); + (FiberTmS::meta(tm_m), FiberTmV::meta(tm_m), FiberTyV::over(BaseTmV::meta(obj_m))) + } + + /// Report an error and return a synthesis hole. + fn fiber_syn_error(&mut self, err: FiberError) -> (FiberTmS, FiberTmV, FiberTyV) { + self.report_fiber(err); + self.fiber_syn_hole() + } + + /// Report an error and return a checking hole. + fn fiber_chk_error(&mut self, err: FiberError) -> (FiberTmS, FiberTmV) { + self.report_fiber(err); + let tm_m = self.fresh_meta(); + (FiberTmS::meta(tm_m), FiberTmV::meta(tm_m)) + } + + /// Whether two codomain models agree closely enough to import an + /// instance of one into an instance of the other: identical top-level + /// field names (in order) and convertible field types. + /// + /// This is deliberately stricter than [`Evaluator::convertible_ty`], + /// which for records is positional and ignores field names and arity + /// — so it would wrongly accept e.g. `[V : Entity, E : Entity]` as + /// convertible with `[W : Entity, F : Entity]`. + fn codomains_match(&self, a: &BaseTyV, b: &BaseTyV) -> bool { + if let (BaseTyV_::Record(r1), BaseTyV_::Record(r2)) = (&**a, &**b) { + let names_a: Vec<_> = r1.fields.iter().map(|(n, _)| n).collect(); + let names_b: Vec<_> = r2.fields.iter().map(|(n, _)| n).collect(); + if names_a != names_b { + return false; + } + } + self.evaluator().convertible_ty(a, b).is_ok() + } + + /// Project a generator out of a sub-instance import, e.g. `we.e`. The + /// field's label comes from the import's record row. + fn fiber_proj( + &mut self, + recv_s: FiberTmS, + recv_v: FiberTmV, + recv_ty: &FiberTyV, + field: FieldName, + ) -> (FiberTmS, FiberTmV, FiberTyV) { + let FiberTyV_::Record(r) = &**recv_ty else { + return self.fiber_syn_error(FiberError::ProjNonRecord); + }; + let Some((label, field_ty)) = r.get_with_label(field) else { + return self.fiber_syn_error(FiberError::UnknownProj(field.to_string())); + }; + let (label, field_ty) = (*label, field_ty.clone()); + ( + FiberTmS::proj(recv_s, field, label), + FiberTmV::proj(recv_v, field, label), + field_ty, + ) + } + + /// Whether the theory has the given object operation, reporting an error + /// if not. Checked by dispatchers *before* synthesizing the argument, to + /// preserve error order. + fn check_ob_op(&mut self, op: VarName) -> bool { + if self.theory_def().basic_ob_op([op].into()).is_none() { + let th = self.elab_theory().name.to_string(); + self.report_fiber(FiberError::UnknownObOp(op.to_string(), th)); + return false; + } + true + } + + /// Apply a theory object-operation to an already-synthesized fiber + /// element, e.g. `@tensor [a, b]`. The resulting element lies over the + /// operation applied to the argument's base object. + fn fiber_ob_app( + &mut self, + op: VarName, + arg_s: FiberTmS, + arg_v: FiberTmV, + arg_ty: &FiberTyV, + ) -> (FiberTmS, FiberTmV, FiberTyV) { + let FiberTyV_::Over(arg_obj) = &**arg_ty else { + return self.fiber_syn_error(FiberError::ObOpOnNonElement(op.to_string())); + }; + let obj = BaseTmV::app(op, arg_obj.clone()); + (FiberTmS::ob_app(op, arg_s), FiberTmV::ob_app(op, arg_v), FiberTyV::over(obj)) + } + + /// Apply an already-resolved codomain morphism to a fiber argument + /// already known to lie over `arg_obj`. The argument's object must equal + /// the morphism's domain object (compared as base objects, so modal + /// domains — lists, tensors — need no special handling); the result lies + /// over the morphism's codomain object. Resolution of the morphism (and + /// the check that the argument is an element at all) stays with the + /// caller, which knows its surface syntax and error order. + fn fiber_mor_app( + &mut self, + path: &[(FieldName, LabelSegment)], + mor_ty: &BaseTyV, + arg_s: FiberTmS, + arg_v: FiberTmV, + arg_obj: &BaseTmV, + ) -> (FiberTmS, FiberTmV, FiberTyV) { + let BaseTyV_::Morphism(_, dom_obj, cod_obj) = &**mor_ty else { + return self.fiber_syn_error(FiberError::NotAMorphism(path_str(path))); + }; + if let Err(e) = self.evaluator().equal_tm(arg_obj, dom_obj) { + let ev = self.evaluator(); + let err = FiberError::ArgMismatch { + path: path_str(path), + got: ev.quote_tm(arg_obj).to_string(), + expected: ev.quote_tm(dom_obj).to_string(), + detail: e.pretty().to_string(), + }; + return self.fiber_syn_error(err); + } + let cod_s = self.evaluator().quote_tm(cod_obj); + ( + FiberTmS::over_app(path.to_vec(), cod_s, arg_s), + FiberTmV::over_app(path.to_vec(), cod_obj.clone(), arg_v), + FiberTyV::over(cod_obj.clone()), + ) + } + + /// Check a synthesized fiber term against an expected fiber type. + fn check_fiber( + &mut self, + syn: (FiberTmS, FiberTmV, FiberTyV), + expected: &FiberTyV, + ) -> (FiberTmS, FiberTmV) { + let (s, v, ty) = syn; + if let Err(e) = self.evaluator().convertible_fiber_ty(&ty, expected) { + return self.fiber_chk_error(FiberError::WrongFiberType(e.pretty().to_string())); + } + (s, v) + } + + /// Assemble an equation ([`Id`](FiberTyS_::Id)) field from an + /// already-checked pair of sides, the LHS lying over `over_obj`. + fn fiber_id_field( + &self, + lhs_ty: &FiberTyV, + over_obj: &BaseTmV, + lhs_s: FiberTmS, + lhs_v: FiberTmV, + rhs_s: FiberTmS, + rhs_v: FiberTmV, + ) -> (FiberTyS, FiberTyV) { + let over_s = FiberTyS::over(self.evaluator().quote_tm(over_obj)); + (FiberTyS::id(over_s, lhs_s, rhs_s), FiberTyV::id(lhs_ty.clone(), lhs_v, rhs_v)) + } +} diff --git a/packages/catlog/src/tt/mod.rs b/packages/catlog/src/tt/mod.rs index b0aedd6d4..d5c379631 100644 --- a/packages/catlog/src/tt/mod.rs +++ b/packages/catlog/src/tt/mod.rs @@ -18,8 +18,8 @@ //! //! | | Syntax | Value | //! |------|--------|-------| -//! | Term | [TmS] | [TmV] | -//! | Type | [TyS] | [TyV] | +//! | Term | [BaseTmS] | [BaseTmV] | +//! | Type | [BaseTyS] | [BaseTyV] | //! //! Evaluation is the process of going from syntax to values. Evaluation is used to //! *normalize types*. We need to normalize types because there are many different @@ -36,28 +36,28 @@ //! ```ignore //! type BwdIdx = usize; //! -//! enum TmS { +//! enum BaseTmS { //! Var(BwdIdx), -//! App(TmS, TmS), -//! Lam(TmS) +//! App(BaseTmS, BaseTmS), +//! Lam(BaseTmS) //! } //! //! type Env = Bwd; //! //! struct Closure { //! env: Env, -//! body: TmS +//! body: BaseTmS //! } //! -//! fn eval(env: Env, tm_s: TmS) -> Closure { +//! fn eval(env: Env, tm_s: BaseTmS) -> Closure { //! match tm_s { -//! TmS::Var(i) => env.lookup(i), -//! TmS::App(f, x) => { +//! BaseTmS::Var(i) => env.lookup(i), +//! BaseTmS::App(f, x) => { //! let fv = eval(env, f); //! let xv = eval(env, x); //! eval(fv.env.snoc(xv), fv.body) //! } -//! TmS::Lam(body) => Closure { env, body } +//! BaseTmS::Lam(body) => Closure { env, body } //! } //! } //! ``` @@ -71,43 +71,43 @@ //! ```ignore //! type FwdIdx = usize; //! -//! enum TmV { +//! enum BaseTmV { //! // f a₁ ... aₙ -//! Neu(FwdIdx, Bwd), +//! Neu(FwdIdx, Bwd), //! Clo(Closure) //! } //! -//! impl TmV { -//! fn app(self, arg: TmV) -> TmV { +//! impl BaseTmV { +//! fn app(self, arg: BaseTmV) -> BaseTmV { //! match self { -//! TmV::Neu(head, args) => TmV::Neu(head, args.snoc(arg)), -//! TmV::Clo(clo) => eval(clo.env.snoc(arg), clo.body) +//! BaseTmV::Neu(head, args) => BaseTmV::Neu(head, args.snoc(arg)), +//! BaseTmV::Clo(clo) => eval(clo.env.snoc(arg), clo.body) //! } //! } //! } //! -//! type Env = Bwd; +//! type Env = Bwd; //! -//! fn eval(env: Env, tm_s: TmS) -> Closure { +//! fn eval(env: Env, tm_s: BaseTmS) -> Closure { //! match tm_s { -//! TmS::Var(i) => env.lookup(i), -//! TmS::App(f, x) => { +//! BaseTmS::Var(i) => env.lookup(i), +//! BaseTmS::App(f, x) => { //! let fv = eval(env, f); //! let xv = eval(env, x); //! fv.app(xv) //! } -//! TmS::Lam(body) => TmV::Clo(Closure { env, body }) +//! BaseTmS::Lam(body) => BaseTmV::Clo(Closure { env, body }) //! } //! } //! -//! fn quote(scope_len: usize, tm_v: TmV) -> TmS { +//! fn quote(scope_len: usize, tm_v: BaseTmV) -> BaseTmS { //! match tm_v { -//! TmV::Neu(f, xs) => -//! xs.iter.fold(TmS::Var(scope_len - f - 1), |f, x| TmS::App(f, x)), -//! TmV::Clo(clo) => { -//! let x_v = TmV::Neu(scope_len, Bwd::Nil); +//! BaseTmV::Neu(f, xs) => +//! xs.iter.fold(BaseTmS::Var(scope_len - f - 1), |f, x| BaseTmS::App(f, x)), +//! BaseTmV::Clo(clo) => { +//! let x_v = BaseTmV::Neu(scope_len, Bwd::Nil); //! let body_v = eval(clo.env.snoc(x_v), clo.body); -//! TmS::Lam(quote(scope_len + 1, body_v)); +//! BaseTmS::Lam(quote(scope_len + 1, body_v)); //! } //! } //! } @@ -134,7 +134,7 @@ //! the following double models. //! //! ```text -//! type Graph := [ +//! model Graph := [ //! E : Entity, //! V : Entity, //! src : (Id Entity)[E, V], @@ -142,7 +142,7 @@ //! ] //! /# declared: Graph //! -//! type Graph2 := [ +//! model Graph2 := [ //! V : Entity, //! g1 : Graph & [ .V := V ], //! g2 : Graph & [ .V := V ] @@ -195,6 +195,7 @@ pub mod batch; pub mod context; pub mod eval; +pub(in crate::tt) mod fiber_elab; pub mod modelgen; pub mod notebook_elab; pub mod prelude; @@ -202,7 +203,6 @@ pub mod stx; pub mod text_elab; pub mod theory; pub mod toplevel; -pub mod util; pub mod val; pub mod wd; diff --git a/packages/catlog/src/tt/modelgen.rs b/packages/catlog/src/tt/modelgen.rs index 3b5bcb3b0..92f868941 100644 --- a/packages/catlog/src/tt/modelgen.rs +++ b/packages/catlog/src/tt/modelgen.rs @@ -4,17 +4,20 @@ use all_the_same::all_the_same; use derive_more::{From, TryInto}; use tattle::display::SourceInfo; +use std::rc::Rc; + use super::{eval::*, prelude::*, text_elab, theory::*, toplevel::*, val::*}; use crate::dbl::{ - discrete, discrete_tabulator, modal, - model::{DblModel, DblModelPrinter, MutDblModel}, + discrete::{self, DiscreteDblModelInstance, DiscreteInstanceTerm}, + discrete_tabulator, modal, + model::{DblModel, DblModelPrinter, FpDblModel, MutDblModel}, theory::{DblTheory, DblTheoryKind, NonUnital, Unital}, }; use crate::one::{ Category, path::{Path, PathEq}, }; -use crate::zero::{Namespace, QualifiedName}; +use crate::zero::{Namespace, QualifiedName, SkelColumn}; /// A model generated by DoubleTT. /// @@ -91,7 +94,7 @@ impl Model { /// Generates a model from a type. /// /// Precondition: `ty` must be valid in the empty context. - pub fn from_ty(toplevel: &Toplevel, th: &TheoryDef, ty: &TyV) -> (Self, Namespace) { + pub fn from_ty(toplevel: &Toplevel, th: &TheoryDef, ty: &BaseTyV) -> (Self, Namespace) { let mut generator = ModelGenerator::new(toplevel, th); let namespace = generator.generate(ty); (generator.model, namespace) @@ -215,7 +218,7 @@ impl<'a> ModelGenerator<'a> { Self { eval, theory, model } } - fn generate(&mut self, ty: &TyV) -> Namespace { + fn generate(&mut self, ty: &BaseTyV) -> Namespace { let tm_n; (tm_n, self.eval) = self.eval.bind_self(ty.clone()); let tm_v = self.eval.eta_neu(&tm_n, ty); @@ -236,7 +239,7 @@ impl<'a> ModelGenerator<'a> { /// Constructs an application of an object operation, if the theory is modal. /// /// Returns the constructed object along with the expected object type of the result. - fn ob_app(&self, name: &NameSegment, tm_v: &TmV) -> Option<(Ob, ObType)> { + fn ob_app(&self, name: &NameSegment, tm_v: &BaseTmV) -> Option<(Ob, ObType)> { let name: QualifiedName = [*name].into(); match &self.model { Model::Discrete(_) | Model::DiscreteTab(_) => None, @@ -249,7 +252,7 @@ impl<'a> ModelGenerator<'a> { &self, model: &modal::ModalDblModel, name: QualifiedName, - tm_v: &TmV, + tm_v: &BaseTmV, ) -> Option<(Ob, ObType)> { let theory = model.theory(); let op = modal::ModalObOp::generator(name.clone()); @@ -279,10 +282,10 @@ impl<'a> ModelGenerator<'a> { /// Attempts to make an object of a model from a term. /// /// Returns the object together with the appropriate type. - fn make_ob_synth_type(&self, val: &TmV) -> Option<(Ob, ObType)> { + fn make_ob_synth_type(&self, val: &BaseTmV) -> Option<(Ob, ObType)> { match &**val { - TmV_::Neu(n, ty_v) => { - let TyV_::Object(ob_type) = &**ty_v else { + BaseTmV_::Neu(n, ty_v) => { + let BaseTyV_::Object(ob_type) = &**ty_v else { return None; }; let name = n.to_qualified_name(); @@ -295,8 +298,8 @@ impl<'a> ModelGenerator<'a> { }; Some((ob, ob_type.clone())) } - TmV_::App(name, tm_v) => self.ob_app(name, tm_v), - TmV_::Tab(mor_tm_v) => { + BaseTmV_::App(name, tm_v) => self.ob_app(name, tm_v), + BaseTmV_::Tab(mor_tm_v) => { let (mor, mor_type) = self.synth_mor(mor_tm_v)?; Some((self.model.tabulated(mor)?, self.theory.tabulator(mor_type)?)) } @@ -307,10 +310,10 @@ impl<'a> ModelGenerator<'a> { /// Attempts to make an object of a model from a term. /// /// Also checks that the term constructs an object of the given type, returning only the object if successful. - fn make_ob_check_type(&self, val: &TmV, ob_type: &ObType) -> Option { + fn make_ob_check_type(&self, val: &BaseTmV, ob_type: &ObType) -> Option { match &**val { // ob_type checked recursively. - TmV_::List(elems) => { + BaseTmV_::List(elems) => { let el_type = ob_type.clone().list_arg()?; let elems: Option> = elems.iter().map(|tm| self.make_ob_check_type(tm, &el_type)).collect(); @@ -326,21 +329,21 @@ impl<'a> ModelGenerator<'a> { /// Attempts to make a morphism of a model from a term. /// /// Also returns the expected morphism type of the result. - fn synth_mor(&self, val: &TmV) -> Option<(Mor, MorType)> { + fn synth_mor(&self, val: &BaseTmV) -> Option<(Mor, MorType)> { match &**val { - TmV_::Neu(n, ty_v) => { - let TyV_::Morphism(mor_type, _, _) = &**ty_v else { + BaseTmV_::Neu(n, ty_v) => { + let BaseTyV_::Morphism(mor_type, _, _) = &**ty_v else { return None; }; let name = n.to_qualified_name(); Some((self.mor_generator(name), mor_type.clone())) } - TmV_::Id(x) => { + BaseTmV_::Id(x) => { let (dom, dom_type) = self.make_ob_synth_type(x)?; let mor_type = self.theory.hom_type(dom_type)?; Some((self.model.id(dom), mor_type)) } - TmV_::Compose(f, g) => { + BaseTmV_::Compose(f, g) => { let (mf, mtf) = self.synth_mor(f)?; let (mg, mtg) = self.synth_mor(g)?; Some((self.model.compose2(mf, mg), self.theory.compose_types2(mtf, mtg)?)) @@ -353,24 +356,29 @@ impl<'a> ModelGenerator<'a> { /// /// At this time, all morphism constructors allow for type synthesis, but /// eventually this will change. - fn make_mor(&self, val: &TmV, mor_type: &MorType) -> Option { + fn make_mor(&self, val: &BaseTmV, mor_type: &MorType) -> Option { let (mor, mt) = self.synth_mor(val)?; (mt == *mor_type).then_some(mor) } - fn extract(&mut self, prefix: Vec, val: &TmV, ty: &TyV) -> Option { + fn extract( + &mut self, + prefix: Vec, + val: &BaseTmV, + ty: &BaseTyV, + ) -> Option { match &**ty { - TyV_::Object(ot) => { + BaseTyV_::Object(ot) => { self.model.add_ob(prefix.into(), ot.clone()); None } - TyV_::Morphism(mt, dom, cod) => { + BaseTyV_::Morphism(mt, dom, cod) => { let dom = self.make_ob_check_type(dom, &self.theory.src_type(mt))?; let cod = self.make_ob_check_type(cod, &self.theory.tgt_type(mt))?; self.model.add_mor(prefix.into(), dom, cod, mt.clone()); None } - TyV_::Record(r) => { + BaseTyV_::Record(r) => { let mut namespace = Namespace::new_for_uuid(); for (name, (label, _)) in r.fields.iter() { let mut prefix = prefix.clone(); @@ -386,9 +394,9 @@ impl<'a> ModelGenerator<'a> { } Some(namespace) } - TyV_::Sing(_, _) => None, - TyV_::Id(mor_ty, lhs, rhs) => { - let TyV_::Morphism(mt, _, _) = &**mor_ty else { + BaseTyV_::Sing(_, _) => None, + BaseTyV_::Id(mor_ty, lhs, rhs) => { + let BaseTyV_::Morphism(mt, _, _) = &**mor_ty else { return None; }; if let (Some(lhs), Some(rhs)) = (self.make_mor(lhs, mt), self.make_mor(rhs, mt)) { @@ -396,8 +404,774 @@ impl<'a> ModelGenerator<'a> { } None } - TyV_::Unit => None, - TyV_::Meta(_) => None, + BaseTyV_::Meta(_) => None, + } + } + + /// Extracts a [`ModelInstance`] from the fiber record of an instance body, + /// dispatching on the doctrine of the (already generated) codomain model. + fn instance( + &self, + fields: &Row, + namespace: &mut Namespace, + ) -> Result { + match &self.model { + Model::Discrete(model) => { + let mut instance = DiscreteDblModelInstance::new(Rc::new((**model).clone())); + extract_instance_record(&mut instance, namespace, &[], fields)?; + Ok(ModelInstance::Discrete(instance)) + } + Model::DiscreteTab(_) => { + Err("instance generation does not support discrete tabulator theories".into()) + } + Model::ModalUnital(model) => { + Ok(ModelInstance::ModalUnital(self.modal_instance(model, namespace, fields)?)) + } + Model::ModalNonUnital(model) => { + Ok(ModelInstance::ModalNonUnital(self.modal_instance(model, namespace, fields)?)) + } + } + } + + /// Builds a modal instance over the given codomain model by walking the + /// fiber record. + fn modal_instance( + &self, + model: &modal::ModalDblModel, + namespace: &mut Namespace, + fields: &Row, + ) -> Result, String> { + let mut instance = modal::ModalDblModelInstance::new(Rc::new(model.clone())); + self.extract_modal_record(&mut instance, namespace, &[], fields)?; + Ok(instance) + } + + /// Registers the contents of an instance (a fiber record) into a modal + /// model instance, recursing into sub-instance imports under their prefix. + /// + /// Two passes, matching [`extract_instance_record`]: generators and + /// sub-instances first, then the equation ([`Id`](FiberTyV_::Id)) fields, + /// so every generator a term may mention is already registered. + fn extract_modal_record( + &self, + instance: &mut modal::ModalDblModelInstance, + namespace: &mut Namespace, + prefix: &[NameSegment], + fields: &Row, + ) -> Result<(), String> { + for (name, (label, field_ty)) in fields.iter() { + match &**field_ty { + FiberTyV_::Over(obj) => { + if let NameSegment::Uuid(uuid) = name { + namespace.set_label(*uuid, *label); + } + let mut qsegs = prefix.to_vec(); + qsegs.push(*name); + let qname: QualifiedName = qsegs.into(); + let fiber = self.modal_fiber_ob(obj)?; + instance.add_generator(qname, fiber); + } + FiberTyV_::Record(sub_fields) => { + if let NameSegment::Uuid(uuid) = name { + namespace.set_label(*uuid, *label); + } + // Labels of the sub-instance's own generators live in an + // inner namespace keyed by the import's name, mirroring + // the structure of the flattened qualified names. + let mut sub_ns = Namespace::new_for_uuid(); + let mut sub_prefix = prefix.to_vec(); + sub_prefix.push(*name); + self.extract_modal_record(instance, &mut sub_ns, &sub_prefix, sub_fields)?; + namespace.add_inner(*name, sub_ns); + } + FiberTyV_::Id(_, _, _) => {} + } + } + for (_, (_, field_ty)) in fields.iter() { + if let FiberTyV_::Id(eq_ty, lhs, rhs) = &**field_ty { + let ob_type = self.modal_equation_ob_type(eq_ty)?; + let lhs_t = self.modal_instance_term(&*instance, lhs, &ob_type, prefix)?; + let rhs_t = self.modal_instance_term(&*instance, rhs, &ob_type, prefix)?; + instance.add_equation(lhs_t, rhs_t); + } + } + Ok(()) + } + + /// Converts the codomain object a generator lies over into a [`ModalOb`]. + fn modal_fiber_ob(&self, obj: &BaseTmV) -> Result { + let (ob, _) = self.make_ob_synth_type(obj).ok_or_else(|| { + "instance generator lies over an object this doctrine cannot yet extract".to_string() + })?; + ob.try_into() + .map_err(|_| "expected a modal object as a generator's fiber".to_string()) + } + + /// The object type an equation lives over (its `Over` fiber type). + fn modal_equation_ob_type(&self, eq_ty: &FiberTyV) -> Result { + let FiberTyV_::Over(obj) = &**eq_ty else { + return Err("instance equation is not over an object".into()); + }; + let (_, ob_type) = self + .make_ob_synth_type(obj) + .ok_or_else(|| "cannot determine the type of an instance equation".to_string())?; + Ok(ob_type) + } + + /// Converts a fiber term into a [`ModalInstanceTerm`]. + fn modal_instance_term( + &self, + instance: &modal::ModalDblModelInstance, + tm: &FiberTmV, + expected: &ObType, + prefix: &[NameSegment], + ) -> Result { + let (mor, base) = self.modal_mor_base(instance, tm, expected, prefix)?; + Ok(modal::ModalInstanceTerm { mor, base }) + } + + /// The heart of modal term extraction: converts a fiber term into a + /// morphism applied to a base of generators, threading the expected object + /// type down so list modalities can be recovered. + /// + /// The returned morphism is an identity exactly when the term is a pure + /// base (generators/lists with no applied morphisms), keeping terms in the + /// flat normal form of [`ModalInstanceTerm`]: composition and list-tupling + /// of morphisms encountered inside list elements are pushed into the + /// morphism (via `Composite`/`List`) rather than nesting applications. + fn modal_mor_base( + &self, + instance: &modal::ModalDblModelInstance, + tm: &FiberTmV, + expected: &ObType, + prefix: &[NameSegment], + ) -> Result<(modal::ModalMor, modal::ModalInstanceBase), String> { + use modal::{ModalInstanceBase, ModalMor, ModalOb, Modality, MorListData}; + match &**tm { + FiberTmV_::Var(_, _, _) | FiberTmV_::Proj(_, _, _) => { + let mut segs = prefix.to_vec(); + segs.extend(fiber_full_name(tm)?); + let qname: QualifiedName = segs.into(); + let fiber = instance + .fiber_of(&qname) + .ok_or_else(|| format!("instance term mentions unknown generator {qname}"))?; + let id = instance.model().id(fiber.clone()); + Ok((id, ModalInstanceBase::Generator(qname))) + } + FiberTmV_::List(elems) => { + let (modality, el_type) = expected + .clone() + .mode_app() + .ok_or_else(|| "expected a modal list type for a list term".to_string())?; + let Modality::List(list_ty) = modality else { + return Err("expected a list modality for a list term".into()); + }; + let mut mors = Vec::with_capacity(elems.len()); + let mut bases = Vec::with_capacity(elems.len()); + for elem in elems { + let (m, b) = self.modal_mor_base(instance, elem, &el_type, prefix)?; + mors.push(m); + bases.push(b); + } + let base = ModalInstanceBase::List(list_ty, bases); + // If every element is a pure base, the whole list is too, and + // the morphism is the identity on the list object. + let identity_obs: Option> = + mors.iter().map(modal::modal_mor_as_identity).collect(); + if let Some(objs) = identity_obs { + let list_ob = ModalOb::List(list_ty, objs.into_iter().cloned().collect()); + Ok((instance.model().id(list_ob), base)) + } else { + let data = match list_ty { + modal::List::Plain => MorListData::Plain(), + modal::List::Symmetric => { + MorListData::Symmetric(SkelColumn::new((0..mors.len()).collect())) + } + other => { + return Err(format!( + "instance terms do not yet support the {other:?} list modality" + )); + } + }; + Ok((ModalMor::List(data, mors), base)) + } + } + FiberTmV_::OverApp(path, _cod, inner) => { + let qname: QualifiedName = + path.iter().map(|(seg, _)| *seg).collect::>().into(); + let mor = ModalMor::Generator(qname.clone()); + let mor_type = MorType::Modal(instance.model().mor_generator_type(&qname)); + let dom_ty = self.theory.src_type(&mor_type); + let (inner_mor, base) = self.modal_mor_base(instance, inner, &dom_ty, prefix)?; + let full = if modal::modal_mor_as_identity(&inner_mor).is_some() { + mor + } else { + instance.model().compose2(inner_mor, mor) + }; + Ok((full, base)) + } + FiberTmV_::ObApp(op, inner) => { + let op_name: QualifiedName = [*op].into(); + // Recurse into the argument at the object operation's domain + // type, so a list argument recovers its modality. + let ob_op = modal::ModalObOp::generator(op_name.clone()); + let dom_ty: ObType = instance.model().theory().ob_op_dom(&ob_op).into(); + let (inner_mor, inner_base) = + self.modal_mor_base(instance, inner, &dom_ty, prefix)?; + // If the argument is a pure base (identity morphism), the + // object operation lands on data and the morphism stays the + // identity on the resulting `App` object. Otherwise the + // operation acts functorially on the argument's morphism, + // yielding a `HomApp`. + let mor = match modal::modal_mor_as_identity(&inner_mor).cloned() { + Some(inner_ob) => { + let app_ob = ModalOb::App(Box::new(inner_ob), op_name.clone()); + instance.model().id(app_ob) + } + None => ModalMor::HomApp(Box::new(inner_mor.into()), op_name.clone()), + }; + let base = ModalInstanceBase::ObApp(op_name, Box::new(inner_base)); + Ok((mor, base)) + } + FiberTmV_::Meta(_) => Err("instance term contains an unresolved metavariable".into()), + } + } +} + +/// An instance of a model generated by DoubleTT. +/// +/// Like [`Model`], this boxes the per-doctrine concrete instance types +/// behind one enum. +pub enum ModelInstance { + /// An instance of a discrete double model. + Discrete(DiscreteDblModelInstance), + /// An instance of a unital modal double model. + ModalUnital(modal::ModalDblModelInstance), + /// An instance of a non-unital modal double model. + ModalNonUnital(modal::ModalDblModelInstance), +} + +/// Generates a [`ModelInstance`] from an elaborated [`Instance`] declaration. +/// +/// Walks the instance's fiber [`Record`](FiberTyV_::Record), registering +/// each generator with its fiber, each equation as a pair of instance +/// terms, and each sub-instance's contents under the appropriate prefix. +/// +/// The codomain model is built with a `ModelGenerator`, which is then +/// reused to type the instance's generators and equation terms (this is how +/// modal list modalities are recovered). +pub fn instance_from_def( + toplevel: &Toplevel, + th: &TheoryDef, + inst: &Instance, +) -> Result<(ModelInstance, Namespace), String> { + let mut generator = ModelGenerator::new(toplevel, th); + // Seed the namespace with the codomain model's labels, so that names of + // codomain objects and morphisms appearing in fibers and terms resolve + // alongside the instance's own generators. + let mut namespace = generator.generate(&inst.codomain); + let FiberTyV_::Record(fields) = &*inst.val else { + return Err("expected an instance (a fiber record)".into()); + }; + let instance = generator.instance(fields, &mut namespace)?; + Ok((instance, namespace)) +} + +/// The flat normal form of a single instance term, as produced by +/// [`normalize_instance_term`]: a composite model morphism applied to a base +/// of generators. Doctrine-specific because each has its own term type. +pub enum NormalizedInstanceTerm { + /// A term of an instance of a discrete double model. + Discrete(DiscreteInstanceTerm), + /// A term of an instance of a modal double model (unital or non-unital). + Modal(modal::ModalInstanceTerm), +} + +impl NormalizedInstanceTerm { + /// Renders the term in its flat normal form `mor @ base`, exposing the + /// single (composite) model morphism acting on a base of generators. A + /// term whose morphism is the identity (a pure base) is rendered as just + /// its base. This is deliberately distinct from the applicative + /// reconstruction used in instance summaries: the point of `norm` is to + /// *show* the composite, e.g. `t(@tensor [s(x0), s(x0)])` normalizing to + /// `(@tensor [s, s] ; t) @ @tensor [x0, x0]`. + pub fn render(&self) -> String { + match self { + NormalizedInstanceTerm::Discrete(t) => match &t.path { + Path::Id(_) => format!("{}", t.base), + Path::Seq(edges) => { + let parts: Vec<_> = edges.iter().map(|mor| format!("{mor}")).collect(); + if parts.len() == 1 { + format!("{} @ {}", parts[0], t.base) + } else { + format!("({}) @ {}", parts.join(" ; "), t.base) + } + } + }, + NormalizedInstanceTerm::Modal(t) => { + let base = render_modal_base(&t.base); + match modal::modal_mor_as_identity(&t.mor) { + Some(_) => base, + None => format!("{} @ {}", render_modal_mor(&t.mor), base), + } + } + } + } +} + +/// Renders the base of a flat modal term: generators by name, lists as +/// `[a, b, …]`, and object operations as `@op `. +fn render_modal_base(base: &modal::ModalInstanceBase) -> String { + match base { + modal::ModalInstanceBase::Generator(name) => format!("{name}"), + modal::ModalInstanceBase::List(_, bases) => { + let inner: Vec<_> = bases.iter().map(render_modal_base).collect(); + format!("[{}]", inner.join(", ")) + } + modal::ModalInstanceBase::ObApp(op, inner) => { + format!("@{op} {}", render_modal_base(inner)) + } + } +} + +/// Renders a model morphism in a flat term. A composite of length ≥ 2 is +/// shown parenthesized as `(m1 ; m2 ; …)`, applied left-to-right; the +/// functorial `HomApp` of an object operation shows as `@op `. +fn render_modal_mor(mor: &modal::ModalMor) -> String { + match mor { + modal::ModalMor::Generator(name) => format!("{name}"), + modal::ModalMor::Composite(path) => { + let parts = render_modal_mor_path(path); + match parts.len() { + 0 => "id".to_string(), + 1 => parts.into_iter().next().unwrap(), + _ => format!("({})", parts.join(" ; ")), + } + } + modal::ModalMor::App(path, op) => { + format!("{op}({})", render_modal_mor_path(path).join(" ; ")) + } + modal::ModalMor::HomApp(path, op) => { + format!("@{op} {}", render_modal_mor_path(path).join(" ; ")) + } + modal::ModalMor::List(_, mors) => { + let inner: Vec<_> = mors.iter().map(render_modal_mor).collect(); + format!("[{}]", inner.join(", ")) + } + } +} + +/// The morphisms along a path, each flat-rendered; empty for an identity path. +fn render_modal_mor_path(path: &Path) -> Vec { + match path { + Path::Id(_) => Vec::new(), + Path::Seq(edges) => edges.iter().map(render_modal_mor).collect(), + } +} + +/// Normalizes a single already-elaborated fiber term `tm` (lying over the +/// codomain object `over`) in the context of the instance `inst`, returning +/// its flat normal form. +/// +/// This is what backs `norm [inst] `: the term is elaborated against +/// `inst`'s fiber scope elsewhere (in `text_elab`), then handed here to run +/// the same extraction that turns an instance body's equations into +/// [`modal::ModalInstanceTerm`]s — which is where nested morphism applications +/// get composed into a single (`Composite`/`List`) morphism. +pub fn normalize_instance_term( + toplevel: &Toplevel, + th: &TheoryDef, + inst: &Instance, + tm: &FiberTmV, + over: &BaseTmV, +) -> Result { + let mut generator = ModelGenerator::new(toplevel, th); + generator.generate(&inst.codomain); + let FiberTyV_::Record(fields) = &*inst.val else { + return Err("expected an instance (a fiber record)".into()); + }; + // Build the instance first, so every generator the term may mention is + // registered before we extract (extraction resolves generators by name). + let mut namespace = Namespace::new_for_uuid(); + match generator.instance(fields, &mut namespace)? { + ModelInstance::Discrete(instance) => { + let term = fiber_tm_to_discrete_instance_term(&instance, tm, &[])?; + Ok(NormalizedInstanceTerm::Discrete(term)) + } + ModelInstance::ModalUnital(instance) => { + let (_, ob_type) = generator + .make_ob_synth_type(over) + .ok_or_else(|| "cannot determine the object the term lies over".to_string())?; + let term = generator.modal_instance_term(&instance, tm, &ob_type, &[])?; + Ok(NormalizedInstanceTerm::Modal(term)) + } + ModelInstance::ModalNonUnital(instance) => { + let (_, ob_type) = generator + .make_ob_synth_type(over) + .ok_or_else(|| "cannot determine the object the term lies over".to_string())?; + let term = generator.modal_instance_term(&instance, tm, &ob_type, &[])?; + Ok(NormalizedInstanceTerm::Modal(term)) + } + } +} + +/// Register the contents of an instance (a fiber record) into the model +/// instance under construction, recursing into sub-instance imports under +/// their prefix. +/// +/// Two passes so that every generator — including those of sub-instances +/// — is registered before any equation that may mention it: first the +/// [`Over`](FiberTyV_::Over) generator fields and [`Record`](FiberTyV_::Record) +/// sub-instances, then the [`Id`](FiberTyV_::Id) equation fields. +fn extract_instance_record( + instance: &mut DiscreteDblModelInstance, + namespace: &mut Namespace, + prefix: &[NameSegment], + fields: &Row, +) -> Result<(), String> { + for (name, (label, field_ty)) in fields.iter() { + match &**field_ty { + FiberTyV_::Over(obj) => { + if let NameSegment::Uuid(uuid) = name { + namespace.set_label(*uuid, *label); + } + let mut qsegs = prefix.to_vec(); + qsegs.push(*name); + let qname: QualifiedName = qsegs.into(); + // The fiber is the codomain object the generator lies over. + // For a plain generator that is a projection `self.V`, whose + // qualified name is `V`. Modal objects (lists, tensors) are + // not yet supported by discrete model generation. + let BaseTmV_::Neu(n, _) = &**obj else { + return Err("model generation does not yet support generators over a modal \ + object (list/tensor)" + .into()); + }; + instance.add_generator(qname, n.to_qualified_name()); + } + FiberTyV_::Record(sub_fields) => { + if let NameSegment::Uuid(uuid) = name { + namespace.set_label(*uuid, *label); + } + // Labels of the sub-instance's own generators live in an + // inner namespace keyed by the import's name, mirroring the + // structure of the flattened qualified names. + let mut sub_ns = Namespace::new_for_uuid(); + let mut sub_prefix = prefix.to_vec(); + sub_prefix.push(*name); + extract_instance_record(instance, &mut sub_ns, &sub_prefix, sub_fields)?; + namespace.add_inner(*name, sub_ns); + } + FiberTyV_::Id(_, _, _) => {} + } + } + for (_, (_, field_ty)) in fields.iter() { + if let FiberTyV_::Id(_, lhs, rhs) = &**field_ty { + let lhs_t = fiber_tm_to_discrete_instance_term(instance, lhs, prefix)?; + let rhs_t = fiber_tm_to_discrete_instance_term(instance, rhs, prefix)?; + instance.add_equation(lhs_t, rhs_t); + } + } + Ok(()) +} + +/// Convert a fiber term into a [`DiscreteInstanceTerm`], prefixing each +/// generator name with the path into any enclosing sub-instances. +/// +/// Nested [`OverApp`](FiberTmV_::OverApp)s are flattened into a single +/// morphism path applied to the generator at the leaf, matching the flat +/// canonical shape of [`DiscreteInstanceTerm`]. +fn fiber_tm_to_discrete_instance_term( + instance: &DiscreteDblModelInstance, + tm: &FiberTmV, + prefix: &[NameSegment], +) -> Result { + // Walk outer-to-inner, recording each applied morphism. + let mut mors_outer_first: Vec = Vec::new(); + let mut cur = tm; + let base: QualifiedName = loop { + match &**cur { + FiberTmV_::OverApp(mor_path, _, inner) => { + let name: QualifiedName = + mor_path.iter().map(|(seg, _)| *seg).collect::>().into(); + mors_outer_first.push(name); + cur = inner; + } + _ => { + let mut segs = prefix.to_vec(); + segs.extend(fiber_full_name(cur)?); + break segs.into(); + } + } + }; + // Path order is innermost-first (apply first goes first). + mors_outer_first.reverse(); + let path = match Path::from_vec(mors_outer_first) { + Some(p) => p, + None => { + let fiber = instance + .fiber_of(&base) + .ok_or_else(|| format!("instance term mentions unknown generator {base}"))?; + Path::Id(fiber.clone()) } + }; + Ok(DiscreteInstanceTerm { path, base }) +} + +/// Read off the full name of a fiber term that is a generator or a chain +/// of projections out of a sub-instance import (e.g. `we.e`): the leading +/// variable contributes a segment (it is itself a generator/import name), +/// followed by each projected field. +fn fiber_full_name(tm: &FiberTmV) -> Result, String> { + let mut segments = Vec::new(); + let mut cur = tm; + loop { + match &**cur { + FiberTmV_::Var(_, name, _) => { + segments.push(*name); + break; + } + FiberTmV_::Proj(inner, f, _) => { + segments.push(*f); + cur = inner; + } + _ => return Err("expected a fiber generator or projection".into()), + } + } + segments.reverse(); + Ok(segments) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tt::text_elab::{TT_PARSE_CONFIG, TopElabResult, TopElaborator}; + use crate::tt::theory::std_theories; + + fn elaborate_to_toplevel(src: &str) -> Toplevel { + let reporter = Reporter::new(); + let mut toplevel = Toplevel::new(std_theories()); + let _ = TT_PARSE_CONFIG.with_parsed_top(src, reporter.clone(), |topntns| { + let mut topelab = TopElaborator::new(reporter.clone()); + for topntn in topntns.iter() { + if let Some(TopElabResult::Declaration(name, decl)) = + topelab.elab(&toplevel, topntn) + { + toplevel.declarations.insert(name, decl); + } + } + Some(()) + }); + assert!(!reporter.errored(), "elaboration produced errors"); + toplevel + } + + #[test] + fn instance_over_weighted_graph() { + let src = r#" +set_theory ThSchema + +model WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] + +instance I : WeightedGraph := [ + V := [v], + E := [e], + src(e) := v +] +"#; + let toplevel = elaborate_to_toplevel(src); + let def = match toplevel.declarations.get(&name_seg("I")) { + Some(TopDecl::Instance(i)) => i.clone(), + _ => panic!("expected I to be an instance declaration"), + }; + let (instance, _ns) = instance_from_def(&toplevel, &def.theory.definition, &def).unwrap(); + let ModelInstance::Discrete(instance) = instance else { + panic!("expected a discrete instance"); + }; + + let e_qname: QualifiedName = vec![name_seg("e")].into(); + let e_fiber: QualifiedName = vec![name_seg("E")].into(); + assert_eq!(instance.fiber_of(&e_qname), Some(&e_fiber)); + assert_eq!(instance.equations().count(), 1); + } + + /// An instance carrying fiber equations, imported as a sub-instance. + /// + /// `Loop` is itself a fiber record carrying an `Id`-typed field per + /// fiber equation alongside its `e`/`v` generators, and extraction of + /// the importer must recover both copies of the loop's structure. + #[test] + fn import_of_instance_with_fiber_equations() { + let src = r#" +set_theory ThSchema + +model WeightedGraph := [ + V : Entity, + E : Entity, + Weight : AttrType, + src : (Hom Entity)[E, V], + tgt : (Hom Entity)[E, V], + weight : Attr[E, Weight] +] + +instance Loop : WeightedGraph := [ + V := [v], + E := [e], + src(e) := v, + tgt(e) := v +] + +instance UseLoop : WeightedGraph := [ + l : Loop +] +"#; + let toplevel = elaborate_to_toplevel(src); + + // `Loop` is a fiber record exposing its two fiber equations as + // `Id`-typed fields alongside the `e`/`v` generators. + let loop_def = match toplevel.declarations.get(&name_seg("Loop")) { + Some(TopDecl::Instance(i)) => i.clone(), + _ => panic!("expected Loop to be an instance declaration"), + }; + let FiberTyV_::Record(r) = &*loop_def.val else { + panic!("Loop should be a fiber record"); + }; + assert!(r.get(name_seg("e")).is_some(), "generator field e"); + assert!(r.get(name_seg("v")).is_some(), "generator field v"); + assert!(r.get(name_seg("_eq0")).is_some(), "first fiber equation"); + assert!(r.get(name_seg("_eq1")).is_some(), "second fiber equation"); + + // The importer still extracts both generators and the imported + // copy's two equations. + let use_def = match toplevel.declarations.get(&name_seg("UseLoop")) { + Some(TopDecl::Instance(i)) => i.clone(), + _ => panic!("expected UseLoop to be an instance declaration"), + }; + let (instance, _ns) = + instance_from_def(&toplevel, &use_def.theory.definition, &use_def).unwrap(); + let ModelInstance::Discrete(instance) = instance else { + panic!("expected a discrete instance"); + }; + let le_qname: QualifiedName = vec![name_seg("l"), name_seg("e")].into(); + let e_fiber: QualifiedName = vec![name_seg("E")].into(); + assert_eq!(instance.fiber_of(&le_qname), Some(&e_fiber)); + assert_eq!(instance.equations().count(), 2); + } + + /// A modal (multicategory) instance: generators lie over plain objects and + /// equations are list-domain morphism applications. + #[test] + fn instance_over_multicategory_monoid() { + let src = r#" +set_theory ThMulticategory + +model SigMonoid := [ + M : Object, + op : Multihom[[M, M], M], + unit : Multihom[[], M] +] + +instance Z2 : SigMonoid := [ + M := [x], + op([x,x]) := unit([]), + op([x,unit([])]) := x +] +"#; + let toplevel = elaborate_to_toplevel(src); + let def = match toplevel.declarations.get(&name_seg("Z2")) { + Some(TopDecl::Instance(i)) => i.clone(), + _ => panic!("expected Z2 to be an instance declaration"), + }; + let (instance, _ns) = instance_from_def(&toplevel, &def.theory.definition, &def).unwrap(); + let ModelInstance::ModalUnital(instance) = instance else { + panic!("expected a unital modal instance"); + }; + + let x_qname: QualifiedName = vec![name_seg("x")].into(); + let m_fiber = modal::ModalOb::Generator(vec![name_seg("M")].into()); + assert_eq!(instance.fiber_of(&x_qname), Some(&m_fiber)); + assert_eq!(instance.equations().count(), 2); + } + + /// A symmetric-monoidal instance: equation terms feed generators through + /// the `@tensor` object operation into unary homs between product objects. + #[test] + fn instance_over_symmetric_monoidal() { + let src = r#" +set_theory ThSymMonoidalCategory + +model AB := [ + A : Object, + B : Object, + f : (Hom Object)[@tensor [A, B], A] +] + +instance i : AB := [ + A := [a], + B := [b], + f(@tensor [a, b]) := a +] +"#; + let toplevel = elaborate_to_toplevel(src); + let def = match toplevel.declarations.get(&name_seg("i")) { + Some(TopDecl::Instance(i)) => i.clone(), + _ => panic!("expected i to be an instance declaration"), + }; + let (instance, _ns) = instance_from_def(&toplevel, &def.theory.definition, &def).unwrap(); + let ModelInstance::ModalUnital(instance) = instance else { + panic!("expected a unital modal instance"); + }; + + let a_qname: QualifiedName = vec![name_seg("a")].into(); + let a_fiber = modal::ModalOb::Generator(vec![name_seg("A")].into()); + assert_eq!(instance.fiber_of(&a_qname), Some(&a_fiber)); + assert_eq!(instance.equations().count(), 1); + } + + /// A symmetric-monoidal instance whose equation feeds a *morphism* + /// application through `@tensor`, exercising the functorial action of the + /// object operation (a `HomApp`) rather than a pure object-op base. + #[test] + fn instance_over_symmetric_monoidal_functorial() { + let src = r#" +set_theory ThSymMonoidalCategory + +model Chain := [ + X : Object, + Y : Object, + s : (Hom Object)[X, Y], + t : (Hom Object)[@tensor [Y, Y], X] +] + +instance chain : Chain := [ + X := [x0], + t(@tensor [s(x0), s(x0)]) := x0 +] +"#; + let toplevel = elaborate_to_toplevel(src); + let def = match toplevel.declarations.get(&name_seg("chain")) { + Some(TopDecl::Instance(i)) => i.clone(), + _ => panic!("expected chain to be an instance declaration"), + }; + let (instance, _ns) = instance_from_def(&toplevel, &def.theory.definition, &def).unwrap(); + let ModelInstance::ModalUnital(instance) = instance else { + panic!("expected a unital modal instance"); + }; + assert_eq!(instance.equations().count(), 1); + + // The left-hand side's morphism is a HomApp: the object operation + // `tensor` acting on the list morphism `[s, s]`. + let (lhs, _) = instance.equations().next().unwrap(); + assert!( + matches!(&lhs.mor, modal::ModalMor::Composite(_)), + "expected the applied morphism to compose `t` after the tensor's HomApp" + ); } } diff --git a/packages/catlog/src/tt/notebook_elab.rs b/packages/catlog/src/tt/notebook_elab.rs index f84623bb8..d65d09974 100644 --- a/packages/catlog/src/tt/notebook_elab.rs +++ b/packages/catlog/src/tt/notebook_elab.rs @@ -10,6 +10,7 @@ use nonempty::NonEmpty; use std::str::FromStr; use uuid::Uuid; +use super::fiber_elab::{CODOMAIN_BINDER, FiberElab, FiberError}; use super::{context::*, eval::*, prelude::*, stx::*, theory::*, toplevel::*, val::*}; use crate::dbl::{ modal, @@ -27,6 +28,8 @@ pub struct Elaborator<'a> { errors: Vec, ref_id: Ustr, next_meta: usize, + /// The cell currently being elaborated, for error attribution. + current_cell: Option, } struct ElaboratorCheckpoint { @@ -43,6 +46,7 @@ impl<'a> Elaborator<'a> { errors: Vec::new(), ref_id, next_meta: 0, + current_cell: None, } } @@ -67,10 +71,10 @@ impl<'a> Elaborator<'a> { Evaluator::new(self.toplevel, self.ctx.env.clone(), self.ctx.scope.len()) } - fn intro(&mut self, name: VarName, label: LabelSegment, ty: Option) -> TmV { - let v = TmV::neu( + fn intro(&mut self, name: VarName, label: LabelSegment, ty: Option) -> BaseTmV { + let v = BaseTmV::neu( TmN::var(self.ctx.scope.len().into(), name, label), - ty.clone().unwrap_or(TyV::unit()), + ty.clone().unwrap_or(BaseTyV::empty_record()), ); let v = if ty.is_some() { self.evaluator().eta(&v, ty.as_ref()) @@ -88,10 +92,10 @@ impl<'a> Elaborator<'a> { MetaVar::new(Some(self.ref_id), i) } - fn ty_error(&mut self, error: InvalidDblModel) -> (TyS, TyV) { + fn ty_error(&mut self, error: InvalidDblModel) -> (BaseTyS, BaseTyV) { self.errors.push(error); let ty_m = self.fresh_meta(); - (TyS::meta(ty_m), TyV::meta(ty_m)) + (BaseTyS::meta(ty_m), BaseTyV::meta(ty_m)) } fn ob_type(&mut self, ob_type: &nb::ObType) -> Option { @@ -102,46 +106,49 @@ impl<'a> Elaborator<'a> { } } - fn object_cell(&mut self, ob_decl: &nb::ObDecl) -> (NameSegment, LabelSegment, TyS, TyV) { + fn object_cell( + &mut self, + ob_decl: &nb::ObDecl, + ) -> (NameSegment, LabelSegment, BaseTyS, BaseTyV) { let name = NameSegment::Uuid(ob_decl.id); let label = LabelSegment::Text(ustr(&ob_decl.name)); let (ty_s, ty_v) = match self.ob_type(&ob_decl.ob_type) { - Some(ob_type) => (TyS::object(ob_type.clone()), TyV::object(ob_type)), + Some(ob_type) => (BaseTyS::object(ob_type.clone()), BaseTyV::object(ob_type)), None => self.ty_error(InvalidDblModel::ObType(QualifiedName::single(name))), }; (name, label, ty_s, ty_v) } - fn lookup_tm(&self, name: VarName) -> Option<(TmS, TmV, TyV)> { + fn lookup_tm(&self, name: VarName) -> Option<(BaseTmS, BaseTmV, BaseTyV)> { let (i, label, ty) = self.ctx.lookup(name)?; let v = self.ctx.env.get(*i).unwrap().clone(); - Some((TmS::var(i, name, label), v, ty.clone().unwrap())) + Some((BaseTmS::var(i, name, label), v, ty.clone().unwrap())) } - fn resolve_name(&self, segments: &[VarName]) -> Option<(TmS, TmV, TyV)> { + fn resolve_name(&self, segments: &[VarName]) -> Option<(BaseTmS, BaseTmV, BaseTyV)> { let (&last, rest) = segments.split_last()?; if rest.is_empty() { self.lookup_tm(last) } else { let (tm_s, tm_v, ty_v) = self.resolve_name(rest)?; - let TyV_::Record(r) = &*ty_v else { + let BaseTyV_::Record(r) = &*ty_v else { return None; }; let &(label, _) = r.fields.get_with_label(last)?; Some(( - TmS::proj(tm_s, last, label), + BaseTmS::proj(tm_s, last, label), self.evaluator().proj(&tm_v, last, label), self.evaluator().field_ty(&ty_v, &tm_v, last), )) } } - fn ob_syn(&self, n: &nb::Ob) -> Option<(TmS, TmV, ObType)> { + fn ob_syn(&self, n: &nb::Ob) -> Option<(BaseTmS, BaseTmV, ObType)> { match n { nb::Ob::Basic(name) => { let name = QualifiedName::deserialize_str(name).unwrap(); let (stx, val, ty) = self.resolve_name(name.as_slice())?; - let TyV_::Object(ob_type) = &*ty else { + let BaseTyV_::Object(ob_type) = &*ty else { return None; }; Some((stx, val, ob_type.clone())) @@ -151,28 +158,28 @@ impl<'a> Elaborator<'a> { let ob_op = self.theory().basic_ob_op([name].into())?; let arg_type = self.theory().ob_op_dom(&ob_op); let (arg_stx, arg_val) = self.ob_chk(ob, &arg_type)?; - let stx = TmS::ob_app(name, arg_stx); - let val = TmV::app(name, arg_val); + let stx = BaseTmS::ob_app(name, arg_stx); + let val = BaseTmV::app(name, arg_val); Some((stx, val, self.theory().ob_op_cod(&ob_op))) } nb::Ob::Tabulated(mor) => { let (mor_stx, mor_val, mor_ty) = self.mor_syn(mor)?; - let TyV_::Morphism(mt, _, _) = &*mor_ty else { + let BaseTyV_::Morphism(mt, _, _) = &*mor_ty else { return None; }; let ob_type = self.theory().tabulator(mt.clone())?; - Some((TmS::tab(mor_stx), TmV::tab(mor_val), ob_type)) + Some((BaseTmS::tab(mor_stx), BaseTmV::tab(mor_val), ob_type)) } _ => None, } } - fn mor_syn(&self, n: &nb::Mor) -> Option<(TmS, TmV, TyV)> { + fn mor_syn(&self, n: &nb::Mor) -> Option<(BaseTmS, BaseTmV, BaseTyV)> { match n { nb::Mor::Basic(name) => { let name = QualifiedName::deserialize_str(name).unwrap(); let (stx, val, ty) = self.resolve_name(name.as_slice())?; - let TyV_::Morphism(..) = &*ty else { + let BaseTyV_::Morphism(..) = &*ty else { return None; }; Some((stx, val, ty)) @@ -181,7 +188,7 @@ impl<'a> Elaborator<'a> { nb::path::Path::Id(ob) => { let (stx, val, ob_type) = self.ob_syn(ob)?; let mor_type = self.theory().hom_type(ob_type)?; - Some((stx, val.clone(), TyV::morphism(mor_type, val.clone(), val.clone()))) + Some((stx, val.clone(), BaseTyV::morphism(mor_type, val.clone(), val.clone()))) } nb::path::Path::Seq(ms) => match ms.as_slice() { [] => None, @@ -190,10 +197,11 @@ impl<'a> Elaborator<'a> { let (stx_first, val_first, type_first) = self.mor_syn(first)?; let rest = nb::Mor::Composite(Box::new(nb::path::Path::Seq(rest.to_vec()))); let (stx_rest, val_rest, type_rest) = self.mor_syn(&rest)?; - let TyV_::Morphism(mt_first, dom_first, cod_first) = &*type_first else { + let BaseTyV_::Morphism(mt_first, dom_first, cod_first) = &*type_first + else { unreachable!() }; - let TyV_::Morphism(mt_rest, dom_rest, cod_rest) = &*type_rest else { + let BaseTyV_::Morphism(mt_rest, dom_rest, cod_rest) = &*type_rest else { unreachable!() }; if mt_first != mt_rest { @@ -202,12 +210,16 @@ impl<'a> Elaborator<'a> { if self.evaluator().equal_tm(cod_first, dom_rest).is_err() { return None; } - let stx = TmS::compose(stx_first, stx_rest); - let val = TmV::compose(val_first, val_rest); + let stx = BaseTmS::compose(stx_first, stx_rest); + let val = BaseTmV::compose(val_first, val_rest); Some(( stx, val, - TyV::morphism(mt_first.clone(), dom_first.clone(), cod_rest.clone()), + BaseTyV::morphism( + mt_first.clone(), + dom_first.clone(), + cod_rest.clone(), + ), )) } }, @@ -216,7 +228,7 @@ impl<'a> Elaborator<'a> { } } - fn ob_chk(&self, n: &nb::Ob, ob_type: &ObType) -> Option<(TmS, TmV)> { + fn ob_chk(&self, n: &nb::Ob, ob_type: &ObType) -> Option<(BaseTmS, BaseTmV)> { match n { nb::Ob::List { modality: nb_modality, objects: elems } => { let (modality, ob_type) = ob_type.clone().mode_app()?; @@ -230,7 +242,7 @@ impl<'a> Elaborator<'a> { elem_stxs.push(tm_s); elem_vals.push(tm_v); } - Some((TmS::list(elem_stxs), TmV::list(elem_vals))) + Some((BaseTmS::list(elem_stxs), BaseTmV::list(elem_vals))) } _ => { let (tm_s, tm_v, synthed) = self.ob_syn(n)?; @@ -243,7 +255,7 @@ impl<'a> Elaborator<'a> { } } - fn morphism_cell_ty(&mut self, mor_decl: &nb::MorDecl) -> (TyS, TyV) { + fn morphism_cell_ty(&mut self, mor_decl: &nb::MorDecl) -> (BaseTyS, BaseTyV) { let id = QualifiedName::from(mor_decl.id); let (mor_type, dom_ty, cod_ty) = match &mor_decl.mor_type { nb::MorType::Basic(name) => { @@ -275,19 +287,22 @@ impl<'a> Elaborator<'a> { return self.ty_error(InvalidDblModel::CodType(id)); }; ( - TyS::morphism(mor_type.clone(), dom_s, cod_s), - TyV::morphism(mor_type, dom_v, cod_v), + BaseTyS::morphism(mor_type.clone(), dom_s, cod_s), + BaseTyV::morphism(mor_type, dom_v, cod_v), ) } - fn morphism_cell(&mut self, mor_decl: &nb::MorDecl) -> (NameSegment, LabelSegment, TyS, TyV) { + fn morphism_cell( + &mut self, + mor_decl: &nb::MorDecl, + ) -> (NameSegment, LabelSegment, BaseTyS, BaseTyV) { let name = NameSegment::Uuid(mor_decl.id); let label = LabelSegment::Text(ustr(&mor_decl.name)); let (ty_s, ty_v) = self.morphism_cell_ty(mor_decl); (name, label, ty_s, ty_v) } - fn equation_cell_ty(&mut self, eqn_decl: &nb::EqnDecl) -> (TyS, TyV) { + fn equation_cell_ty(&mut self, eqn_decl: &nb::EqnDecl) -> (BaseTyS, BaseTyV) { let (lhs_m, rhs_m) = match (&eqn_decl.lhs, &eqn_decl.rhs) { (Some(lhs), Some(rhs)) => (lhs, rhs), _ => { @@ -312,10 +327,10 @@ impl<'a> Elaborator<'a> { }; if let (Some((_, _, lhs_ty)), Some((_, _, rhs_ty))) = (&lhs, &rhs) { - let TyV_::Morphism(mt_lhs, dom_lhs, cod_lhs) = &**lhs_ty else { + let BaseTyV_::Morphism(mt_lhs, dom_lhs, cod_lhs) = &**lhs_ty else { unreachable!() }; - let TyV_::Morphism(mt_rhs, dom_rhs, cod_rhs) = &**rhs_ty else { + let BaseTyV_::Morphism(mt_rhs, dom_rhs, cod_rhs) = &**rhs_ty else { unreachable!() }; if mt_lhs != mt_rhs { @@ -331,8 +346,8 @@ impl<'a> Elaborator<'a> { } match (NonEmpty::from_vec(errors), lhs, rhs) { (None, Some((lhs_s, lhs_v, lhs_ty)), Some((rhs_s, rhs_v, _))) => { - let ty_s = TyS::id(self.evaluator().quote_ty(&lhs_ty), lhs_s, rhs_s); - let ty_v = TyV::id(lhs_ty, lhs_v, rhs_v); + let ty_s = BaseTyS::id(self.evaluator().quote_ty(&lhs_ty), lhs_s, rhs_s); + let ty_v = BaseTyV::id(lhs_ty, lhs_v, rhs_v); (ty_s, ty_v) } (Some(errors), _, _) => { @@ -346,7 +361,10 @@ impl<'a> Elaborator<'a> { } } - fn equation_cell(&mut self, eqn_decl: &nb::EqnDecl) -> (NameSegment, LabelSegment, TyS, TyV) { + fn equation_cell( + &mut self, + eqn_decl: &nb::EqnDecl, + ) -> (NameSegment, LabelSegment, BaseTyS, BaseTyV) { // Kind of funny that the decl's id produces the cell's name // but the decl's name produces the cell's label. let name = NameSegment::Uuid(eqn_decl.id); @@ -355,7 +373,7 @@ impl<'a> Elaborator<'a> { (name, label, ty_s, ty_v) } - fn instantiation_cell_ty(&mut self, i_decl: &nb::InstantiatedModel) -> (TyS, TyV) { + fn instantiation_cell_ty(&mut self, i_decl: &nb::InstantiatedModel) -> (BaseTyS, BaseTyV) { let name = QualifiedName::single(NameSegment::Uuid(i_decl.id)); let link = match &i_decl.model { Some(l) => l, @@ -373,7 +391,7 @@ impl<'a> Elaborator<'a> { return self.ty_error(InvalidDblModel::InvalidLink(name)); } let mut specializations = Vec::new(); - let TyV_::Record(r) = &*type_def.val else { + let BaseTyV_::Record(r) = &*type_def.val else { return self.ty_error(InvalidDblModel::InvalidLink(name)); }; let mut r = r.clone(); @@ -387,7 +405,7 @@ impl<'a> Elaborator<'a> { continue; }; match &**field_ty { - TyS_::Object(expected_ob_ty) => { + BaseTyS_::Object(expected_ob_ty) => { if &ob_type != expected_ob_ty { continue; } @@ -398,26 +416,26 @@ impl<'a> Elaborator<'a> { } specializations.push(( vec![(field_name, *field_label)], - TyS::sing(TyS::object(ob_type.clone()), ob_s), + BaseTyS::sing(BaseTyS::object(ob_type.clone()), ob_s), )); r = r.add_specialization( &[(field_name, *field_label)], - TyV::sing(TyV::object(ob_type), ob_v), + BaseTyV::sing(BaseTyV::object(ob_type), ob_v), ) } } let ty_s = if specializations.is_empty() { - TyS::topvar(topname) + BaseTyS::topvar(topname) } else { - TyS::specialize(TyS::topvar(topname), specializations) + BaseTyS::specialize(BaseTyS::topvar(topname), specializations) }; - (ty_s, TyV::record(r)) + (ty_s, BaseTyV::record(r)) } fn instantiation_cell( &mut self, i_decl: &nb::InstantiatedModel, - ) -> (NameSegment, LabelSegment, TyS, TyV) { + ) -> (NameSegment, LabelSegment, BaseTyS, BaseTyV) { let name = NameSegment::Uuid(i_decl.id); let label = LabelSegment::Text(ustr(&i_decl.name)); let (ty_s, ty_v) = self.instantiation_cell_ty(i_decl); @@ -428,7 +446,7 @@ impl<'a> Elaborator<'a> { pub fn notebook<'b>( &mut self, cells: impl Iterator, - ) -> (TyS, TyV) { + ) -> (BaseTyS, BaseTyV) { // Process the cells in dependency order. This is important because the // UI allows users to reorder cells freely and that shouldn't affect the // result of elaboration. @@ -454,7 +472,7 @@ impl<'a> Elaborator<'a> { field_ty_vs.push((name, (label, ty_v.clone()))); self.ctx.scope.push(VarInContext::new(name, label, Some(ty_v.clone()))); self.ctx.env = - self.ctx.env.snoc(TmV::neu(TmN::proj(self_var.clone(), name, label), ty_v)); + self.ctx.env.snoc(BaseTmV::neu(TmN::proj(self_var.clone(), name, label), ty_v)); } self.reset_to(c); @@ -463,7 +481,352 @@ impl<'a> Elaborator<'a> { .map(|(name, (label, ty_v))| (*name, (*label, self.evaluator().quote_ty(ty_v)))) .collect(); let r_v = RecordV::new(self.ctx.env.clone(), field_tys.clone(), Dtry::empty()); - (TyS::record(field_tys), TyV::record(r_v)) + (BaseTyS::record(field_tys), BaseTyV::record(r_v)) + } +} + +/// Instance-notebook elaboration: cells presenting an instance of a model, +/// elaborated to a fiber record packaged as an [`Instance`] — the +/// same target as the text elaborator's `instance NAME : X := [...]` path, +/// whose `instance_body_inner` is the blueprint for everything here. The +/// fiber helpers are deliberate near-duplicates of their text-side namesakes +/// with typed errors; extracting a shared core is planned once the error +/// channels unify. +impl<'a> Elaborator<'a> { + /// Resolve a qualified name to a codomain morphism: the path (with + /// labels, for [`FiberTmS::over_app`]) and the morphism's type. The + /// codomain's fields are in the base scope (see + /// [`Self::instance_notebook`]), so the first segment is a context + /// variable and later segments project through records (a morphism of a + /// model instantiated into the codomain). + fn resolve_codomain_mor( + &self, + name: &QualifiedName, + ) -> Option<(Vec<(FieldName, LabelSegment)>, BaseTyV)> { + let (&first, rest) = name.as_slice().split_first()?; + let (i, label, ty) = self.ctx.lookup(first)?; + let mut tm_v = self.ctx.env.get(*i).unwrap().clone(); + let mut ty_v = ty?; + let mut path = vec![(first, label)]; + for &seg in rest { + let BaseTyV_::Record(r) = &*ty_v else { + return None; + }; + let (seg_label, _) = r.fields.get_with_label(seg)?; + path.push((seg, *seg_label)); + let next_ty = self.evaluator().field_ty(&ty_v, &tm_v, seg); + tm_v = self.evaluator().proj(&tm_v, seg, *seg_label); + ty_v = next_ty; + } + Some((path, ty_v)) + } + + /// Resolve a qualified fiber reference: a generator, or a projection + /// path through imports (`hydro.n`). The fiber-scope analogue of + /// [`Self::resolve_name`]. + fn resolve_fiber(&self, segments: &[VarName]) -> Option<(FiberTmS, FiberTmV, FiberTyV)> { + let (&first, rest) = segments.split_first()?; + let (mut tm_s, mut tm_v, mut ty_v) = self.lookup_fiber_tm(first)?; + for &seg in rest { + let FiberTyV_::Record(r) = &*ty_v else { + return None; + }; + let (label, field_ty) = r.get_with_label(seg)?; + tm_s = FiberTmS::proj(tm_s, seg, *label); + tm_v = FiberTmV::proj(tm_v, seg, *label); + ty_v = field_ty.clone(); + } + Some((tm_s, tm_v, ty_v)) + } + + /// Apply a codomain morphism to an already-elaborated fiber argument: + /// resolve the morphism against the codomain fields in scope, then + /// delegate the checks and construction to the shared + /// [`FiberElab::fiber_mor_app`]. + fn apply_codomain_morphism( + &mut self, + mor_name: &QualifiedName, + arg_s: FiberTmS, + arg_v: FiberTmV, + arg_ty: FiberTyV, + ) -> (FiberTmS, FiberTmV, FiberTyV) { + let Some((path, mor_ty)) = self.resolve_codomain_mor(mor_name) else { + return self.fiber_syn_error(FiberError::UnknownElement(mor_name.to_string())); + }; + let FiberTyV_::Over(arg_obj) = &*arg_ty else { + return self.fiber_syn_error(FiberError::ArgNotElement(None)); + }; + let arg_obj = arg_obj.clone(); + self.fiber_mor_app(&path, &mor_ty, arg_s, arg_v, &arg_obj) + } + + /// Synthesize a fiber term from a notebook instance term. Mirrors the + /// text elaborator's `fiber_syn`, dispatching on [`nb::InstanceTm`] + /// instead of surface notation; errors are attributed to the cell in + /// [`Self::current_cell`]. + fn fiber_syn_nb(&mut self, tm: &nb::InstanceTm) -> (FiberTmS, FiberTmV, FiberTyV) { + match tm { + nb::InstanceTm::Generator(name) => { + let Ok(qname) = QualifiedName::deserialize_str(name) else { + return self.fiber_syn_error(FiberError::UnknownElement(name.clone())); + }; + match self.resolve_fiber(qname.as_slice()) { + Some(r) => r, + None => self.fiber_syn_error(FiberError::UnknownElement(name.clone())), + } + } + nb::InstanceTm::App { mor, arg } => { + let nb::Mor::Basic(mor_name) = mor else { + self.errors + .push(InvalidDblModel::UnsupportedFeature(Feature::CompositeApplication)); + return self.fiber_syn_hole(); + }; + let Ok(mor_qname) = QualifiedName::deserialize_str(mor_name) else { + return self.fiber_syn_error(FiberError::UnknownElement(mor_name.clone())); + }; + let (arg_s, arg_v, arg_ty) = self.fiber_syn_nb(arg); + self.apply_codomain_morphism(&mor_qname, arg_s, arg_v, arg_ty) + } + nb::InstanceTm::List { terms, .. } => { + let mut ss = Vec::with_capacity(terms.len()); + let mut vs = Vec::with_capacity(terms.len()); + let mut objs = Vec::with_capacity(terms.len()); + for term in terms { + let Some(term) = term else { + return self.fiber_syn_error(FiberError::MissingTerm); + }; + let (s, v, ty) = self.fiber_syn_nb(term); + let FiberTyV_::Over(o) = &*ty else { + return self.fiber_syn_error(FiberError::ListElementNotOver); + }; + objs.push(o.clone()); + ss.push(s); + vs.push(v); + } + (FiberTmS::list(ss), FiberTmV::list(vs), FiberTyV::over(BaseTmV::list(objs))) + } + nb::InstanceTm::ObApp { op, tm } => { + let nb::ObOp::Basic(op_name) = op; + let op_seg = name_seg(*op_name); + if !self.check_ob_op(op_seg) { + return self.fiber_syn_hole(); + } + let (arg_s, arg_v, arg_ty) = self.fiber_syn_nb(tm); + self.fiber_ob_app(op_seg, arg_s, arg_v, &arg_ty) + } + } + } + + /// Check a notebook instance term against an expected fiber type. Fiber + /// terms are all synthesizing, so this synthesizes and checks + /// convertibility. + fn fiber_chk_nb(&mut self, expected: &FiberTyV, tm: &nb::InstanceTm) -> (FiberTmS, FiberTmV) { + let syn = self.fiber_syn_nb(tm); + self.check_fiber(syn, expected) + } + + /// Elaborate the cells of an instance notebook against the codomain + /// model, producing the instance as a fiber record — the notebook + /// analogue of the text elaborator's `instance_body`. + /// + /// The codomain is bound under `CODOMAIN_BINDER` and each of its + /// fields is pushed into the base scope as a variable projecting out of + /// that binding, so cell references to codomain objects and morphisms + /// (UUID-qualified names) resolve through the ordinary + /// `Self::resolve_name` machinery — including modal objects in `over` + /// and paths through model instantiations. Generators and imports go to + /// the separate fiber scope, exactly as in the text pipeline. Unlike the + /// text pipeline, a bad cell does not abort the instance: the error is + /// recorded against the cell and elaboration continues. + pub fn instance_notebook<'b>( + &mut self, + codomain: &RecordV, + cells: impl Iterator, + ) -> (FiberTyS, FiberTyV) { + let toplevel = self.toplevel; + // Like model notebooks, cells are elaborated in dependency order so + // that UI reordering cannot change the result. + let mut cells: Vec<_> = cells.collect(); + cells.sort_by_key(|judgment| match judgment { + nb::InstanceJudgment::Generator(_) => 0, + nb::InstanceJudgment::Import(_) => 1, + nb::InstanceJudgment::Equation(_) => 2, + }); + + let c = self.checkpoint(); + let codomain_ty = BaseTyV::record(codomain.clone()); + let self_v = self.intro( + name_seg(CODOMAIN_BINDER), + label_seg(CODOMAIN_BINDER), + Some(codomain_ty.clone()), + ); + for (name, (label, _)) in codomain.fields.iter() { + let field_ty = self.evaluator().field_ty(&codomain_ty, &self_v, *name); + let field_v = self.evaluator().proj(&self_v, *name, *label); + self.ctx.push_scope(*name, *label, Some(field_ty)); + self.ctx.env = self.ctx.env.snoc(field_v); + } + + let mut fields_s: Row = Row::empty(); + let mut fields_v: Row = Row::empty(); + + for cell in cells { + match cell { + // A generator lying over a codomain object. + nb::InstanceJudgment::Generator(gen_decl) => { + let name = NameSegment::Uuid(gen_decl.id); + let label = LabelSegment::Text(ustr(&gen_decl.name)); + self.current_cell = Some(QualifiedName::single(name)); + let over = gen_decl.over.as_ref().and_then(|ob| self.ob_syn(ob)); + let (ty_s, ty_v) = match over { + Some((obj_s, obj_v, _)) => (FiberTyS::over(obj_s), FiberTyV::over(obj_v)), + None => { + self.errors.push(InvalidDblModel::ObType(QualifiedName::single(name))); + let m = self.fresh_meta(); + (FiberTyS::over(BaseTmS::meta(m)), FiberTyV::over(BaseTmV::meta(m))) + } + }; + self.intro_fiber(name, label, ty_v.clone()); + fields_s.insert(name, label, ty_s); + fields_v.insert(name, label, ty_v); + } + // An import of another instance of the same codomain. + nb::InstanceJudgment::Import(import) => { + let name = NameSegment::Uuid(import.id); + let label = LabelSegment::Text(ustr(&import.name)); + let qname = QualifiedName::single(name); + self.current_cell = Some(qname.clone()); + let resolved = import.instance.as_ref().and_then(|link| { + let nb::LinkType::Instantiation = link.r#type else { + return None; + }; + let topname = NameSegment::Text(ustr(&link.stable_ref.id)); + match toplevel.declarations.get(&topname) { + Some(TopDecl::Instance(inst)) if inst.theory == self.theory => { + Some((topname, inst)) + } + _ => None, + } + }); + let Some((topname, inst)) = resolved else { + self.errors.push(InvalidDblModel::InvalidLink(qname)); + continue; + }; + if !self.codomains_match(&codomain_ty, &inst.codomain) { + self.errors.push(InvalidDblModel::ImportCodomain(qname)); + continue; + } + let val = inst.val.clone(); + self.intro_fiber(name, label, val.clone()); + fields_s.insert(name, label, FiberTyS::topvar(topname)); + fields_v.insert(name, label, val); + } + // An equation between fiber elements. + nb::InstanceJudgment::Equation(eqn_decl) => { + let name = NameSegment::Uuid(eqn_decl.id); + let label = LabelSegment::Text(ustr(&eqn_decl.name)); + self.current_cell = Some(QualifiedName::single(name)); + let (Some(lhs), Some(rhs)) = (&eqn_decl.lhs, &eqn_decl.rhs) else { + self.errors + .push(InvalidDblModel::UnsupportedFeature(Feature::PartialEquation)); + continue; + }; + let (lhs_s, lhs_v, lhs_ty) = self.fiber_syn_nb(lhs); + let FiberTyV_::Over(obj) = &*lhs_ty else { + // Only fiber-element equations live in an instance; + // morphism equations constrain the model. + self.report_fiber(FiberError::EquationNotOver); + continue; + }; + let obj = obj.clone(); + let (rhs_s, rhs_v) = self.fiber_chk_nb(&lhs_ty, rhs); + let (id_s, id_v) = + self.fiber_id_field(&lhs_ty, &obj, lhs_s, lhs_v, rhs_s, rhs_v); + fields_s.insert(name, label, id_s); + fields_v.insert(name, label, id_v); + } + } + } + self.reset_to(c); + (FiberTyS::record(fields_s), FiberTyV::record(fields_v)) + } + + /// Elaborate an instance document into a top-level instance declaration. + /// + /// Resolves the document's `instanceOf` link to a model previously + /// declared in the toplevel (mirroring how instantiation cells resolve + /// their links), elaborates the cells against it, and packages the + /// result exactly as the text pipeline does — ready for + /// [`instance_from_def`](super::modelgen::instance_from_def). + /// + /// Returns `None` (with an error recorded) if the codomain link cannot + /// be resolved at all; cell-level problems are recorded per-cell in + /// [`Self::errors`] and still produce an instance. + pub fn instance_document(&mut self, doc: &nb::InstanceDocumentContent) -> Option { + let toplevel = self.toplevel; + let link = &doc.instance_of; + let link_name = QualifiedName::single(NameSegment::Text(ustr(&link.stable_ref.id))); + let nb::LinkType::InstanceOf = link.r#type else { + self.errors.push(InvalidDblModel::InvalidLink(link_name)); + return None; + }; + let topname = NameSegment::Text(ustr(&link.stable_ref.id)); + let Some(TopDecl::Type(type_def)) = toplevel.declarations.get(&topname) else { + self.errors.push(InvalidDblModel::InvalidLink(link_name)); + return None; + }; + if type_def.theory != self.theory { + self.errors.push(InvalidDblModel::InvalidLink(link_name)); + return None; + } + let BaseTyV_::Record(codomain) = &*type_def.val else { + self.errors.push(InvalidDblModel::InvalidLink(link_name)); + return None; + }; + let codomain = codomain.clone(); + let codomain_ty = type_def.val.clone(); + let (stx, val) = self.instance_notebook(&codomain, doc.notebook.formal_content()); + Some(Instance::new(self.theory.clone(), stx, val, codomain_ty)) + } +} + +impl<'a> FiberElab for Elaborator<'a> { + fn ctx(&self) -> &Context { + &self.ctx + } + + fn ctx_mut(&mut self) -> &mut Context { + &mut self.ctx + } + + fn elab_theory(&self) -> &Theory { + &self.theory + } + + fn evaluator(&self) -> Evaluator<'_> { + Elaborator::evaluator(self) + } + + fn fresh_meta(&mut self) -> MetaVar { + Elaborator::fresh_meta(self) + } + + /// Attribute fiber errors to the cell currently being elaborated, as + /// typed [`InvalidDblModel`] values for the notebook interface. + fn report_fiber(&mut self, err: FiberError) { + let cell = self + .current_cell + .clone() + .unwrap_or_else(|| QualifiedName::single(name_seg("unknown cell"))); + let error = match err { + FiberError::UnknownElement(_) + | FiberError::ProjNonRecord + | FiberError::UnknownProj(_) + | FiberError::MissingTerm => InvalidDblModel::FiberElement(cell), + FiberError::ImportCodomainMismatch(_) => InvalidDblModel::ImportCodomain(cell), + _ => InvalidDblModel::FiberType(cell), + }; + self.errors.push(error); } } @@ -503,15 +866,17 @@ mod test { use ustr::ustr; use crate::dbl::model::DblModelPrinter; - use crate::stdlib::{th_schema, th_sym_monoidal_category}; + use crate::stdlib::{th_schema, th_sym_monoidal_category, th_sym_multicategory}; use crate::tt::{ - modelgen::Model, + batch::{format_modal_instance_term, format_modal_ob, write_instance_summary}, + modelgen::{Model, ModelInstance, instance_from_def}, notebook_elab::Elaborator, + prelude::*, theory::{Theory, TheoryDef}, - toplevel::Toplevel, + toplevel::{Instance, TopDecl, Toplevel, Type}, }; use crate::zero::name; - use catcolab_document_types::current::ModelDocumentContent; + use catcolab_document_types::current::{InstanceDocumentContent, ModelDocumentContent}; fn elab_example(theory: &Theory, name: &str, expected: Expect) -> Model { let src = fs::read_to_string(format!("examples/tt/notebook/{name}.json")).unwrap(); @@ -578,6 +943,217 @@ mod test { ); } + /// Every notebook fixture under `examples/tt/notebook` deserializes + /// against the document schema for its declared `type`. Elaboration + /// coverage is per-file opt-in (each fixture needs a theory and, for + /// instances, a populated toplevel), but this sweep catches schema + /// drift and orphaned fixtures that no named test reads. + #[test] + fn notebook_fixtures_deserialize() { + fn walk(dir: &std::path::Path, checked: &mut usize) { + for entry in fs::read_dir(dir).unwrap().flatten() { + let path = entry.path(); + if path.is_dir() { + walk(&path, checked); + continue; + } + if path.extension().is_none_or(|e| e != "json") { + continue; + } + let src = fs::read_to_string(&path).unwrap(); + let value: serde_json::Value = serde_json::from_str(&src).unwrap(); + let display = path.display(); + match value.get("type").and_then(|t| t.as_str()) { + Some("model") => { + serde_json::from_str::(&src) + .unwrap_or_else(|e| panic!("{display}: {e}")); + } + Some("instance") => { + serde_json::from_str::(&src) + .unwrap_or_else(|e| panic!("{display}: {e}")); + } + other => panic!("{display}: unexpected document type {other:?}"), + } + *checked += 1; + } + } + let mut checked = 0; + walk(std::path::Path::new("examples/tt/notebook"), &mut checked); + assert!(checked >= 8, "expected at least 8 fixtures, found {checked}"); + } + + /// Elaborate a model document and install it in the toplevel under the + /// given ref id, so instance documents can link to it. + fn install_model(toplevel: &mut Toplevel, theory: &Theory, ref_id: &str, src: &str) { + let doc: ModelDocumentContent = serde_json::from_str(src).unwrap(); + let (ty_s, ty_v) = { + let mut elab = Elaborator::new(theory.clone(), toplevel, ustr(ref_id)); + let r = elab.notebook(doc.notebook.formal_content()); + assert!(elab.errors().is_empty(), "{ref_id}: {:?}", elab.errors()); + r + }; + toplevel.declarations.insert( + NameSegment::Text(ustr(ref_id)), + TopDecl::Type(Type::new(theory.clone(), ty_s, ty_v)), + ); + } + + /// Elaborate an instance document, asserting no errors. + fn elab_instance(toplevel: &Toplevel, theory: &Theory, ref_id: &str, src: &str) -> Instance { + let doc: InstanceDocumentContent = serde_json::from_str(src).unwrap(); + let mut elab = Elaborator::new(theory.clone(), toplevel, ustr(ref_id)); + let inst = elab.instance_document(&doc).expect("codomain should resolve"); + assert!(elab.errors().is_empty(), "{ref_id}: {:?}", elab.errors()); + inst + } + + /// The Klausmeier fixtures: DEC model + hydro/phyto instances installed + /// in a toplevel, ready for tests to elaborate against. + fn klausmeier_setup() -> (Theory, Toplevel) { + let th = + Theory::new(name("ThMulticategory"), TheoryDef::modal_unital(th_sym_multicategory())); + let mut toplevel = Toplevel::new(Default::default()); + let src = fs::read_to_string("examples/tt/notebook/klausmeier/dec_model.json").unwrap(); + install_model(&mut toplevel, &th, "dec_model", &src); + for ref_id in ["hydrodynamics", "phytodynamics"] { + let src = fs::read_to_string(format!("examples/tt/notebook/klausmeier/{ref_id}.json")) + .unwrap(); + let inst = elab_instance(&toplevel, &th, ref_id, &src); + toplevel + .declarations + .insert(NameSegment::Text(ustr(ref_id)), TopDecl::Instance(inst)); + } + (th, toplevel) + } + + /// Render an elaborated instance through `instance_from_def` in the + /// batch snapshot format. + fn instance_summary(toplevel: &Toplevel, theory: &Theory, inst: &Instance) -> String { + let (instance, ns) = instance_from_def(toplevel, &theory.definition, inst).unwrap(); + let ModelInstance::ModalUnital(instance) = &instance else { + panic!("expected a modal instance"); + }; + let mut out = String::new(); + write_instance_summary( + &mut out, + instance, + &ns, + |ob| format_modal_ob(ob, &ns), + |tm| format_modal_instance_term(tm, &ns), + ); + out + } + + /// End-to-end: the Klausmeier instance notebooks elaborate to + /// `DblModelInstance`s through the same pipeline as the text examples + /// (compare `examples/tt/text/test_klausmeier.dbltt.snapshot`). + #[test] + fn klausmeier_instance_notebooks() { + let (th, toplevel) = klausmeier_setup(); + + let Some(TopDecl::Instance(hydro)) = + toplevel.declarations.get(&NameSegment::Text(ustr("hydrodynamics"))) + else { + unreachable!() + }; + expect![[r#" + #/ instance generators: + #/ a : Form0 + #/ k : Form0 + #/ dX : Form1 + #/ w : DualForm0 + #/ n : DualForm0 + #/ x0 : DualForm0 + #/ x1 : DualForm0 + #/ x2 : DualForm0 + #/ x3 : DualForm0 + #/ x4 : DualForm0 + #/ x5 : DualForm0 + #/ instance equations: + #/ x0 == sub_d01([w, a]) + #/ x1 == square_d0([n]) + #/ x2 == mult_d0d0([w, x1]) + #/ x3 == sub_d0d0([x0, x2]) + #/ x4 == lie_1d0([dX, w]) + #/ x5 == mult_0d0([k, x4]) + #/ partial_d0([w]) == add_d0d0([x3, x5]) + "#]] + .assert_eq(&instance_summary(&toplevel, &th, hydro)); + + let src = fs::read_to_string("examples/tt/notebook/klausmeier/klausmeier.json").unwrap(); + let klausmeier = elab_instance(&toplevel, &th, "klausmeier", &src); + expect![[r#" + #/ instance generators: + #/ hydro.a : Form0 + #/ hydro.k : Form0 + #/ hydro.dX : Form1 + #/ hydro.w : DualForm0 + #/ hydro.n : DualForm0 + #/ hydro.x0 : DualForm0 + #/ hydro.x1 : DualForm0 + #/ hydro.x2 : DualForm0 + #/ hydro.x3 : DualForm0 + #/ hydro.x4 : DualForm0 + #/ hydro.x5 : DualForm0 + #/ phyto.m : Form0 + #/ phyto.n : DualForm0 + #/ phyto.w : DualForm0 + #/ phyto.y0 : DualForm0 + #/ phyto.y1 : DualForm0 + #/ phyto.y2 : DualForm0 + #/ phyto.y3 : DualForm0 + #/ phyto.y4 : DualForm0 + #/ instance equations: + #/ hydro.x0 == sub_d01([hydro.w, hydro.a]) + #/ hydro.x1 == square_d0([hydro.n]) + #/ hydro.x2 == mult_d0d0([hydro.w, hydro.x1]) + #/ hydro.x3 == sub_d0d0([hydro.x0, hydro.x2]) + #/ hydro.x4 == lie_1d0([hydro.dX, hydro.w]) + #/ hydro.x5 == mult_0d0([hydro.k, hydro.x4]) + #/ partial_d0([hydro.w]) == add_d0d0([hydro.x3, hydro.x5]) + #/ phyto.y0 == square_d0([phyto.n]) + #/ phyto.y1 == mult_d0d0([phyto.w, phyto.y0]) + #/ phyto.y2 == mult_0d0([phyto.m, phyto.n]) + #/ phyto.y3 == sub_d0d0([phyto.y1, phyto.y2]) + #/ phyto.y4 == lapl_d0([phyto.n]) + #/ partial_d0([phyto.w]) == add_d0d0([phyto.y3, phyto.y4]) + #/ hydro.n == phyto.n + #/ hydro.w == phyto.w + "#]] + .assert_eq(&instance_summary(&toplevel, &th, &klausmeier)); + } + + /// Importing an instance of a different model into an instance notebook + /// is an error (notebook twin of the text suite's MismatchedImport). + #[test] + fn instance_import_codomain_mismatch() { + use crate::dbl::model::InvalidDblModel; + let (th, mut toplevel) = klausmeier_setup(); + let other_model = r##"{"type":"model","name":"Other","theory":"multicategory","version":"2", + "notebook":{"cellContents":{"11111111-1111-1111-1111-111111111111":{ + "tag":"formal","id":"11111111-1111-1111-1111-111111111111", + "content":{"tag":"object","name":"X","id":"22222222-2222-2222-2222-222222222222", + "obType":{"tag":"Basic","content":"Object"}}}}, + "cellOrder":["11111111-1111-1111-1111-111111111111"]}}"##; + install_model(&mut toplevel, &th, "other_model", other_model); + let bad_import = r##"{"type":"instance","name":"Bad","version":"2", + "instanceOf":{"_id":"other_model","_version":null,"_server":"catcolab.org","type":"instance-of"}, + "notebook":{"cellContents":{"33333333-3333-3333-3333-333333333333":{ + "tag":"formal","id":"33333333-3333-3333-3333-333333333333", + "content":{"tag":"import","name":"h","id":"44444444-4444-4444-4444-444444444444", + "instance":{"_id":"hydrodynamics","_version":null,"_server":"catcolab.org","type":"instantiation"}}}}, + "cellOrder":["33333333-3333-3333-3333-333333333333"]}}"##; + let doc: InstanceDocumentContent = serde_json::from_str(bad_import).unwrap(); + let mut elab = Elaborator::new(th.clone(), &toplevel, ustr("bad_import")); + let inst = elab.instance_document(&doc); + assert!(inst.is_some()); + assert!( + elab.errors().iter().any(|e| matches!(e, InvalidDblModel::ImportCodomain(_))), + "expected an ImportCodomain error, got {:?}", + elab.errors() + ); + } + /// Test a notebook with an equation. #[test] fn commutative_square() { diff --git a/packages/catlog/src/tt/prelude.rs b/packages/catlog/src/tt/prelude.rs index e358d9c54..a7c9e7634 100644 --- a/packages/catlog/src/tt/prelude.rs +++ b/packages/catlog/src/tt/prelude.rs @@ -1,10 +1,10 @@ //! Common imports for [`tt`](crate::tt). -pub use crate::tt::util::*; pub use crate::zero::{ LabelSegment, qualified::{label_seg, name_seg}, }; +pub use crate::zero::{dtry::*, idx::*, pretty::*, row::*}; pub use crate::{one::Path, zero::NameSegment}; pub use indexmap::IndexMap; pub use std::collections::HashMap; diff --git a/packages/catlog/src/tt/stx.rs b/packages/catlog/src/tt/stx.rs index e01fc7886..5c144876a 100644 --- a/packages/catlog/src/tt/stx.rs +++ b/packages/catlog/src/tt/stx.rs @@ -27,8 +27,8 @@ impl fmt::Display for MetaVar { } } -/// Inner enum for [TyS]. -pub enum TyS_ { +/// Inner enum for [BaseTyS]. +pub enum BaseTyS_ { /// A reference to a top-level declaration. TopVar(TopVarName), /// Type constructor for object types. @@ -46,7 +46,7 @@ pub enum TyS_ { /// /// A term of type `Morphism(mt, dom, cod)` represents an morphism of morphism /// type `mt` from `dom` to `cod`. - Morphism(MorType, TmS, TmS), + Morphism(MorType, BaseTmS, BaseTmS), /// Type constructor for record types. /// @@ -54,7 +54,7 @@ pub enum TyS_ { /// /// A term `x` of type `Record(r)` represents a record where field `f` has type /// `eval(env.snoc(eval(env, x)), r.fields1[f])`. - Record(Row), + Record(Row), /// Type constructor for singleton types. /// @@ -62,14 +62,14 @@ pub enum TyS_ { /// /// A term `x` of type `Sing(ty, tm)` is a term of `ty` that is convertible with /// `tm`. - Sing(TyS, TmS), + Sing(BaseTyS, BaseTmS), /// Type constructor for identity types. /// /// Example syntax: `a == b` (assuming `a` and `b` are terms that synthesize the same type). /// /// A term `p` of type `a == b` is a proof that `a` and `b` are equal. - Id(TyS, TmS, TmS), + Id(BaseTyS, BaseTmS, BaseTmS), /// Type constructor for specialized types. /// @@ -80,14 +80,7 @@ pub enum TyS_ { /// /// In order to form this type, it must be the case that `d[p]` is a subtype of /// the type of the field at path `p`. - Specialize(TyS, Vec<(Vec<(FieldName, LabelSegment)>, TyS)>), - - /// Type constructor for the unit type. - /// - /// Example syntax: `Unit`. - /// - /// All terms of this type are convertible with `tt : Unit`. - Unit, + Specialize(BaseTyS, Vec<(Vec<(FieldName, LabelSegment)>, BaseTyS)>), /// A metavar. /// @@ -96,86 +89,80 @@ pub enum TyS_ { Meta(MetaVar), } -/// Syntax for total types, dereferences to [TyS_]. +/// Syntax for total types, dereferences to [BaseTyS_]. /// /// See [crate::tt] for an explanation of what total types are, and for an /// explanation of our approach to Rc pointers in abstract syntax trees. #[derive(Clone, Deref)] #[deref(forward)] -pub struct TyS(Rc); +pub struct BaseTyS(Rc); -impl TyS { - /// Smart constructor for [TyS], [TyS_::TopVar] case. +impl BaseTyS { + /// Smart constructor for [BaseTyS], [BaseTyS_::TopVar] case. pub fn topvar(name: TopVarName) -> Self { - Self(Rc::new(TyS_::TopVar(name))) + Self(Rc::new(BaseTyS_::TopVar(name))) } - /// Smart constructor for [TyS], [TyS_::Object] case. + /// Smart constructor for [BaseTyS], [BaseTyS_::Object] case. pub fn object(object_type: ObType) -> Self { - Self(Rc::new(TyS_::Object(object_type))) + Self(Rc::new(BaseTyS_::Object(object_type))) } - /// Smart constructor for [TyS], [TyS_::Morphism] case. - pub fn morphism(morphism_type: MorType, dom: TmS, cod: TmS) -> Self { - Self(Rc::new(TyS_::Morphism(morphism_type, dom, cod))) + /// Smart constructor for [BaseTyS], [BaseTyS_::Morphism] case. + pub fn morphism(morphism_type: MorType, dom: BaseTmS, cod: BaseTmS) -> Self { + Self(Rc::new(BaseTyS_::Morphism(morphism_type, dom, cod))) } - /// Smart constructor for [TyS], [TyS_::Record] case. - pub fn record(fields: Row) -> Self { - Self(Rc::new(TyS_::Record(fields))) + /// Smart constructor for [BaseTyS], [BaseTyS_::Record] case. + pub fn record(fields: Row) -> Self { + Self(Rc::new(BaseTyS_::Record(fields))) } - /// Smart constructor for [TyS], [TyS_::Sing] case. - pub fn sing(ty: TyS, tm: TmS) -> Self { - Self(Rc::new(TyS_::Sing(ty, tm))) + /// Smart constructor for [BaseTyS], [BaseTyS_::Sing] case. + pub fn sing(ty: BaseTyS, tm: BaseTmS) -> Self { + Self(Rc::new(BaseTyS_::Sing(ty, tm))) } - /// Smart constructor for [TyS], [TyS_::Id] case. - pub fn id(ty: TyS, tm1: TmS, tm2: TmS) -> Self { - Self(Rc::new(TyS_::Id(ty, tm1, tm2))) + /// Smart constructor for [BaseTyS], [BaseTyS_::Id] case. + pub fn id(ty: BaseTyS, tm1: BaseTmS, tm2: BaseTmS) -> Self { + Self(Rc::new(BaseTyS_::Id(ty, tm1, tm2))) } - /// Smart constructor for [TyS], [TyS_::Specialize] case. + /// Smart constructor for [BaseTyS], [BaseTyS_::Specialize] case. pub fn specialize( - ty: TyS, - specializations: Vec<(Vec<(FieldName, LabelSegment)>, TyS)>, + ty: BaseTyS, + specializations: Vec<(Vec<(FieldName, LabelSegment)>, BaseTyS)>, ) -> Self { - Self(Rc::new(TyS_::Specialize(ty, specializations))) - } - - /// Smart constructor for [TyS], [TyS_::Unit] case. - pub fn unit() -> Self { - Self(Rc::new(TyS_::Unit)) + Self(Rc::new(BaseTyS_::Specialize(ty, specializations))) } - /// Smart constructor for [TyS], [TyS_::Meta] case. + /// Smart constructor for [BaseTyS], [BaseTyS_::Meta] case. pub fn meta(mv: MetaVar) -> Self { - Self(Rc::new(TyS_::Meta(mv))) + Self(Rc::new(BaseTyS_::Meta(mv))) } } -impl ToDoc for TyS { +impl ToDoc for BaseTyS { fn to_doc<'a>(&self) -> D<'a> { match &**self { - TyS_::TopVar(name) => t(format!("{}", name)), - TyS_::Object(ob_type) => t(format!("{}", ob_type)), - TyS_::Morphism(mor_type, dom, cod) => { + BaseTyS_::TopVar(name) => t(format!("{}", name)), + BaseTyS_::Object(ob_type) => t(format!("{}", ob_type)), + BaseTyS_::Morphism(mor_type, dom, cod) => { mor_type.to_doc().parens() + tuple([dom.to_doc(), cod.to_doc()]) } - TyS_::Record(fields) => tuple(fields.iter().map(|(_, (label, ty))| { + BaseTyS_::Record(fields) => tuple(fields.iter().map(|(_, (label, ty))| { binop(t(":"), t(format!("{}", label)).group(), ty.to_doc()) })), - TyS_::Sing(_, tm) => t("@sing") + s() + tm.to_doc(), - TyS_::Id(_, tm1, tm2) => binop(t("=="), tm1.to_doc(), tm2.to_doc()), - TyS_::Specialize(ty, d) => binop( + BaseTyS_::Sing(_, tm) => t("@sing") + s() + tm.to_doc(), + BaseTyS_::Id(_, tm1, tm2) => binop(t("=="), tm1.to_doc(), tm2.to_doc()), + BaseTyS_::Specialize(ty, d) => binop( t("&"), ty.to_doc(), tuple( d.iter().map(|(name, ty)| binop(t(":"), t(path_to_string(name)), ty.to_doc())), ), ), - TyS_::Unit => t("Unit"), - TyS_::Meta(mv) => t(format!("?{}", mv.id)), + BaseTyS_::Meta(mv) => t(format!("?{}", mv.id)), } } } @@ -188,141 +175,320 @@ fn path_to_string(path: &[(FieldName, LabelSegment)]) -> String { out } -impl fmt::Display for TyS { +impl fmt::Display for BaseTyS { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_doc().group().pretty()) } } -/// Inner enum for [TmS]. -pub enum TmS_ { - /// A reference to a top-level constant def. - TopVar(TopVarName), +/// Inner enum for [BaseTmS]. +pub enum BaseTmS_ { /// An application of a top-level term judgment to arguments. - TopApp(TopVarName, Vec), + /// + /// A closed term (a nullary `def`, e.g. `tt : Unit`) is the empty-argument + /// case `TopApp(name, [])`. + TopApp(TopVarName, Vec), /// Variable syntax. /// /// We use a backward index, as when we evaluate we store the /// environment in a [bwd::Bwd], and this indexes into that. Var(BwdIdx, VarName, LabelSegment), /// Record introduction. - Cons(Row), + Cons(Row), /// Record elimination. - Proj(TmS, FieldName, LabelSegment), - /// Unit introduction. - /// - /// Note that eta-expansion takes care of elimination for units. - Tt, + Proj(BaseTmS, FieldName, LabelSegment), /// Identity morphism at an object. - Id(TmS), + Id(BaseTmS), /// Tabulation of a morphism. - Tab(TmS), + Tab(BaseTmS), /// Composite of two morphisms. - Compose(TmS, TmS), + Compose(BaseTmS, BaseTmS), /// Application of an object operation in the theory. - ObApp(VarName, TmS), + ObApp(VarName, BaseTmS), /// List of objects. - List(Vec), + List(Vec), /// A metavar. /// /// This only appears when we have an error in elaboration. Meta(MetaVar), } -/// Syntax for total terms, dereferences to [TmS_]. +/// Syntax for total terms, dereferences to [BaseTmS_]. /// /// See [crate::tt] for an explanation of what total types are, and for an /// explanation of our approach to Rc pointers in abstract syntax trees. #[derive(Clone, Deref)] #[deref(forward)] -pub struct TmS(Rc); - -impl TmS { - /// Smart constructor for [TmS], [TmS_::TopVar] case. - pub fn topvar(var_name: VarName) -> Self { - Self(Rc::new(TmS_::TopVar(var_name))) - } +pub struct BaseTmS(Rc); - /// Smart constructor for [TmS], [TmS_::TopApp] case. - pub fn topapp(var_name: VarName, args: Vec) -> Self { - Self(Rc::new(TmS_::TopApp(var_name, args))) +impl BaseTmS { + /// Smart constructor for [BaseTmS], [BaseTmS_::TopApp] case. + pub fn topapp(var_name: VarName, args: Vec) -> Self { + Self(Rc::new(BaseTmS_::TopApp(var_name, args))) } - /// Smart constructor for [TmS], [TmS_::Var] case. + /// Smart constructor for [BaseTmS], [BaseTmS_::Var] case. pub fn var(bwd_idx: BwdIdx, var_name: VarName, label: LabelSegment) -> Self { - Self(Rc::new(TmS_::Var(bwd_idx, var_name, label))) + Self(Rc::new(BaseTmS_::Var(bwd_idx, var_name, label))) } - /// Smart constructor for [TmS], [TmS_::Cons] case. - pub fn cons(row: Row) -> Self { - Self(Rc::new(TmS_::Cons(row))) + /// Smart constructor for [BaseTmS], [BaseTmS_::Cons] case. + pub fn cons(row: Row) -> Self { + Self(Rc::new(BaseTmS_::Cons(row))) } - /// Smart constructor for [TmS], [TmS_::Proj] case. - pub fn proj(tm_s: TmS, field_name: FieldName, label: LabelSegment) -> Self { - Self(Rc::new(TmS_::Proj(tm_s, field_name, label))) + /// Smart constructor for [BaseTmS], [BaseTmS_::Proj] case. + pub fn proj(tm_s: BaseTmS, field_name: FieldName, label: LabelSegment) -> Self { + Self(Rc::new(BaseTmS_::Proj(tm_s, field_name, label))) } - /// Smart constructor for [TmS], [TmS_::Tt] case. - pub fn tt() -> Self { - Self(Rc::new(TmS_::Tt)) + /// Smart constructor for [BaseTmS], [BaseTmS_::Id] case. + pub fn id(ob: BaseTmS) -> Self { + Self(Rc::new(BaseTmS_::Id(ob))) } - /// Smart constructor for [TmS], [TmS_::Id] case. - pub fn id(ob: TmS) -> Self { - Self(Rc::new(TmS_::Id(ob))) + /// Smart constructor for [BaseTmS], [BaseTmS_::Tab] case. + pub fn tab(mor: BaseTmS) -> Self { + Self(Rc::new(BaseTmS_::Tab(mor))) } - /// Smart constructor for [TmS], [TmS_::Tab] case. - pub fn tab(mor: TmS) -> Self { - Self(Rc::new(TmS_::Tab(mor))) + /// Smart constructor for [BaseTmS], [BaseTmS_::Compose] case. + pub fn compose(f: BaseTmS, g: BaseTmS) -> Self { + Self(Rc::new(BaseTmS_::Compose(f, g))) } - /// Smart constructor for [TmS], [TmS_::Compose] case. - pub fn compose(f: TmS, g: TmS) -> Self { - Self(Rc::new(TmS_::Compose(f, g))) + /// Smart constructor for [BaseTmS], [BaseTmS_::ObApp] case. + pub fn ob_app(name: VarName, x: BaseTmS) -> Self { + Self(Rc::new(BaseTmS_::ObApp(name, x))) } - /// Smart constructor for [TmS], [TmS_::ObApp] case. - pub fn ob_app(name: VarName, x: TmS) -> Self { - Self(Rc::new(TmS_::ObApp(name, x))) + /// Smart constructor for [BaseTmS], [BaseTmS_::List] case. + pub fn list(elems: Vec) -> Self { + Self(Rc::new(BaseTmS_::List(elems))) } - /// Smart constructor for [TmS], [TmS_::List] case. - pub fn list(elems: Vec) -> Self { - Self(Rc::new(TmS_::List(elems))) - } - - /// Smart constructor for [TmS], [TmS_::Meta] case. + /// Smart constructor for [BaseTmS], [BaseTmS_::Meta] case. pub fn meta(mv: MetaVar) -> Self { - Self(Rc::new(TmS_::Meta(mv))) + Self(Rc::new(BaseTmS_::Meta(mv))) } } -impl ToDoc for TmS { +impl ToDoc for BaseTmS { fn to_doc<'a>(&self) -> D<'a> { match &**self { - TmS_::TopVar(name) => t(format!("{}", name)), - TmS_::TopApp(name, args) => { + BaseTmS_::TopApp(name, args) if args.is_empty() => t(format!("{}", name)), + BaseTmS_::TopApp(name, args) => { t(format!("{}", name)) + tuple(args.iter().map(|arg| arg.to_doc())) } - TmS_::Var(_, _, label) => t(format!("{}", label)), - TmS_::Proj(tm, _, label) => tm.to_doc() + t(format!(".{}", label)), - TmS_::Cons(fields) => tuple(fields.iter().map(|(_, (label, field))| { + BaseTmS_::Var(_, _, label) => t(format!("{}", label)), + BaseTmS_::Proj(tm, _, label) => tm.to_doc() + t(format!(".{}", label)), + BaseTmS_::Cons(fields) => tuple(fields.iter().map(|(_, (label, field))| { binop(t(":="), t(format!("{}", label)), field.to_doc()) })), - TmS_::Id(ob) => (t("@id") + s() + ob.to_doc()).parens(), - TmS_::Tab(mor) => (t("@tab") + s() + mor.to_doc()).parens(), - TmS_::Compose(f, g) => binop(t("·"), f.to_doc(), g.to_doc()), - TmS_::ObApp(name, x) => unop(t(format!("@{name}")), x.to_doc()), - TmS_::List(elems) => tuple(elems.iter().map(|elem| elem.to_doc())), - TmS_::Tt => t("tt"), - TmS_::Meta(mv) => t(format!("?{}", mv.id)), + BaseTmS_::Id(ob) => (t("@id") + s() + ob.to_doc()).parens(), + BaseTmS_::Tab(mor) => (t("@tab") + s() + mor.to_doc()).parens(), + BaseTmS_::Compose(f, g) => binop(t("·"), f.to_doc(), g.to_doc()), + BaseTmS_::ObApp(name, x) => unop(t(format!("@{name}")), x.to_doc()), + BaseTmS_::List(elems) => tuple(elems.iter().map(|elem| elem.to_doc())), + BaseTmS_::Meta(mv) => t(format!("?{}", mv.id)), + } + } +} + +impl fmt::Display for BaseTmS { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_doc().group().pretty()) + } +} + +/// Inner enum for [FiberTyS]. +/// +/// Fiber types type the fiber world — instances of a model and their +/// elements — mirroring how [`BaseTyS`] types the base world (models). +/// See [`crate::tt::toplevel`] for the comprehension-category picture. +/// The constructors parallel the base world: [`TopVar`](Self::TopVar) +/// references a top-level instance, [`Over`](Self::Over) is the atomic +/// fiber-element type, [`Record`](Self::Record) assembles them into an +/// instance, and [`Id`](Self::Id) imposes a (propositional) equation — +/// just as [`BaseTyS_::TopVar`] and [`BaseTyS_::Id`] do in the base. +pub enum FiberTyS_ { + /// A reference to a top-level instance declaration, as in a + /// sub-instance import `we : Edge`. Mirrors [`BaseTyS_::TopVar`]: it + /// appears only in *syntax* and exists to preserve the instance's + /// name for display — like base top-vars, it is resolved away in the + /// value world (there is no `FiberTyV_::TopVar`), where it becomes the + /// referenced instance's [`Record`](Self::Record). + TopVar(TopVarName), + /// The type of a fiber element lying over a codomain object `obj`. + /// + /// `obj` is a base object *term* (rooted at the codomain model), so it + /// may be a plain generator (`self.V`), or a modal object such as a + /// list `[M, M]` or a tensor `@tensor [H, M]`. Comparing two + /// `Over` types is comparing their base objects, so modal objects need + /// no special handling. No surface syntax — its inhabitants + /// ([`FiberTmS`]) are introduced by set-literal clauses `field := + /// [...]`, projection out of a sub-instance import, fiber list/object + /// -operation literals, and codomain-morphism application. + Over(BaseTmS), + /// An instance of a model — an object of the fiber over the codomain + /// model — presented as a record of fiber types. A generator is an + /// [`Over`](Self::Over) field, a sub-instance import is a nested + /// [`Record`](Self::Record) field, and an equation is an + /// [`Id`](Self::Id) field. This is what `instance I : X := [...]` + /// elaborates to, and also the type of a sub-instance import `we : + /// Edge` (whose generators are then projected as `we.e`). + Record(Row), + /// A propositional equation between two fiber elements of the given + /// fiber type, asserted to hold in the enclosing instance. Mirrors + /// [`BaseTyS_::Id`]; like it, these are proof-irrelevant. + Id(FiberTyS, FiberTmS, FiberTmS), +} + +/// Syntax for fiber types, dereferences to [FiberTyS_]. +#[derive(Clone, Deref)] +#[deref(forward)] +pub struct FiberTyS(Rc); + +impl FiberTyS { + /// Smart constructor for [FiberTyS], [FiberTyS_::TopVar] case. + pub fn topvar(name: TopVarName) -> Self { + Self(Rc::new(FiberTyS_::TopVar(name))) + } + + /// Smart constructor for [FiberTyS], [FiberTyS_::Over] case. + pub fn over(obj: BaseTmS) -> Self { + Self(Rc::new(FiberTyS_::Over(obj))) + } + + /// Smart constructor for [FiberTyS], [FiberTyS_::Record] case. + pub fn record(fields: Row) -> Self { + Self(Rc::new(FiberTyS_::Record(fields))) + } + + /// Smart constructor for [FiberTyS], [FiberTyS_::Id] case. + pub fn id(ty: FiberTyS, tm1: FiberTmS, tm2: FiberTmS) -> Self { + Self(Rc::new(FiberTyS_::Id(ty, tm1, tm2))) + } +} + +impl ToDoc for FiberTyS { + fn to_doc<'a>(&self) -> D<'a> { + match &**self { + FiberTyS_::TopVar(name) => t(format!("{}", name)), + FiberTyS_::Over(obj) => t("Over(") + obj.to_doc() + t(")"), + FiberTyS_::Record(fields) => tuple(fields.iter().map(|(_, (label, ty))| { + binop(t(":"), t(format!("{}", label)).group(), ty.to_doc()) + })), + FiberTyS_::Id(_, tm1, tm2) => binop(t("=="), tm1.to_doc(), tm2.to_doc()), + } + } +} + +impl fmt::Display for FiberTyS { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_doc().group().pretty()) + } +} + +/// Inner enum for [FiberTmS]: a term of a fiber type, i.e. an element of +/// an instance. +/// +/// Fiber terms reference the elaborator's *fiber* scope (generators and +/// sub-instance imports), which is separate from the base context; see +/// [`crate::tt::context::Context`]. They are all neutral — there is no +/// fiber introduction form yet (mapping out of an instance by a record +/// literal is future work), so a fiber term is always a variable, a +/// projection, or a codomain-morphism application. +pub enum FiberTmS_ { + /// A fiber-context variable: a generator or a sub-instance import. + /// Backward index into the fiber environment. + Var(BwdIdx, VarName, LabelSegment), + /// Projection of a generator out of a sub-instance import, e.g. + /// `we.e`. + Proj(FiberTmS, FieldName, LabelSegment), + /// A fiber list literal `[a, b, ...]` (possibly empty). Its fiber type + /// is `Over([A, B, ...])` where each `x_i : Over(A_i)`. Mirrors base + /// [`BaseTmS_::List`]; used to supply the (modal) list argument of a + /// multi-ary morphism, e.g. `op[x, x]`. + List(Vec), + /// Application of a theory object-operation to a fiber element, e.g. + /// `@tensor [a, b]`. Mirrors base [`BaseTmS_::ObApp`]; its fiber type + /// is `Over(@op ...)` over the operation applied to the argument's + /// base object. + ObApp(VarName, FiberTmS), + /// Application of a codomain morphism to a fiber element. Arguments, + /// in order: the *path* to the morphism in the codomain (a single + /// segment like `src`, or a nested one like `Add.op` for a morphism of + /// a sub-model), the codomain object it lands at (a base object term, + /// stored so the result fiber type is recoverable without re-deriving + /// it), and the fiber-typed argument (e.g. the elaboration of `we.e`, + /// or a fiber list `[x, x]` for a multi-ary morphism). + /// + /// Example: in `src(we.e) := v1`, the LHS elaborates to + /// `OverApp([src], self.V, Proj(Var(we), e, e))` of fiber type + /// `Over(self.V)`. + OverApp(Vec<(FieldName, LabelSegment)>, BaseTmS, FiberTmS), + /// A metavar (elaboration-error placeholder). + Meta(MetaVar), +} + +/// Syntax for fiber terms, dereferences to [FiberTmS_]. +#[derive(Clone, Deref)] +#[deref(forward)] +pub struct FiberTmS(Rc); + +impl FiberTmS { + /// Smart constructor for [FiberTmS], [FiberTmS_::Var] case. + pub fn var(bwd_idx: BwdIdx, var_name: VarName, label: LabelSegment) -> Self { + Self(Rc::new(FiberTmS_::Var(bwd_idx, var_name, label))) + } + + /// Smart constructor for [FiberTmS], [FiberTmS_::Proj] case. + pub fn proj(tm: FiberTmS, field_name: FieldName, label: LabelSegment) -> Self { + Self(Rc::new(FiberTmS_::Proj(tm, field_name, label))) + } + + /// Smart constructor for [FiberTmS], [FiberTmS_::List] case. + pub fn list(elems: Vec) -> Self { + Self(Rc::new(FiberTmS_::List(elems))) + } + + /// Smart constructor for [FiberTmS], [FiberTmS_::ObApp] case. + pub fn ob_app(name: VarName, arg: FiberTmS) -> Self { + Self(Rc::new(FiberTmS_::ObApp(name, arg))) + } + + /// Smart constructor for [FiberTmS], [FiberTmS_::OverApp] case. + pub fn over_app(mor: Vec<(FieldName, LabelSegment)>, cod: BaseTmS, inner: FiberTmS) -> Self { + Self(Rc::new(FiberTmS_::OverApp(mor, cod, inner))) + } + + /// Smart constructor for [FiberTmS], [FiberTmS_::Meta] case. + pub fn meta(mv: MetaVar) -> Self { + Self(Rc::new(FiberTmS_::Meta(mv))) + } +} + +impl ToDoc for FiberTmS { + fn to_doc<'a>(&self) -> D<'a> { + match &**self { + FiberTmS_::Var(_, _, label) => t(format!("{}", label)), + FiberTmS_::Proj(tm, _, label) => tm.to_doc() + t(format!(".{}", label)), + FiberTmS_::List(elems) => tuple(elems.iter().map(|e| e.to_doc())), + FiberTmS_::ObApp(name, arg) => unop(t(format!("@{name}")), arg.to_doc()), + FiberTmS_::OverApp(path, _, inner) => { + let mut d = inner.to_doc(); + for (_, label) in path { + d = d + t(format!(".{label}")); + } + d + } + FiberTmS_::Meta(mv) => t(format!("?{}", mv.id)), } } } -impl fmt::Display for TmS { +impl fmt::Display for FiberTmS { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_doc().group().pretty()) } diff --git a/packages/catlog/src/tt/text_elab.rs b/packages/catlog/src/tt/text_elab.rs index 64532fc95..a012f2fd1 100644 --- a/packages/catlog/src/tt/text_elab.rs +++ b/packages/catlog/src/tt/text_elab.rs @@ -6,6 +6,7 @@ use scopeguard::{ScopeGuard, guard}; use fnotation::{ParseConfig, parser::Prec}; use tattle::declare_error; +use super::fiber_elab::{CODOMAIN_BINDER, FiberElab, FiberError, path_str}; use super::{ context::*, eval::*, modelgen::*, prelude::*, stx::*, theory::*, toplevel::*, val::*, wd::*, }; @@ -24,7 +25,17 @@ pub const TT_PARSE_CONFIG: ParseConfig = ParseConfig::new( ("==", Prec::nonassoc(30)), ], &[":", ":=", "&", "Unit", "Hom", "*", "=="], - &["type", "def", "syn", "chk", "norm", "generate", "uwd", "set_theory"], + &[ + "model", + "def", + "instance", + "syn", + "chk", + "norm", + "generate", + "uwd", + "set_theory", + ], ); /// The result of elaborating a top-level statement. @@ -115,12 +126,12 @@ impl TopElaborator { }, _ => self.error(tn.loc, "expected a theory name"), }, - "type" => { + "model" => { let theory = self.get_theory(tn.loc)?; let (name, ty_n) = self.bare_def(tn.body).or_else(|| { self.error( tn.loc, - "unknown syntax for type declaration, expected := ", + "unknown syntax for model declaration, expected := ", ) })?; let (ty_s, ty_v) = self.elaborator(&theory, toplevel).ty(ty_n); @@ -160,15 +171,50 @@ impl TopElaborator { } None => { let mut elab = self.elaborator(&theory, toplevel); - let (_, ty_v) = elab.ty(ty_n); - let (tm_s, tm_v) = elab.chk(&ty_v, tm_n); + let (ret_ty_s, ret_ty_v) = elab.ty(ty_n); + let (body_s, _) = elab.chk(&ret_ty_v, tm_n); + // A closed (empty-context) term: a tight transformation + // S -> Unit. Unit is the empty record, i.e. the empty model. + // A tight map into the empty model exists only when S is itself empty, + // so the sole closed `def` is the identity on the empty + // model, `tt : Unit`. Such a closed term is just a nullary + // `Def` (empty argument context). Some(TopElabResult::Declaration( name, - TopDecl::DefConst(DefConst::new(theory.clone(), tm_s, tm_v, ty_v)), + TopDecl::Def(Def::new(theory.clone(), Row::empty(), ret_ty_s, body_s)), )) } } } + "instance" => { + let theory = self.get_theory(tn.loc)?; + let (name, args_n, ty_n, tm_n) = self.annotated_def(tn.body).or_else(|| { + self.error( + tn.loc, + "unknown syntax for instance declaration, expected : := [...]", + ) + })?; + if args_n.is_some() { + return self.error( + tn.loc, + "an instance takes no arguments; for a parameterized map between \ + models, use `def`", + ); + } + let mut elab = self.elaborator(&theory, toplevel); + let (_, ret_ty_v) = elab.ty(ty_n); + // An instance body is checked against its codomain model, a + // record type. + let BaseTyV_::Record(r) = &*ret_ty_v else { + return self + .error(tn.loc, "an instance must be declared against a record type"); + }; + let (tm_s, tm_v) = elab.instance_body(r, tm_n); + Some(TopElabResult::Declaration( + name, + TopDecl::Instance(Instance::new(theory.clone(), tm_s, tm_v, ret_ty_v)), + )) + } "syn" => { let theory = self.get_theory(tn.loc)?; let (ctx_ns, n) = self.expr_with_context(tn.body); @@ -184,8 +230,38 @@ impl TopElaborator { ))) } "norm" => { - let theory = self.get_theory(tn.loc)?; let (ctx_ns, n) = self.expr_with_context(tn.body); + // `norm [inst] `: normalize a single instance + // term in the scope of the existing instance `inst`, showing + // its flat (composite) normal form. A fiber term is neutral + // at the type-theory level; the composition happens when it is + // extracted to a model-instance term (see + // [`normalize_instance_term`]). + if let [single] = ctx_ns + && let Var(inst_name) = single.ast0() + && let Some(TopDecl::Instance(inst)) = + toplevel.declarations.get(&name_seg(*inst_name)) + { + let mut elab = self.elaborator(&inst.theory, toplevel); + elab.enter_instance(inst)?; + let (_, tm_v, ty_v) = elab.fiber_syn(n); + let FiberTyV_::Over(over) = &*ty_v else { + return elab + .error("norm expects an instance element (a term over an object)"); + }; + let over = over.clone(); + return match normalize_instance_term( + toplevel, + &inst.theory.definition, + inst, + &tm_v, + &over, + ) { + Ok(nt) => Some(TopElabResult::Output(nt.render())), + Err(msg) => self.error(tn.loc, msg), + }; + } + let theory = self.get_theory(tn.loc)?; let mut elab = self.elaborator(&theory, toplevel); for ctx_n in ctx_ns { let (name, label, _, ty_v) = elab.binding(ctx_n)?; @@ -267,6 +343,21 @@ impl<'a> Elaborator<'a> { } } + /// The codomain model of the instance body currently being + /// elaborated, if any. Its fields are the codomain's generators, + /// looked up by name by the instance-clause arms. + /// + /// The model is held as a record variable in the context under the + /// reserved [`CODOMAIN_BINDER`] name (see + /// [`Self::instance_body`]). + fn instance_codomain(&self) -> Option> { + let (_, _, ty) = self.ctx.lookup(name_seg(CODOMAIN_BINDER))?; + match &*ty? { + BaseTyV_::Record(r) => Some(Rc::new(r.clone())), + _ => None, + } + } + fn theory(&self) -> &TheoryDef { &self.theory.definition } @@ -302,33 +393,33 @@ impl<'a> Elaborator<'a> { None } - fn ty_hole(&mut self) -> (TyS, TyV) { + fn ty_hole(&mut self) -> (BaseTyS, BaseTyV) { let ty_m = self.fresh_meta(); - (TyS::meta(ty_m), TyV::meta(ty_m)) + (BaseTyS::meta(ty_m), BaseTyV::meta(ty_m)) } - fn ty_error(&mut self, msg: impl Into) -> (TyS, TyV) { + fn ty_error(&mut self, msg: impl Into) -> (BaseTyS, BaseTyV) { self.reporter.error_option_loc(self.loc, ELAB_ERROR, msg.into()); self.ty_hole() } - fn syn_hole(&mut self) -> (TmS, TmV, TyV) { + fn syn_hole(&mut self) -> (BaseTmS, BaseTmV, BaseTyV) { let tm_m = self.fresh_meta(); let ty_m = self.fresh_meta(); - (TmS::meta(tm_m), TmV::meta(tm_m), TyV::meta(ty_m)) + (BaseTmS::meta(tm_m), BaseTmV::meta(tm_m), BaseTyV::meta(ty_m)) } - fn syn_error(&mut self, msg: impl Into) -> (TmS, TmV, TyV) { + fn syn_error(&mut self, msg: impl Into) -> (BaseTmS, BaseTmV, BaseTyV) { self.reporter.error_option_loc(self.loc, ELAB_ERROR, msg.into()); self.syn_hole() } - fn chk_hole(&mut self) -> (TmS, TmV) { + fn chk_hole(&mut self) -> (BaseTmS, BaseTmV) { let tm_m = self.fresh_meta(); - (TmS::meta(tm_m), TmV::meta(tm_m)) + (BaseTmS::meta(tm_m), BaseTmV::meta(tm_m)) } - fn chk_error(&mut self, msg: impl Into) -> (TmS, TmV) { + fn chk_error(&mut self, msg: impl Into) -> (BaseTmS, BaseTmV) { self.reporter.error_option_loc(self.loc, ELAB_ERROR, msg.into()); self.chk_hole() } @@ -337,10 +428,10 @@ impl<'a> Elaborator<'a> { Evaluator::new(self.toplevel, self.ctx.env.clone(), self.ctx.scope.len()) } - fn intro(&mut self, name: VarName, label: LabelSegment, ty: Option) -> TmV { - let v = TmV::neu( + fn intro(&mut self, name: VarName, label: LabelSegment, ty: Option) -> BaseTmV { + let v = BaseTmV::neu( TmN::var(self.ctx.scope.len().into(), name, label), - ty.clone().unwrap_or(TyV::unit()), + ty.clone().unwrap_or(BaseTyV::empty_record()), ); let v = if ty.is_some() { self.evaluator().eta(&v, ty.as_ref()) @@ -352,7 +443,475 @@ impl<'a> Elaborator<'a> { v } - fn binding(&mut self, n: &FNtn) -> Option<(VarName, LabelSegment, TyS, TyV)> { + /// Report a text-surface error and return a synthesis hole. Errors + /// from the shared fiber machinery go through + /// [`FiberElab::report_fiber`] instead. + fn fiber_syn_error_msg(&mut self, msg: impl Into) -> (FiberTmS, FiberTmV, FiberTyV) { + self.reporter.error_option_loc(self.loc, ELAB_ERROR, msg.into()); + self.fiber_syn_hole() + } + + /// Synthesize a fiber term and its fiber type. A fiber term is a + /// generator/import variable, a projection out of a sub-instance + /// (`we.e`), or a codomain-morphism application (`src(we.e)`). + fn fiber_syn(&mut self, n: &FNtn) -> (FiberTmS, FiberTmV, FiberTyV) { + let mut elab = self.enter(n.loc()); + match n.ast0() { + Var(name) => match elab.lookup_fiber_tm(name_seg(*name)) { + Some(r) => r, + None => elab.fiber_syn_error(FiberError::UnknownElement(name.to_string())), + }, + // Projection of a generator out of a sub-instance import: `we.e`. + App1(recv_n, L(_, Field(f))) => { + let (recv_s, recv_v, recv_ty) = elab.fiber_syn(recv_n); + elab.fiber_proj(recv_s, recv_v, &recv_ty, name_seg(*f)) + } + // A theory object-operation on a fiber element, e.g. + // `@tensor [a, b]`. The resulting element lies over the + // operation applied to the argument's base object. + App1(L(_, Prim(op)), arg_n) => { + let op_name = name_seg(*op); + if !elab.check_ob_op(op_name) { + return elab.fiber_syn_hole(); + } + let (arg_s, arg_v, arg_ty) = elab.fiber_syn(arg_n); + elab.fiber_ob_app(op_name, arg_s, arg_v, &arg_ty) + } + // Codomain-morphism application `f(arg)`. The morphism `f` may + // be a nested path into the codomain (e.g. `Add.op`), so its + // head is a projection chain, not just a bare variable. + App1(head_n, arg_n) => { + let Some(path) = morphism_path(head_n) else { + return elab.fiber_syn_error_msg( + "expected a codomain morphism (a name or path like `Add.op`) applied \ + to a fiber element", + ); + }; + // A display label for the argument, used only in errors. + let label = match arg_n.ast0() { + Var(x) => x.to_string(), + App1(_, L(_, Field(fld))) => fld.to_string(), + _ => "argument".to_string(), + }; + let (arg_s, arg_v, arg_ty) = elab.fiber_syn(arg_n); + elab.apply_codomain_morphism(&path, arg_s, arg_v, arg_ty, &label) + } + // A fiber list literal `[a, b, ...]` (the argument of a + // multi-ary morphism); its object is the list of the elements' + // objects. + Tuple(elems) => { + let mut ss = Vec::with_capacity(elems.len()); + let mut vs = Vec::with_capacity(elems.len()); + let mut objs = Vec::with_capacity(elems.len()); + for e in elems.iter() { + let (s, v, ty) = elab.fiber_syn(e); + let FiberTyV_::Over(o) = &*ty else { + return elab.fiber_syn_error(FiberError::ListElementNotOver); + }; + objs.push(o.clone()); + ss.push(s); + vs.push(v); + } + (FiberTmS::list(ss), FiberTmV::list(vs), FiberTyV::over(BaseTmV::list(objs))) + } + _ => elab.fiber_syn_error_msg( + "expected a fiber element: a generator, a projection `we.e`, a fiber list \ + `[..]`, an object operation `@op [..]`, or a morphism application `f[..]`", + ), + } + } + + /// Check a fiber term against an expected fiber type. Fiber terms are + /// all synthesizing, so this synthesizes and checks convertibility. + fn fiber_chk(&mut self, expected: &FiberTyV, n: &FNtn) -> (FiberTmS, FiberTmV) { + let syn = self.fiber_syn(n); + self.check_fiber(syn, expected) + } + + /// Elaborate a fiber-type annotation. Used for sub-instance imports + /// (`we : Edge`, where `Edge` names a top-level instance) and anonymous + /// equations (`name : (a == b)`). + fn fiber_ty(&mut self, n: &FNtn) -> Option<(FiberTyS, FiberTyV)> { + match n.ast0() { + Var(name) => { + let topvar = name_seg(*name); + let (imported_codomain, val) = match self.toplevel.declarations.get(&topvar) { + Some(TopDecl::Instance(i)) => (i.codomain.clone(), i.val.clone()), + _ => { + return self.error(format!( + "{name} must reference a top-level instance declaration" + )); + } + }; + // The imported instance must be an instance of the *same* + // model as the enclosing one — otherwise its `Over` paths + // refer to objects foreign to this codomain, producing a + // malformed instance. + if let Some(cod) = self.instance_codomain() { + let enclosing = BaseTyV::record((*cod).clone()); + if !self.codomains_match(&enclosing, &imported_codomain) { + self.report_fiber(FiberError::ImportCodomainMismatch(name.to_string())); + return None; + } + } + // The syntax keeps the instance's name (for display); the + // value is the referenced instance's resolved record, just + // as a base top-var evaluates to its model. See + // [`FiberTyS_::TopVar`]. + Some((FiberTyS::topvar(topvar), val)) + } + App2(L(_, Keyword("==")), a_n, b_n) => { + let (a_s, a_v, a_ty) = self.fiber_syn(a_n); + let (b_s, b_v, b_ty) = self.fiber_syn(b_n); + if let Err(e) = self.evaluator().convertible_fiber_ty(&a_ty, &b_ty) { + self.report_fiber(FiberError::InconvertibleEquationSides( + e.pretty().to_string(), + )); + return None; + } + let FiberTyV_::Over(obj) = &*a_ty else { + self.report_fiber(FiberError::EquationNotOver); + return None; + }; + Some(self.fiber_id_field(&a_ty, obj, a_s, a_v, b_s, b_v)) + } + _ => self.error("expected an instance name or an equation `a == b`"), + } + } + + /// The unit type, elaborated as the empty record — i.e. the empty + /// model. `Unit` and `tt` are surface sugar for the empty record type + /// and its unique element, the empty cons `[]`. + fn empty_record_ty(&self) -> (BaseTyS, BaseTyV) { + (BaseTyS::record(Row::empty()), BaseTyV::empty_record()) + } + + /// The value of the codomain `self` binding — the eta-expanded model + /// record. Codomain object values (`self.V`, morphism dom/cod) are + /// obtained by projecting / evaluating field types against it, so that + /// every codomain object is rooted at the same `self` neutral and thus + /// compares equal under [`Evaluator::equal_tm`]. + fn codomain_self_value(&self) -> Option { + let (i, _, _) = self.ctx.lookup(name_seg(CODOMAIN_BINDER))?; + self.ctx.env.get(*i).cloned() + } + + /// The codomain object `self.` (a base object value), for a + /// generator declared over the object-typed codomain field `field`. + fn codomain_object(&self, field: FieldName, label: LabelSegment) -> Option { + Some(self.evaluator().proj(&self.codomain_self_value()?, field, label)) + } + + /// Apply a codomain morphism `f` to an already-elaborated fiber + /// argument. The argument's `Over` object must equal the morphism's + /// domain object (compared as base objects, so modal domains — lists, + /// tensors — need no special handling); the result lies over the + /// morphism's codomain object. + fn apply_codomain_morphism( + &mut self, + path: &[(FieldName, LabelSegment)], + arg_s: FiberTmS, + arg_v: FiberTmV, + arg_ty: FiberTyV, + arg_label_str: &str, + ) -> (FiberTmS, FiberTmV, FiberTyV) { + let Some(codomain) = self.instance_codomain() else { + return self.fiber_syn_error_msg( + "applied codomain morphism is only allowed inside an instance body", + ); + }; + let FiberTyV_::Over(arg_obj) = &*arg_ty else { + return self + .fiber_syn_error(FiberError::ArgNotElement(Some(arg_label_str.to_string()))); + }; + let arg_obj = arg_obj.clone(); + let Some(self_val) = self.codomain_self_value() else { + return self.fiber_syn_error_msg( + "applied codomain morphism is only allowed inside an instance body", + ); + }; + // Resolve the morphism's type by walking its (possibly nested) + // path into the codomain model, e.g. `Add.op`. + let record_ty = BaseTyV::record((*codomain).clone()); + let mor_ty = match self.evaluator().path_ty(&record_ty, &self_val, path) { + Ok(ty) => ty, + Err(e) => { + return self.fiber_syn_error_msg(format!( + "no such codomain morphism {}: {e}", + path_str(path) + )); + } + }; + self.fiber_mor_app(path, &mor_ty, arg_s, arg_v, &arg_obj) + } + + /// Elaborate an instance body — a tuple of `name : type`, `field + /// := [names]`, and `mor(arg) := target` clauses — against the + /// enclosing codomain model. Produces the instance as a fiber + /// [`Record`](FiberTyS_::Record): generators become + /// [`Over`](FiberTyS_::Over) fields, sub-instance imports nested + /// [`Record`](FiberTyS_::Record) fields, and equations + /// [`Id`](FiberTyS_::Id) fields. + /// + /// The codomain model is bound into the *base* context as a `self`-typed + /// record variable (and the binding is dropped on exit) so that + /// applied-codomain-morphism syntax resolves morphisms by name. The + /// instance's own generators and imports live in the separate *fiber* + /// scope. + fn instance_body(&mut self, codomain: &RecordV, n: &FNtn) -> (FiberTyS, FiberTyV) { + let c = self.checkpoint(); + let binder = name_seg(CODOMAIN_BINDER); + self.intro(binder, label_seg(CODOMAIN_BINDER), Some(BaseTyV::record(codomain.clone()))); + let result = self.instance_body_inner(n); + self.reset_to(c); + result + } + + /// Re-establish the scope of an already-elaborated instance so a fresh + /// term can be elaborated against it (see the `norm [inst] ` + /// command): bind the codomain model as `self` — so codomain-morphism + /// syntax like `t(..)` resolves — and introduce each generator and + /// sub-instance import into the fiber scope by its original name. + fn enter_instance(&mut self, inst: &Instance) -> Option<()> { + self.intro( + name_seg(CODOMAIN_BINDER), + label_seg(CODOMAIN_BINDER), + Some(inst.codomain.clone()), + ); + let FiberTyV_::Record(fields) = &*inst.val else { + return self.error("instance value is not a fiber record"); + }; + for (name, (label, field_ty)) in fields.iter() { + match &**field_ty { + // Generators (`Over`) and sub-instance imports (`Record`) + // become fiber-scope bindings; projections into an import + // resolve against the record type. Equations (`Id`) are not + // in scope as terms. + FiberTyV_::Over(_) | FiberTyV_::Record(_) => { + self.intro_fiber(*name, *label, field_ty.clone()); + } + FiberTyV_::Id(_, _, _) => {} + } + } + Some(()) + } + + /// Elaborate the clauses of an instance body (the f-notation `n`) into a + /// fiber [`Record`](FiberTyS_::Record). The codomain is already set on + /// the context by [`Self::instance_body`]. + /// + /// Steps: + /// 1. Set up empty accumulators (see below) for the clauses to fill. + /// 2. Walk each clause, dispatching on its surface shape into one of + /// the forms below. A malformed clause reports an error and sets + /// `failed`, but the walk continues so a single pass surfaces as + /// many errors as possible. + /// 3. If any clause failed, return an empty instance (errors already + /// reported); otherwise assemble the accumulators into the paired + /// instance terms. + /// + /// The clause forms, in match order: + /// - `name : type` — dispatched on the *elaborated type's* shape: a + /// fiber type `Over(p)` declares a generator; a record type is a + /// sub-instance import (must name a top-level instance def); an + /// identity type `a == b` is an anonymous equation. + /// - `field := [k := t, ...]` — mapping-literal: sugar for a batch of + /// per-key equations `field(k) := t` against a codomain *morphism*. + /// - `field := [n1, n2, ...]` — set-literal: declares generators in + /// the fiber over a codomain *object* `field`. + /// - `mor(arg) := target` — a single equation witness. + fn instance_body_inner(&mut self, n: &FNtn) -> (FiberTyS, FiberTyV) { + let mut elab = self.enter(n.loc()); + let empty = || (FiberTyS::record(Row::empty()), FiberTyV::record(Row::empty())); + let Tuple(field_ns) = n.ast0() else { + elab.error::<()>("expected a tuple instance body"); + return empty(); + }; + // The instance is assembled as a fiber record: a generator is an + // `Over` field, a sub-instance import a nested `Record` field, and + // an equation an `Id` field (with a synthetic `_eqN` name). + // `fields_s`/`fields_v` hold the syntactic / value rows; `eq_count` + // names successive equation fields. + let mut fields_s: Row = Row::empty(); + let mut fields_v: Row = Row::empty(); + let mut eq_count = 0usize; + let mut failed = false; + + for field_n in field_ns.iter() { + elab.loc = Some(field_n.loc()); + match field_n.ast0() { + // `name : type` — a sub-instance import (`we : Edge`) or an + // anonymous equation (`name : (a == b)`), dispatched on the + // elaborated fiber type's shape. + App2(L(_, Keyword(":")), L(_, Var(name)), ty_n) => { + let n_seg = name_seg(*name); + let label = label_seg(*name); + let Some((ty_s, ty_v)) = elab.fiber_ty(ty_n) else { + failed = true; + continue; + }; + match &*ty_v { + // A sub-instance import: bind it in the fiber scope + // (so `name.gen` projections resolve) and record it. + FiberTyV_::Record(_) => { + elab.intro_fiber(n_seg, label, ty_v.clone()); + fields_s.insert(n_seg, label, ty_s); + fields_v.insert(n_seg, label, ty_v); + } + // A named equation (e.g. `eq : (.src(e) == .src(f))`). + FiberTyV_::Id(_, _, _) => { + fields_s.insert(n_seg, label, ty_s); + fields_v.insert(n_seg, label, ty_v); + } + FiberTyV_::Over(_) => { + elab.error::<()>(format!( + "instance clause {name} cannot be annotated with a bare \ + element type", + )); + failed = true; + } + } + } + // `field := [k1 := t1, ...]` — mapping-literal: a batch of + // per-key equations against a morphism-typed codomain field. + App2(L(_, Keyword(":=")), L(_, Var(field_name)), L(_, Tuple(entries))) + if !entries.is_empty() + && entries + .iter() + .all(|e| matches!(e.ast0(), App2(L(_, Keyword(":=")), _, _))) => + { + let Some(codomain) = elab.instance_codomain() else { + elab.error::<()>( + "mapping-literal assignment is only allowed inside an instance body", + ); + failed = true; + continue; + }; + let f_seg = name_seg(*field_name); + if !codomain.fields.has(f_seg) { + elab.error::<()>(format!("no such codomain field {field_name}")); + failed = true; + continue; + } + // Each `key := target` entry is the equation + // `field(key) == target`: apply the codomain morphism + // to the key (which also checks the key's object against + // the morphism's domain and yields the codomain object), + // then equate the result to the target. + let mut entry_failed = false; + for entry_n in entries.iter() { + elab.loc = Some(entry_n.loc()); + let App2(L(_, Keyword(":=")), key_n, target_n) = entry_n.ast0() else { + unreachable!("guard ensured all entries are `:=` clauses"); + }; + let (key_s, key_v, key_ty) = elab.fiber_syn(key_n); + let label = format!("{field_name} key"); + let mor_path = vec![(name_seg(*field_name), label_seg(*field_name))]; + let (lhs_s, lhs_v, lhs_ty) = + elab.apply_codomain_morphism(&mor_path, key_s, key_v, key_ty, &label); + let FiberTyV_::Over(cod_obj) = &*lhs_ty else { + entry_failed = true; + break; + }; + let cod_obj = cod_obj.clone(); + let (rhs_s, rhs_v) = elab.fiber_chk(&lhs_ty, target_n); + let (id_s, id_v) = + elab.fiber_id_field(&lhs_ty, &cod_obj, lhs_s, lhs_v, rhs_s, rhs_v); + let (eqn, eql) = next_eq_field(&mut eq_count); + fields_s.insert(eqn, eql, id_s); + fields_v.insert(eqn, eql, id_v); + } + if entry_failed { + failed = true; + continue; + } + } + // `field := [n1, n2, ...]` — set-literal: declare generators + // in the fiber over an object-typed codomain field. + App2(L(_, Keyword(":=")), L(_, Var(field_name)), L(_, Tuple(name_ns))) => { + let Some(codomain) = elab.instance_codomain() else { + elab.error::<()>( + "set-literal field assignment is only allowed inside an \ + instance body", + ); + failed = true; + continue; + }; + let f_seg = name_seg(*field_name); + let f_label = label_seg(*field_name); + let Some(field_ty_s) = codomain.fields.get(f_seg) else { + elab.error::<()>(format!("no such codomain field {field_name}")); + failed = true; + continue; + }; + if !matches!(&**field_ty_s, BaseTyS_::Object(_)) { + elab.error::<()>(format!( + "set-literal assignment requires field {field_name} to be \ + object-typed", + )); + failed = true; + continue; + } + // Generators lie over the codomain object `self.`. + let Some(gen_obj_v) = elab.codomain_object(f_seg, f_label) else { + elab.error::<()>( + "set-literal field assignment is only allowed inside an \ + instance body", + ); + failed = true; + continue; + }; + let gen_obj_s = elab.evaluator().quote_tm(&gen_obj_v); + for name_n in name_ns.iter() { + let Var(gen_name) = name_n.ast0() else { + elab.loc = Some(name_n.loc()); + elab.error::<()>("set-literal entries must be bare names"); + failed = true; + break; + }; + let gen_seg = name_seg(*gen_name); + let gen_label = label_seg(*gen_name); + elab.intro_fiber(gen_seg, gen_label, FiberTyV::over(gen_obj_v.clone())); + fields_s.insert(gen_seg, gen_label, FiberTyS::over(gen_obj_s.clone())); + fields_v.insert(gen_seg, gen_label, FiberTyV::over(gen_obj_v.clone())); + } + } + // `mor(arg) := target` — a single equation witness. + App2(L(_, Keyword(":=")), lhs_n, rhs_n) => { + let (lhs_s, lhs_v, lhs_ty) = elab.fiber_syn(lhs_n); + let FiberTyV_::Over(obj) = &*lhs_ty else { + elab.loc = Some(lhs_n.loc()); + elab.report_fiber(FiberError::MappingLhsNotOver); + failed = true; + continue; + }; + let obj = obj.clone(); + let (rhs_s, rhs_v) = elab.fiber_chk(&lhs_ty, rhs_n); + let (id_s, id_v) = + elab.fiber_id_field(&lhs_ty, &obj, lhs_s, lhs_v, rhs_s, rhs_v); + let (eqn, eql) = next_eq_field(&mut eq_count); + fields_s.insert(eqn, eql, id_s); + fields_v.insert(eqn, eql, id_v); + } + _ => { + elab.error::<()>( + "expected fields in the form `name : type`, \ + `field := [names]`, or `mor(arg) := target`", + ); + failed = true; + } + } + } + + // On any failure, errors are already reported, so bail with an + // empty instance rather than a half-built one. + if failed { + return empty(); + } + (FiberTyS::record(fields_s), FiberTyV::record(fields_v)) + } + + fn binding(&mut self, n: &FNtn) -> Option<(VarName, LabelSegment, BaseTyS, BaseTyV)> { let mut elab = self.enter(n.loc()); match n.ast0() { App2(L(_, Keyword(":")), L(_, Var(name)), ty_n) => { @@ -363,15 +922,15 @@ impl<'a> Elaborator<'a> { } } - fn lookup_ty(&mut self, name: VarName) -> (TyS, TyV) { + fn lookup_ty(&mut self, name: VarName) -> (BaseTyS, BaseTyV) { let qname = QualifiedName::single(name); if let Some(ob_type) = self.theory().basic_ob_type(qname) { - (TyS::object(ob_type.clone()), TyV::object(ob_type)) + (BaseTyS::object(ob_type.clone()), BaseTyV::object(ob_type)) } else if let Some(d) = self.toplevel.declarations.get(&name) { match d { TopDecl::Type(t) => { if t.theory == self.theory { - (TyS::topvar(name), t.val.clone()) + (BaseTyS::topvar(name), t.val.clone()) } else { self.ty_error(format!( "{name} refers to a type in theory {}, expected a type in theory {}", @@ -379,15 +938,20 @@ impl<'a> Elaborator<'a> { )) } } - TopDecl::Def(_) | TopDecl::DefConst(_) => { - self.ty_error(format!("{name} refers to a term not a type")) - } + // An instance is a fiber type, not a base type. It can only + // appear as the annotation of a sub-instance import inside an + // instance body (handled by `fiber_ty`), not in base-type + // position. + TopDecl::Instance(_) => self.ty_error(format!( + "{name} refers to an instance, which is not a base type; \ + an instance can only be imported inside another instance body" + )), + TopDecl::Def(_) => self.ty_error(format!("{name} refers to a term not a type")), } } else { self.ty_error(format!("no such type {name} defined")) } } - fn morphism_ty(&mut self, n: &FNtn) -> Option<(MorType, ObType, ObType)> { let elab = self.enter(n.loc()); let theory = elab.theory(); @@ -432,7 +996,10 @@ impl<'a> Elaborator<'a> { } #[allow(clippy::type_complexity)] - fn specialization(&mut self, n: &FNtn) -> Option<(Vec<(NameSegment, LabelSegment)>, TyS, TyV)> { + fn specialization( + &mut self, + n: &FNtn, + ) -> Option<(Vec<(NameSegment, LabelSegment)>, BaseTyS, BaseTyV)> { let mut elab = self.enter(n.loc()); match n.ast0() { App2(L(_, Keyword(":")), p_n, ty_n) => { @@ -443,21 +1010,25 @@ impl<'a> Elaborator<'a> { App2(L(_, Keyword(":=")), p_n, tm_n) => { let p = elab.path(p_n)?; let (tm_s, tm_v, ty_v) = elab.syn(tm_n); - Some((p, TyS::sing(elab.evaluator().quote_ty(&ty_v), tm_s), TyV::sing(ty_v, tm_v))) + Some(( + p, + BaseTyS::sing(elab.evaluator().quote_ty(&ty_v), tm_s), + BaseTyV::sing(ty_v, tm_v), + )) } _ => elab.error("unexpected notation for specialization"), } } /// Elaborates a type from notation, returning both syntax and value. - pub fn ty(&mut self, n: &FNtn) -> (TyS, TyV) { + pub fn ty(&mut self, n: &FNtn) -> (BaseTyS, BaseTyV) { let mut elab = self.enter(n.loc()); match n.ast0() { Var(name) => elab.lookup_ty(name_seg(*name)), - Keyword("Unit") => (TyS::unit(), TyV::unit()), + Keyword("Unit") => elab.empty_record_ty(), App1(L(_, Prim("sing")), tm_n) => { let (tm_s, tm_v, ty_v) = elab.syn(tm_n); - (TyS::sing(elab.evaluator().quote_ty(&ty_v), tm_s), TyV::sing(ty_v, tm_v)) + (BaseTyS::sing(elab.evaluator().quote_ty(&ty_v), tm_s), BaseTyV::sing(ty_v, tm_v)) } App1(mt_n, L(_, Tuple(domcod_n))) => { let [dom_n, cod_n] = domcod_n.as_slice() else { @@ -466,12 +1037,15 @@ impl<'a> Elaborator<'a> { let Some((mt, dom_ty, cod_ty)) = elab.morphism_ty(mt_n) else { return elab.ty_hole(); }; - let (dom_s, dom_v) = elab.chk(&TyV::object(dom_ty.clone()), dom_n); - let (cod_s, cod_v) = elab.chk(&TyV::object(cod_ty.clone()), cod_n); - (TyS::morphism(mt.clone(), dom_s, cod_s), TyV::morphism(mt.clone(), dom_v, cod_v)) + let (dom_s, dom_v) = elab.chk(&BaseTyV::object(dom_ty.clone()), dom_n); + let (cod_s, cod_v) = elab.chk(&BaseTyV::object(cod_ty.clone()), cod_n); + ( + BaseTyS::morphism(mt.clone(), dom_s, cod_s), + BaseTyV::morphism(mt.clone(), dom_v, cod_v), + ) } Tuple(field_ns) => { - let mut field_ty_vs = Vec::<(FieldName, (LabelSegment, TyV))>::new(); + let mut field_ty_vs = Vec::<(FieldName, (LabelSegment, BaseTyV))>::new(); let mut failed = false; let self_var = elab.intro(name_seg("self"), label_seg("self"), None).unwrap_neu(); let c = elab.checkpoint(); @@ -479,7 +1053,8 @@ impl<'a> Elaborator<'a> { elab.loc = Some(field_n.loc()); let Some((name, label, ty_n)) = (match field_n.ast0() { App2(L(_, Keyword(":")), L(_, Var(name)), ty_n) => { - Some((name_seg(*name), label_seg(*name), ty_n)) + let name_seg = name_seg(*name); + Some((name_seg, label_seg(*name), ty_n)) } _ => elab.error("expected fields in the form : "), }) else { @@ -489,8 +1064,10 @@ impl<'a> Elaborator<'a> { let (_, ty_v) = elab.ty(ty_n); field_ty_vs.push((name, (label, ty_v.clone()))); elab.ctx.push_scope(name, label, Some(ty_v.clone())); - elab.ctx.env = - elab.ctx.env.snoc(TmV::neu(TmN::proj(self_var.clone(), name, label), ty_v)); + elab.ctx.env = elab + .ctx + .env + .snoc(BaseTmV::neu(TmN::proj(self_var.clone(), name, label), ty_v)); } if failed { return elab.ty_hole(); @@ -501,7 +1078,7 @@ impl<'a> Elaborator<'a> { .map(|(name, (label, ty_v))| (*name, (*label, elab.evaluator().quote_ty(ty_v)))) .collect(); let r_v = RecordV::new(elab.ctx.env.clone(), field_tys.clone(), Dtry::empty()); - (TyS::record(field_tys), TyV::record(r_v)) + (BaseTyS::record(field_tys), BaseTyV::record(r_v)) } App2(L(_, Keyword("&")), ty_n, L(_, Tuple(specialization_ns))) => { let (ty_s, mut ty_v) = elab.ty(ty_n); @@ -528,15 +1105,18 @@ impl<'a> Elaborator<'a> { } } } - (TyS::specialize(ty_s, specializations), ty_v) + (BaseTyS::specialize(ty_s, specializations), ty_v) } App2(L(_, Keyword("==")), tm1_n, tm2_n) => { let (tm1_s, tm1_v, tm1_ty) = elab.syn(tm1_n); let (tm2_s, tm2_v, tm2_ty) = elab.syn(tm2_n); - let TyV_::Morphism(_, _, _) = &*tm1_ty else { + if !matches!(&*tm1_ty, BaseTyV_::Morphism(_, _, _)) { elab.loc = Some(tm1_n.loc()); - return elab.ty_error("Equality types are only supported for morphisms"); - }; + return elab.ty_error( + "Equality types are only supported for morphisms; equations \ + between instance elements live inside an instance body", + ); + } if let Err(e) = elab.evaluator().convertible_ty(&tm1_ty, &tm2_ty) { let eval = elab.evaluator(); return elab.ty_error(format!( @@ -546,28 +1126,43 @@ impl<'a> Elaborator<'a> { e.pretty() )); } - let eq_ty_s = TyS::id(elab.evaluator().quote_ty(&tm1_ty), tm1_s, tm2_s); - let eq_ty_v = TyV::id(tm1_ty, tm1_v, tm2_v); + let eq_ty_s = BaseTyS::id(elab.evaluator().quote_ty(&tm1_ty), tm1_s, tm2_s); + let eq_ty_v = BaseTyV::id(tm1_ty, tm1_v, tm2_v); (eq_ty_s, eq_ty_v) } _ => elab.ty_error("unexpected notation for type"), } } - fn lookup_tm(&mut self, name: Ustr) -> (TmS, TmV, TyV) { + fn lookup_tm(&mut self, name: Ustr) -> (BaseTmS, BaseTmV, BaseTyV) { let label = label_seg(name); let name = name_seg(name); if let Some((i, _, ty)) = self.ctx.lookup(name) { ( - TmS::var(i, name, label), + BaseTmS::var(i, name, label), self.ctx.env.get(*i).unwrap().clone(), ty.clone().unwrap(), ) } else if let Some(d) = self.toplevel.lookup(name) { match d { TopDecl::Type(_) => self.syn_error(format!("{name} refers type, not term")), - TopDecl::DefConst(d) => (TmS::topvar(name), d.val.clone(), d.ty.clone()), + // A nullary `Def` (a closed term, e.g. `tt : Unit`) used as a + // bare name; evaluate its body and return type in the empty + // context. + TopDecl::Def(d) if d.args.is_empty() => { + let def = d.clone(); + let eval = self.evaluator(); + ( + BaseTmS::topapp(name, vec![]), + eval.eval_tm(&def.body), + eval.eval_ty(&def.ret_ty), + ) + } TopDecl::Def(_) => self.syn_error(format!("{name} must be applied to arguments")), + TopDecl::Instance(_) => self.syn_error(format!( + "{name} refers to an instance; use it in type position to import it, \ + not as a term" + )), } } else { self.syn_error(format!("no such variable {name}")) @@ -575,13 +1170,28 @@ impl<'a> Elaborator<'a> { } /// Elaborates a term from notation, returning syntax, value, and synthesized type. - fn syn(&mut self, n: &FNtn) -> (TmS, TmV, TyV) { + fn syn(&mut self, n: &FNtn) -> (BaseTmS, BaseTmV, BaseTyV) { let mut elab = self.enter(n.loc()); match n.ast0() { Var(name) => elab.lookup_tm(ustr(name)), App1(tm_n, L(_, Field(f))) => { + // A top-level instance has no term-position use, so projecting + // a field out of one would otherwise produce a confusing + // "not a term"/"not a record" cascade; catch it here with the + // targeted elimination message. + if let Var(inst) = tm_n.ast0() + && matches!( + elab.toplevel.declarations.get(&name_seg(*inst)), + Some(TopDecl::Instance(_)) + ) + { + return elab.syn_error( + "cannot project a field out of an instance; an instance is \ + eliminated by mapping out of it, not by projection", + ); + } let (tm_s, tm_v, ty_v) = elab.syn(tm_n); - let TyV_::Record(r) = &*ty_v else { + let BaseTyV_::Record(r) = &*ty_v else { return elab.syn_error("can only project from record type"); }; let label = label_seg(*f); @@ -590,34 +1200,37 @@ impl<'a> Elaborator<'a> { return elab.syn_error(format!("no such field {f}")); } ( - TmS::proj(tm_s, f, label), + BaseTmS::proj(tm_s, f, label), elab.evaluator().proj(&tm_v, f, label), elab.evaluator().field_ty(&ty_v, &tm_v, f), ) } + // Codomain-morphism application (`src(we.e)`, `f(x)`) is fiber + // syntax, elaborated by `fiber_syn` inside an instance body — it + // is not a base term, so base `syn` does not handle it. App1(L(_, Prim("id")), ob_n) => { let (ob_s, ob_v, ob_t) = elab.syn(ob_n); - let TyV_::Object(ob_type) = &*ob_t else { + let BaseTyV_::Object(ob_type) = &*ob_t else { return elab.syn_error("can only apply @id to objects"); }; let Some(mor_type) = elab.theory().hom_type(ob_type.clone()) else { return elab.syn_error("object type does not have a hom type"); }; ( - TmS::id(ob_s), - TmV::id(ob_v.clone()), - TyV::morphism(mor_type, ob_v.clone(), ob_v), + BaseTmS::id(ob_s), + BaseTmV::id(ob_v.clone()), + BaseTyV::morphism(mor_type, ob_v.clone(), ob_v), ) } App1(L(_, Prim("tab")), mor_n) => { let (mor_s, mor_v, mor_t) = elab.syn(mor_n); - let TyV_::Morphism(mor_type, _, _) = &*mor_t else { + let BaseTyV_::Morphism(mor_type, _, _) = &*mor_t else { return elab.syn_error("can only apply @tab to morphisms"); }; let Some(ob_type) = elab.theory().tabulator(mor_type.clone()) else { return elab.syn_error("theory does not have tabulators"); }; - (TmS::tab(mor_s), TmV::tab(mor_v.clone()), TyV::object(ob_type)) + (BaseTmS::tab(mor_s), BaseTmV::tab(mor_v.clone()), BaseTyV::object(ob_type)) } App1(L(_, Prim(name)), ob_n) => { let name = name_seg(*name); @@ -626,18 +1239,18 @@ impl<'a> Elaborator<'a> { return elab.syn_error(format!("operation @{name} not in theory {th_name}")); }; let dom = elab.theory().ob_op_dom(&ob_op); - let (arg_s, arg_v) = elab.chk(&TyV::object(dom), ob_n); + let (arg_s, arg_v) = elab.chk(&BaseTyV::object(dom), ob_n); let cod = elab.theory().ob_op_cod(&ob_op); - (TmS::ob_app(name, arg_s), TmV::app(name, arg_v), TyV::object(cod)) + (BaseTmS::ob_app(name, arg_s), BaseTmV::app(name, arg_v), BaseTyV::object(cod)) } App2(L(_, Keyword("*")), f_n, g_n) => { let (f_s, f_v, f_ty) = elab.syn(f_n); let (g_s, g_v, g_ty) = elab.syn(g_n); - let TyV_::Morphism(f_mt, f_dom, f_cod) = &*f_ty else { + let BaseTyV_::Morphism(f_mt, f_dom, f_cod) = &*f_ty else { elab.loc = Some(f_n.loc()); return elab.syn_error("expected a morphism"); }; - let TyV_::Morphism(g_mt, g_dom, g_cod) = &*g_ty else { + let BaseTyV_::Morphism(g_mt, g_dom, g_cod) = &*g_ty else { elab.loc = Some(g_n.loc()); return elab.syn_error("expected a morphism"); }; @@ -656,9 +1269,9 @@ impl<'a> Elaborator<'a> { )); } ( - TmS::compose(f_s, g_s), - TmV::compose(f_v, g_v), - TyV::morphism( + BaseTmS::compose(f_s, g_s), + BaseTmV::compose(f_v, g_v), + BaseTyV::morphism( elab.theory().compose_types2(f_mt.clone(), g_mt.clone()).unwrap(), f_dom.clone(), g_cod.clone(), @@ -686,9 +1299,13 @@ impl<'a> Elaborator<'a> { env = env.snoc(arg_v); } let eval = elab.evaluator().with_env(env.clone()); - (TmS::topapp(tv, arg_stxs), eval.eval_tm(&d.body), eval.eval_ty(&d.ret_ty)) + (BaseTmS::topapp(tv, arg_stxs), eval.eval_tm(&d.body), eval.eval_ty(&d.ret_ty)) + } + Tag("tt") => { + // `tt` is the unique element of `Unit`, i.e. the empty record `[]`. + let (_, ty_v) = elab.empty_record_ty(); + (BaseTmS::cons(Row::empty()), BaseTmV::cons(Row::empty()), ty_v) } - Tag("tt") => (TmS::tt(), TmV::tt(), TyV::unit()), Tuple(_) => elab.syn_error("must check against a type in order to construct a record"), Prim("hole") => elab.syn_error("explicit hole"), _ => elab.syn_error("unexpected notation for term"), @@ -696,10 +1313,15 @@ impl<'a> Elaborator<'a> { } /// Elaborates a term from notation, checking against an expected type, and returning syntax and value. - fn chk(&mut self, ty: &TyV, n: &FNtn) -> (TmS, TmV) { + fn chk(&mut self, ty: &BaseTyV, n: &FNtn) -> (BaseTmS, BaseTmV) { let mut elab = self.enter(n.loc()); match (&**ty, n.ast0()) { - (TyV_::Record(r), Tuple(field_ns)) => { + (BaseTyV_::Record(r), Tuple(field_ns)) => { + // Ordinary record construction (a tight transformation / + // generalized element). Instance bodies are *not* dispatched + // here — they are introduced by the `instance` keyword, which + // calls `instance_body` directly — so this arm has no clause + // shape to disambiguate. if r.fields.len() != field_ns.len() { return elab.chk_error(format!( "wrong number of fields provided, expected {}, got {}", @@ -723,20 +1345,20 @@ impl<'a> Elaborator<'a> { return elab.chk_error("unexpected notation for field"); } }; - let v = TmV::cons(field_vals.clone().into()); + let v = BaseTmV::cons(field_vals.clone().into()); let field_ty_v = elab.evaluator().with_env(r.env.snoc(v.clone())).eval_ty(field_ty_s); let (tm_s, tm_v) = elab.chk(&field_ty_v, tm_n); field_stxs.insert(*name, (*label, tm_s)); field_vals.insert(*name, (*label, tm_v)); } - (TmS::cons(field_stxs.into()), TmV::cons(field_vals.into())) + (BaseTmS::cons(field_stxs.into()), BaseTmV::cons(field_vals.into())) } - (TyV_::Object(ob_type), Tuple(ob_ns)) => { + (BaseTyV_::Object(ob_type), Tuple(ob_ns)) => { let Some(ob_type) = ob_type.clone().list_arg() else { return elab.chk_error("expected to object type to be a list"); }; - let elem_ty_v = TyV::object(ob_type); + let elem_ty_v = BaseTyV::object(ob_type); let mut elem_stxs = Vec::new(); let mut elem_vals = Vec::new(); for ob_n in ob_ns { @@ -745,7 +1367,7 @@ impl<'a> Elaborator<'a> { elem_stxs.push(tm_s); elem_vals.push(tm_v); } - (TmS::list(elem_stxs), TmV::list(elem_vals)) + (BaseTmS::list(elem_stxs), BaseTmV::list(elem_vals)) } (_, Tuple(_)) => elab.chk_error("tuple expected to be record or object/morphism type"), (_, Prim("hole")) => elab.chk_error("explicit hole"), @@ -774,6 +1396,110 @@ impl<'a> Elaborator<'a> { } } +/// Extract the path to a codomain morphism from the head of an +/// application: a bare variable `f` gives `[f]`, and a projection chain +/// `Add.op` gives `[Add, op]`. Returns `None` for any other shape. +impl<'a> FiberElab for Elaborator<'a> { + fn ctx(&self) -> &Context { + &self.ctx + } + + fn ctx_mut(&mut self) -> &mut Context { + &mut self.ctx + } + + fn elab_theory(&self) -> &Theory { + &self.theory + } + + fn evaluator(&self) -> Evaluator<'_> { + Elaborator::evaluator(self) + } + + fn fresh_meta(&mut self) -> MetaVar { + Elaborator::fresh_meta(self) + } + + /// Formats fiber errors into the exact messages this elaborator has + /// always reported; they appear verbatim in committed snapshots, so the + /// strings must not drift. + fn report_fiber(&mut self, err: FiberError) { + let msg = match err { + FiberError::UnknownElement(name) => format!("no such fiber element {name}"), + FiberError::ProjNonRecord => { + "can only project a generator out of a sub-instance".to_string() + } + FiberError::UnknownProj(field) => { + format!("no such generator {field} in sub-instance") + } + FiberError::UnknownObOp(op, th) => format!("operation @{op} not in theory {th}"), + FiberError::ObOpOnNonElement(op) => format!("@{op} applied to a non-fiber-element"), + FiberError::ListElementNotOver => { + "fiber list elements must be elements over an object".to_string() + } + // Only notebook cells can contain unfilled slots. + FiberError::MissingTerm => "missing term".to_string(), + FiberError::ArgNotElement(label) => match label { + Some(label) => format!("argument {label} is not an element over an object"), + None => "argument is not an element over an object".to_string(), + }, + FiberError::NotAMorphism(path) => { + format!("codomain field {path} is not a morphism") + } + FiberError::ArgMismatch { path, got, expected, detail } => { + format!("argument to {path} lies over {got}, but it expects {expected}:\n{detail}") + } + FiberError::WrongFiberType(detail) => { + format!("fiber element has the wrong type:\n{detail}") + } + FiberError::MappingLhsNotOver => { + "mapping-entry clause `mor(arg) := target` requires the LHS \ + to be an element over an object (a fiber element); morphism \ + equations constrain the model, not an instance" + .to_string() + } + FiberError::EquationNotOver => { + "instance equations must be between elements over an object \ + (fiber elements); morphism equations constrain the model, not \ + an instance" + .to_string() + } + FiberError::InconvertibleEquationSides(detail) => { + format!("equation sides have inconvertible fiber types:\n{detail}") + } + FiberError::ImportCodomainMismatch(name) => { + format!( + "cannot import {name}: it is an instance of a different model than \ + the enclosing instance" + ) + } + }; + self.reporter.error_option_loc(self.loc, ELAB_ERROR, msg); + } +} + +fn morphism_path(n: &FNtn) -> Option> { + match n.ast0() { + Var(f) => Some(vec![(name_seg(*f), label_seg(*f))]), + App1(recv, L(_, Field(g))) => { + let mut p = morphism_path(recv)?; + p.push((name_seg(*g), label_seg(*g))); + Some(p) + } + _ => None, + } +} + +/// Render a morphism/object path as dotted labels (e.g. `Add.op`), for +/// error messages. +/// The synthetic field name/label `_eqN` for the next auto-named equation +/// field of an instance record, advancing the counter. +fn next_eq_field(eq_count: &mut usize) -> (FieldName, LabelSegment) { + let key = format!("_eq{}", *eq_count); + *eq_count += 1; + (name_seg(key.as_str()), label_seg(key.as_str())) +} + // NOTE: Most tests for the text elaborator are in the `examples` dir. #[cfg(test)] mod tests { diff --git a/packages/catlog/src/tt/toplevel.rs b/packages/catlog/src/tt/toplevel.rs index e9f447bcf..157e5980f 100644 --- a/packages/catlog/src/tt/toplevel.rs +++ b/packages/catlog/src/tt/toplevel.rs @@ -1,6 +1,9 @@ //! Data structures for managing toplevel declarations in the type theory. //! -//! Specifically, notebooks will produce [TopDecl::Type] declarations. +//! The three kinds mirror the comprehension category of `D`-models: a [Type] +//! is a model (a context, i.e. an object of the base), a [Def] is a tight +//! transformation (a substitution, i.e. a morphism of the base), and an +//! [Instance] is an object of a fiber (a type in context). use derive_more::Constructor; @@ -12,10 +15,10 @@ use crate::zero::QualifiedName; pub enum TopDecl { /// See [Type]. Type(Type), - /// See [DefConst]. - DefConst(DefConst), /// See [Def]. Def(Def), + /// See [Instance]. + Instance(Instance), } /// A toplevel declaration of a type. @@ -27,26 +30,34 @@ pub struct Type { /// The theory for the type. pub theory: Theory, /// The syntax of the type (unnormalized). - pub stx: TyS, + pub stx: BaseTyS, /// The value of the type (normalized). - pub val: TyV, + pub val: BaseTyV, } -/// A toplevel declaration of a term in the empty context. +/// A toplevel declaration of an instance of a model. /// -/// Also stores the evaluation of that term, and the evaluation of the -/// corresponding type of that term. Because this is an evaluation in the empty -/// context, this is OK to use in any other context as well. +/// An instance is an object of the fiber over its codomain model `X` in the +/// comprehension category of `D`-models: a generator/equation/sub-instance +/// body packaged as the presentation of an `X`-instance. It is declared with +/// `instance NAME : X := [...]`. +/// +/// The instance is represented directly as a fiber type — a fiber +/// [`Record`](super::stx::FiberTyS_::Record) whose fields are its +/// generators ([`Over`](super::stx::FiberTyS_::Over)), sub-instance +/// imports (nested records), and equations +/// ([`Id`](super::stx::FiberTyS_::Id)). A sub-instance import `we : Edge` +/// uses this fiber type directly. #[derive(Constructor, Clone)] -pub struct DefConst { - /// The theory that the constant is defined in. +pub struct Instance { + /// The theory that the instance is defined in. pub theory: Theory, - /// The syntax of the constant (unnormalized). - pub stx: TmS, - /// The value of the constant (normalized). - pub val: TmV, - /// The type of the constant. - pub ty: TyV, + /// The syntax of the instance, as a fiber record type. + pub stx: FiberTyS, + /// The value of the instance, as a fiber record type. + pub val: FiberTyV, + /// The codomain model `X` that this is an instance of. + pub codomain: BaseTyV, } /// A toplevel declaration of a term judgment. @@ -55,13 +66,13 @@ pub struct Def { /// The theory that the definition is defined in. pub theory: Theory, /// The arguments for the definition. - pub args: Row, + pub args: Row, /// The return type of the definition (to be evaluated in an environment /// with values for the arguments). - pub ret_ty: TyS, + pub ret_ty: BaseTyS, /// The body of the definition (to be evaluated in an environment with /// values for the arguments). - pub body: TmS, + pub body: BaseTmS, } impl TopDecl { @@ -76,17 +87,6 @@ impl TopDecl { } } - /// Unwraps the term for a toplevel declaration of a term, or panics. - /// - /// This should only be used after type checking, when we know that a toplevel - /// variable name does in fact point to a toplevel declaration for a term. - pub fn unwrap_const(self) -> DefConst { - match self { - TopDecl::DefConst(d) => d, - _ => panic!("top-level should be a constant declaration"), - } - } - /// Unwraps the definition for a toplevel term judgment, or panics. pub fn unwrap_def(self) -> Def { match self { diff --git a/packages/catlog/src/tt/util/mod.rs b/packages/catlog/src/tt/util/mod.rs deleted file mode 100644 index 4ada5c21e..000000000 --- a/packages/catlog/src/tt/util/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Various utilities that are not strictly tied to the specific type theory. -//! -//! Perhaps some of these could move to [crate::zero]. - -pub mod dtry; -pub mod idx; -pub mod pretty; -pub mod row; - -pub use dtry::*; -pub use idx::*; -pub use pretty::*; -pub use row::*; diff --git a/packages/catlog/src/tt/val.rs b/packages/catlog/src/tt/val.rs index 3caa62e11..b5c683afc 100644 --- a/packages/catlog/src/tt/val.rs +++ b/packages/catlog/src/tt/val.rs @@ -8,8 +8,13 @@ use derive_more::Deref; use super::{prelude::*, stx::*, theory::*}; use crate::zero::{LabelSegment, QualifiedName}; -/// A way of resolving [BwdIdx] found in [TmS_::Var] to values. -pub type Env = Bwd; +/// A way of resolving [BwdIdx] found in [BaseTmS_::Var] to values. +pub type Env = Bwd; + +/// The fiber environment: resolves [BwdIdx] found in +/// [`super::stx::FiberTmS_::Var`] to fiber-term values. Separate from +/// [Env], the base environment. +pub type FiberEnv = Bwd; /// The content of a record type value. #[derive(Clone)] @@ -17,17 +22,17 @@ pub struct RecordV { /// The closed-over environment. pub env: Env, /// The types for the fields. - pub fields: Rc>, + pub fields: Rc>, /// Specializations of the fields. /// /// When we get to actually computing the type of fields, we will look here /// to see if they have been specialized. - pub specializations: Dtry, + pub specializations: Dtry, } impl RecordV { /// Construct a record type value. - pub fn new(env: Env, fields: Row, specializations: Dtry) -> Self { + pub fn new(env: Env, fields: Row, specializations: Dtry) -> Self { Self { env, fields: Rc::new(fields), @@ -38,7 +43,7 @@ impl RecordV { /// Add a specialization a path `path` to type `ty`. /// /// Precondition: assumes that this produces a subtype. - pub fn add_specialization(&self, path: &[(FieldName, LabelSegment)], ty: TyV) -> Self { + pub fn add_specialization(&self, path: &[(FieldName, LabelSegment)], ty: BaseTyV) -> Self { Self { specializations: merge_specializations( &self.specializations, @@ -51,7 +56,7 @@ impl RecordV { /// Merge in the specializations in `specializations`. /// /// Precondition: assumes that this produces a subtype. - pub fn specialize(&self, specializations: &Dtry) -> Self { + pub fn specialize(&self, specializations: &Dtry) -> Self { Self { specializations: merge_specializations(&self.specializations, specializations), ..self.clone() @@ -60,7 +65,7 @@ impl RecordV { } /// Merge new specializations with old specializations. -pub fn merge_specializations(old: &Dtry, new: &Dtry) -> Dtry { +pub fn merge_specializations(old: &Dtry, new: &Dtry) -> Dtry { let mut result: IndexMap<_, _> = old.entries().map(|(name, e)| (*name, e.clone())).collect(); for (field, entry) in new.entries() { let new_entry = match (old.entry(field), &entry.1) { @@ -76,59 +81,57 @@ pub fn merge_specializations(old: &Dtry, new: &Dtry) -> Dtry { result.into() } -/// Inner enum for [TyV]. -pub enum TyV_ { - /// Type constructor for object types, also see [TyS_::Object]. +/// Inner enum for [BaseTyV]. +pub enum BaseTyV_ { + /// Type constructor for object types, also see [BaseTyS_::Object]. Object(ObType), - /// Type constructor for morphism types, also see [TyS_::Morphism]. - Morphism(MorType, TmV, TmV), + /// Type constructor for morphism types, also see [BaseTyS_::Morphism]. + Morphism(MorType, BaseTmV, BaseTmV), /// Type constructor for specialized record types. /// - /// This is the target of both [TyS_::Specialize] and [TyS_::Record]. - /// Specifically, [TyS_::Record] evaluates to `TyV_::Record(r)` with - /// `r.specializations = Dtry::empty()`, and then `TyS_::Specialize(ty, d)` will + /// This is the target of both [BaseTyS_::Specialize] and [BaseTyS_::Record]. + /// Specifically, [BaseTyS_::Record] evaluates to `BaseTyV_::Record(r)` with + /// `r.specializations = Dtry::empty()`, and then `BaseTyS_::Specialize(ty, d)` will /// add the specializations in `d` to the evaluation of `ty` (which must - /// evaluate to a value of form `TyV_::Record(_)`). + /// evaluate to a value of form `BaseTyV_::Record(_)`). Record(RecordV), - /// Type constructor for singleton types, also see [TyS_::Sing]. - Sing(TyV, TmV), - /// Type constructor for identity types, also see [TyS_::Id]. - Id(TyV, TmV, TmV), - /// Type constructor for unit types, also see [TyS_::Unit]. - Unit, - /// A metavariable, also see [TyS_::Meta]. + /// Type constructor for singleton types, also see [BaseTyS_::Sing]. + Sing(BaseTyV, BaseTmV), + /// Type constructor for identity types, also see [BaseTyS_::Id]. + Id(BaseTyV, BaseTmV, BaseTmV), + /// A metavariable, also see [BaseTyS_::Meta]. Meta(MetaVar), } -/// Value for total types, dereferences to [TyV_]. +/// Value for total types, dereferences to [BaseTyV_]. #[derive(Clone, Deref)] #[deref(forward)] -pub struct TyV(Rc); +pub struct BaseTyV(Rc); -impl TyV { - /// Smart constructor for [TyV], [TyV_::Object] case. +impl BaseTyV { + /// Smart constructor for [BaseTyV], [BaseTyV_::Object] case. pub fn object(object_type: ObType) -> Self { - Self(Rc::new(TyV_::Object(object_type))) + Self(Rc::new(BaseTyV_::Object(object_type))) } - /// Smart constructor for [TyV], [TyV_::Morphism] case. - pub fn morphism(morphism_type: MorType, dom: TmV, cod: TmV) -> Self { - Self(Rc::new(TyV_::Morphism(morphism_type, dom, cod))) + /// Smart constructor for [BaseTyV], [BaseTyV_::Morphism] case. + pub fn morphism(morphism_type: MorType, dom: BaseTmV, cod: BaseTmV) -> Self { + Self(Rc::new(BaseTyV_::Morphism(morphism_type, dom, cod))) } - /// Smart constructor for [TyV], [TyV_::Record] case. + /// Smart constructor for [BaseTyV], [BaseTyV_::Record] case. pub fn record(record_v: RecordV) -> Self { - Self(Rc::new(TyV_::Record(record_v))) + Self(Rc::new(BaseTyV_::Record(record_v))) } - /// Smart constructor for [TyV], [TyV_::Sing] case. - pub fn sing(ty_v: TyV, tm_v: TmV) -> Self { - Self(Rc::new(TyV_::Sing(ty_v, tm_v))) + /// Smart constructor for [BaseTyV], [BaseTyV_::Sing] case. + pub fn sing(ty_v: BaseTyV, tm_v: BaseTmV) -> Self { + Self(Rc::new(BaseTyV_::Sing(ty_v, tm_v))) } - /// Smart constructor for [TyV], [TyV_::Id] case. - pub fn id(ty_v: TyV, tm_v1: TmV, tm_v2: TmV) -> Self { - Self(Rc::new(TyV_::Id(ty_v, tm_v1, tm_v2))) + /// Smart constructor for [BaseTyV], [BaseTyV_::Id] case. + pub fn id(ty_v: BaseTyV, tm_v1: BaseTmV, tm_v2: BaseTmV) -> Self { + Self(Rc::new(BaseTyV_::Id(ty_v, tm_v1, tm_v2))) } /// Compute the specialization of `self` by `specializations`. @@ -151,9 +154,9 @@ impl TyV { /// /// r3 and r3' should be represented in the same way, and r3, r3' and r3'' /// should all be equivalent. - pub fn specialize(&self, specializations: &Dtry) -> Self { + pub fn specialize(&self, specializations: &Dtry) -> Self { match &**self { - TyV_::Record(r) => TyV::record(r.specialize(specializations)), + BaseTyV_::Record(r) => BaseTyV::record(r.specialize(specializations)), _ => panic!("can only specialize a record type"), } } @@ -161,21 +164,23 @@ impl TyV { /// Specializes the field at `path` to `ty`. /// /// Precondition: assumes that this produces a subtype. - pub fn add_specialization(&self, path: &[(FieldName, LabelSegment)], ty: TyV) -> Self { + pub fn add_specialization(&self, path: &[(FieldName, LabelSegment)], ty: BaseTyV) -> Self { match &**self { - TyV_::Record(r) => TyV::record(r.add_specialization(path, ty)), + BaseTyV_::Record(r) => BaseTyV::record(r.add_specialization(path, ty)), _ => panic!("can only specialize a record type"), } } - /// Smart constructor for [TyV], [TyV_::Unit] case. - pub fn unit() -> Self { - Self(Rc::new(TyV_::Unit)) + /// The empty record type — the unit type / empty model. + /// Also used as a throwaway type for + /// untyped placeholder binders (whose type is discarded). + pub fn empty_record() -> Self { + Self(Rc::new(BaseTyV_::Record(RecordV::new(Env::nil(), Row::empty(), Dtry::empty())))) } - /// Smart constructor for [TyV], [TyV_::Meta] case. + /// Smart constructor for [BaseTyV], [BaseTyV_::Meta] case. pub fn meta(mv: MetaVar) -> Self { - Self(Rc::new(TyV_::Meta(mv))) + Self(Rc::new(BaseTyV_::Meta(mv))) } } @@ -188,7 +193,7 @@ pub enum TmN_ { Proj(TmN, FieldName, LabelSegment), } -/// Neutrals for [terms](TmV), dereferences to [TmN_]. +/// Neutrals for [terms](BaseTmV), dereferences to [TmN_]. #[derive(Clone, Deref, PartialEq, Eq)] #[deref(forward)] pub struct TmN(Rc); @@ -217,86 +222,187 @@ impl TmN { } } -/// Inner enum for [TmV]. -pub enum TmV_ { +/// Inner enum for [BaseTmV]. +pub enum BaseTmV_ { /// Neutrals. /// /// We store the type because we need it for eta-expansion. - Neu(TmN, TyV), + Neu(TmN, BaseTyV), /// Application of an object operation in the theory. - App(VarName, TmV), + App(VarName, BaseTmV), /// Lists of objects. - List(Vec), + List(Vec), /// Records. - Cons(Row), - /// The unique element of the unit type. - Tt, + Cons(Row), /// The identity morphism of an object. - Id(TmV), + Id(BaseTmV), /// The tabulation of a morphism. - Tab(TmV), + Tab(BaseTmV), /// Composition of morphisms. - Compose(TmV, TmV), + Compose(BaseTmV, BaseTmV), /// A metavariable. Meta(MetaVar), } -/// Values for terms, dereferences to [TmV_]. +/// Values for terms, dereferences to [BaseTmV_]. #[derive(Clone, Deref)] #[deref(forward)] -pub struct TmV(Rc); +pub struct BaseTmV(Rc); -impl TmV { - /// Smart constructor for [TmV], [TmV_::Neu] case. - pub fn neu(n: TmN, ty: TyV) -> Self { - TmV(Rc::new(TmV_::Neu(n, ty))) +impl BaseTmV { + /// Smart constructor for [BaseTmV], [BaseTmV_::Neu] case. + pub fn neu(n: TmN, ty: BaseTyV) -> Self { + BaseTmV(Rc::new(BaseTmV_::Neu(n, ty))) } - /// Smart constructor for [TmV], [TmV_::App] case. - pub fn app(name: VarName, x: TmV) -> Self { - TmV(Rc::new(TmV_::App(name, x))) + /// Smart constructor for [BaseTmV], [BaseTmV_::App] case. + pub fn app(name: VarName, x: BaseTmV) -> Self { + BaseTmV(Rc::new(BaseTmV_::App(name, x))) } - /// Smart constructor for [TmV], [TmV_::List] case. - pub fn list(elems: Vec) -> Self { - TmV(Rc::new(TmV_::List(elems))) + /// Smart constructor for [BaseTmV], [BaseTmV_::List] case. + pub fn list(elems: Vec) -> Self { + BaseTmV(Rc::new(BaseTmV_::List(elems))) } - /// Smart constructor for [TmV], [TmV_::Cons] case. - pub fn cons(fields: Row) -> Self { - TmV(Rc::new(TmV_::Cons(fields))) + /// Smart constructor for [BaseTmV], [BaseTmV_::Cons] case. + pub fn cons(fields: Row) -> Self { + BaseTmV(Rc::new(BaseTmV_::Cons(fields))) } - /// Smart constructor for [TmV], [TmV_::Tt] case. - pub fn tt() -> Self { - TmV(Rc::new(TmV_::Tt)) + /// The empty record value `[]` — the unique element of the empty + /// record type. Also serves as the (proof-irrelevant) canonical + /// inhabitant of `Id` types under eta. + pub fn empty_cons() -> Self { + BaseTmV(Rc::new(BaseTmV_::Cons(Row::empty()))) } - /// Smart constructor for [TmV], [TmV_::Id] case. - pub fn id(x: TmV) -> Self { - TmV(Rc::new(TmV_::Id(x))) + /// Smart constructor for [BaseTmV], [BaseTmV_::Id] case. + pub fn id(x: BaseTmV) -> Self { + BaseTmV(Rc::new(BaseTmV_::Id(x))) } - /// Smart constructor for [TmV], [TmV_::Tab] case. - pub fn tab(mor: TmV) -> Self { - TmV(Rc::new(TmV_::Tab(mor))) + /// Smart constructor for [BaseTmV], [BaseTmV_::Tab] case. + pub fn tab(mor: BaseTmV) -> Self { + BaseTmV(Rc::new(BaseTmV_::Tab(mor))) } - /// Smart constructor for [TmV], [TmV_::Compose] case. - pub fn compose(f: TmV, g: TmV) -> Self { - TmV(Rc::new(TmV_::Compose(f, g))) + /// Smart constructor for [BaseTmV], [BaseTmV_::Compose] case. + pub fn compose(f: BaseTmV, g: BaseTmV) -> Self { + BaseTmV(Rc::new(BaseTmV_::Compose(f, g))) } - /// Smart constructor for [TmV], [TmV_::Meta] case. + /// Smart constructor for [BaseTmV], [BaseTmV_::Meta] case. pub fn meta(mv: MetaVar) -> Self { - TmV(Rc::new(TmV_::Meta(mv))) + BaseTmV(Rc::new(BaseTmV_::Meta(mv))) } /// Unwraps a neutral term, or panics. pub fn unwrap_neu(&self) -> TmN { match &**self { - TmV_::Neu(n, _) => n.clone(), + BaseTmV_::Neu(n, _) => n.clone(), _ => panic!("expected term to be a neutral"), } } } + +/// Inner enum for [FiberTyV]; value counterpart of [`super::stx::FiberTyS_`]. +/// +/// A fiber record stores its evaluated field types directly (no captured +/// environment, unlike [`RecordV`]): the only fields ever projected are +/// the closed [`Over`](Self::Over) generators, and the dependent +/// [`Id`](Self::Id) equation fields are read off by name downstream +/// (conversion and model generation) rather than re-evaluated. +pub enum FiberTyV_ { + /// The type of a fiber element over the codomain object `obj` (a base + /// object value, possibly modal). See [`super::stx::FiberTyS_::Over`]. + Over(BaseTmV), + /// An instance presented as a record of fiber types. See + /// [`super::stx::FiberTyS_::Record`]. + Record(Row), + /// A propositional equation between fiber elements. See + /// [`super::stx::FiberTyS_::Id`]. + Id(FiberTyV, FiberTmV, FiberTmV), +} + +/// Values for fiber types, dereferences to [FiberTyV_]. +#[derive(Clone, Deref)] +#[deref(forward)] +pub struct FiberTyV(Rc); + +impl FiberTyV { + /// Smart constructor for [FiberTyV], [FiberTyV_::Over] case. + pub fn over(obj: BaseTmV) -> Self { + Self(Rc::new(FiberTyV_::Over(obj))) + } + + /// Smart constructor for [FiberTyV], [FiberTyV_::Record] case. + pub fn record(fields: Row) -> Self { + Self(Rc::new(FiberTyV_::Record(fields))) + } + + /// Smart constructor for [FiberTyV], [FiberTyV_::Id] case. + pub fn id(ty: FiberTyV, tm1: FiberTmV, tm2: FiberTmV) -> Self { + Self(Rc::new(FiberTyV_::Id(ty, tm1, tm2))) + } +} + +/// Inner enum for [FiberTmV]; value counterpart of [`super::stx::FiberTmS_`]. +/// +/// Every fiber term is neutral, so — unlike [`BaseTmV_`] — there is no +/// closure/neutral split and no stored type for eta. Variables carry a +/// forward index into the fiber environment. +pub enum FiberTmV_ { + /// A fiber-context variable (generator or sub-instance import). + Var(FwdIdx, VarName, LabelSegment), + /// Projection of a generator out of a sub-instance import (`we.e`). + Proj(FiberTmV, FieldName, LabelSegment), + /// A fiber list literal. See [`super::stx::FiberTmS_::List`]. + List(Vec), + /// A theory object-operation applied to a fiber element. See + /// [`super::stx::FiberTmS_::ObApp`]. + ObApp(VarName, FiberTmV), + /// Application of a codomain morphism (identified by its path) to a + /// fiber element; the second field is the codomain object it lands at. + /// See [`super::stx::FiberTmS_::OverApp`]. + OverApp(Vec<(FieldName, LabelSegment)>, BaseTmV, FiberTmV), + /// A metavariable. + Meta(MetaVar), +} + +/// Values for fiber terms, dereferences to [FiberTmV_]. +#[derive(Clone, Deref)] +#[deref(forward)] +pub struct FiberTmV(Rc); + +impl FiberTmV { + /// Smart constructor for [FiberTmV], [FiberTmV_::Var] case. + pub fn var(fwd_idx: FwdIdx, var_name: VarName, label: LabelSegment) -> Self { + Self(Rc::new(FiberTmV_::Var(fwd_idx, var_name, label))) + } + + /// Smart constructor for [FiberTmV], [FiberTmV_::Proj] case. + pub fn proj(tm: FiberTmV, field_name: FieldName, label: LabelSegment) -> Self { + Self(Rc::new(FiberTmV_::Proj(tm, field_name, label))) + } + + /// Smart constructor for [FiberTmV], [FiberTmV_::List] case. + pub fn list(elems: Vec) -> Self { + Self(Rc::new(FiberTmV_::List(elems))) + } + + /// Smart constructor for [FiberTmV], [FiberTmV_::ObApp] case. + pub fn ob_app(name: VarName, arg: FiberTmV) -> Self { + Self(Rc::new(FiberTmV_::ObApp(name, arg))) + } + + /// Smart constructor for [FiberTmV], [FiberTmV_::OverApp] case. + pub fn over_app(mor: Vec<(FieldName, LabelSegment)>, cod: BaseTmV, inner: FiberTmV) -> Self { + Self(Rc::new(FiberTmV_::OverApp(mor, cod, inner))) + } + + /// Smart constructor for [FiberTmV], [FiberTmV_::Meta] case. + pub fn meta(mv: MetaVar) -> Self { + Self(Rc::new(FiberTmV_::Meta(mv))) + } +} diff --git a/packages/catlog/src/tt/wd.rs b/packages/catlog/src/tt/wd.rs index 50bf4c300..01861bfe5 100644 --- a/packages/catlog/src/tt/wd.rs +++ b/packages/catlog/src/tt/wd.rs @@ -1,8 +1,9 @@ //! Extract wiring diagrams from record types. -use super::{eval::*, theory::*, toplevel::*, util::*, val::*}; +use super::{eval::*, theory::*, toplevel::*, val::*}; use crate::wd::UWD; use crate::zero::QualifiedName; +use crate::zero::dtry::*; /// Extracts an undirected wiring diagram from a record type. /// @@ -22,8 +23,8 @@ use crate::zero::QualifiedName; /// to fields of arbitrary depth. In this function, any specializations more /// than one level deep are ignored. To capture these, one might look for a /// "nested UWD" data structure. -pub fn record_to_uwd(ty: &TyV) -> Option> { - let TyV_::Record(record_v) = &**ty else { +pub fn record_to_uwd(ty: &BaseTyV) -> Option> { + let BaseTyV_::Record(record_v) = &**ty else { return None; }; @@ -37,7 +38,7 @@ pub fn record_to_uwd(ty: &TyV) -> Option> { // First pass: add a box for each field that is itself of record type. for (field_name, (field_label, _)) in record_v.fields.iter() { let field_ty = eval.field_ty(ty, &tm_v, *field_name); - let TyV_::Record(r) = &&*field_ty else { + let BaseTyV_::Record(r) = &&*field_ty else { continue; }; uwd.add_box(*field_name, *field_label); @@ -49,10 +50,10 @@ pub fn record_to_uwd(ty: &TyV) -> Option> { // depth one can be expressed in a UWD. continue; }; - let TyV_::Sing(ty, tm) = &**spec_type else { + let BaseTyV_::Sing(ty, tm) = &**spec_type else { continue; }; - let (TyV_::Object(ob_type), TmV_::Neu(n, _)) = (&**ty, &**tm) else { + let (BaseTyV_::Object(ob_type), BaseTmV_::Neu(n, _)) = (&**ty, &**tm) else { continue; }; let qual_name = n.to_qualified_name(); @@ -67,7 +68,7 @@ pub fn record_to_uwd(ty: &TyV) -> Option> { let field_ty = eval.field_ty(ty, &tm_v, *field_name); match &&*field_ty { // Add outer port for each top-level field that is a junction. - TyV_::Object(ob_type) => { + BaseTyV_::Object(ob_type) => { let qual_name = QualifiedName::single(*field_name); if uwd.has_junction(&qual_name) { uwd.add_outer_port(*field_name, *field_label, ob_type.clone()); @@ -75,7 +76,7 @@ pub fn record_to_uwd(ty: &TyV) -> Option> { } } // Add port to box for each sub-field that is a junction. - TyV_::Record(r) => { + BaseTyV_::Record(r) => { let tm_v = eval.proj(&tm_v, *field_name, *field_label); for (port_name, (port_label, _)) in r.fields.iter() { if uwd.has_port(*field_name, *port_name) { @@ -84,7 +85,7 @@ pub fn record_to_uwd(ty: &TyV) -> Option> { let qual_name: QualifiedName = [*field_name, *port_name].into(); if uwd.has_junction(&qual_name) { let port_ty = eval.field_ty(&field_ty, &tm_v, *port_name); - let TyV_::Object(ob_type) = &*port_ty else { + let BaseTyV_::Object(ob_type) = &*port_ty else { continue; }; uwd.add_port(*field_name, *port_name, *port_label, ob_type.clone()); diff --git a/packages/catlog/src/wd/undirected.rs b/packages/catlog/src/wd/undirected.rs index 3e34d596c..1f1beee25 100644 --- a/packages/catlog/src/wd/undirected.rs +++ b/packages/catlog/src/wd/undirected.rs @@ -3,9 +3,9 @@ use derivative::Derivative; use std::{fmt, hash::Hash}; -use crate::tt::util::{Row, pretty::*}; use crate::validate::{self, Validate}; use crate::zero::{Column, HashColumn, LabelSegment, Mapping, MutMapping, NameSegment}; +use crate::zero::{pretty::*, row::Row}; /// Ports of a wiring diagram. /// diff --git a/packages/catlog/src/tt/util/dtry.rs b/packages/catlog/src/zero/dtry.rs similarity index 83% rename from packages/catlog/src/tt/util/dtry.rs rename to packages/catlog/src/zero/dtry.rs index 509f8e595..7994a7fee 100644 --- a/packages/catlog/src/tt/util/dtry.rs +++ b/packages/catlog/src/zero/dtry.rs @@ -1,9 +1,9 @@ //! Directories. -use crate::{ - tt::prelude::*, - zero::{LabelSegment, QualifiedLabel, QualifiedName}, -}; +use indexmap::IndexMap; + +use super::qualified::{LabelSegment, NameSegment, QualifiedLabel, QualifiedName}; +use super::row::Row; /// An entry in a [Dtry]. /// @@ -27,7 +27,7 @@ impl DtryEntry { } } - fn singleton(path: &[(FieldName, LabelSegment)], val: T) -> Self { + fn singleton(path: &[(NameSegment, LabelSegment)], val: T) -> Self { if path.is_empty() { DtryEntry::File(val) } else { @@ -52,7 +52,7 @@ impl DtryEntry { /// A directory. /// -/// A `Dtry` consists of a mapping from `FieldName`s to directory +/// A `Dtry` consists of a mapping from `NameSegment`s to directory /// entries, where a directory entry is either a "File" ([DtryEntry::File]), /// that is an element of `T`, or a "subdirectory" ([DtryEntry::SubDir]), /// which is just another directory. @@ -86,17 +86,17 @@ impl Dtry { } /// Iterate through the entries of the directory. - pub fn entries(&self) -> impl Iterator))> { + pub fn entries(&self) -> impl Iterator))> { self.0.iter() } /// Get the entry for `field` if it exists. - pub fn entry(&self, field: &FieldName) -> Option<&DtryEntry> { + pub fn entry(&self, field: &NameSegment) -> Option<&DtryEntry> { self.0.get(*field) } /// Create a singleton directory with just one entry at the given path. - pub fn singleton(path: &[(FieldName, LabelSegment)], val: T) -> Self { + pub fn singleton(path: &[(NameSegment, LabelSegment)], val: T) -> Self { assert!(!path.is_empty()); let ((field, label), path) = (path[0], &path[1..]); Dtry([(field, (label, DtryEntry::singleton(path, val)))].into_iter().collect()) @@ -124,8 +124,8 @@ impl Dtry { } } -impl From)>> for Dtry { - fn from(value: IndexMap)>) -> Self { +impl From)>> for Dtry { + fn from(value: IndexMap)>) -> Self { Self(value.into()) } } diff --git a/packages/catlog/src/tt/util/idx.rs b/packages/catlog/src/zero/idx.rs similarity index 100% rename from packages/catlog/src/tt/util/idx.rs rename to packages/catlog/src/zero/idx.rs diff --git a/packages/catlog/src/zero/mod.rs b/packages/catlog/src/zero/mod.rs index b8faee6db..6500e4959 100644 --- a/packages/catlog/src/zero/mod.rs +++ b/packages/catlog/src/zero/mod.rs @@ -2,8 +2,12 @@ pub mod alg; pub mod column; +pub mod dtry; +pub mod idx; +pub mod pretty; pub mod qualified; pub mod rig; +pub mod row; pub mod set; pub use self::column::*; diff --git a/packages/catlog/src/tt/util/pretty.rs b/packages/catlog/src/zero/pretty.rs similarity index 100% rename from packages/catlog/src/tt/util/pretty.rs rename to packages/catlog/src/zero/pretty.rs diff --git a/packages/catlog/src/zero/qualified.rs b/packages/catlog/src/zero/qualified.rs index 908f76d23..eaeda123a 100644 --- a/packages/catlog/src/zero/qualified.rs +++ b/packages/catlog/src/zero/qualified.rs @@ -14,7 +14,7 @@ use serde::{self, Deserialize, Serialize}; use tsify::Tsify; use super::column::{Column, IndexedHashColumn, Mapping, MutMapping}; -use crate::tt::util::pretty::*; +use super::pretty::*; /// A segment in a [qualified name](QualifiedName). /// diff --git a/packages/catlog/src/tt/util/row.rs b/packages/catlog/src/zero/row.rs similarity index 67% rename from packages/catlog/src/tt/util/row.rs rename to packages/catlog/src/zero/row.rs index baf0ada01..a357e54ec 100644 --- a/packages/catlog/src/tt/util/row.rs +++ b/packages/catlog/src/zero/row.rs @@ -4,12 +4,15 @@ use derivative::Derivative; use derive_more::From; use std::ops::Index; -use crate::{tt::prelude::*, zero::LabelSegment}; +use indexmap::IndexMap; -/// An insertion-ordered map from `FieldName` to `T`. +use super::qualified::{LabelSegment, NameSegment, label_seg, name_seg}; +use ustr::Ustr; + +/// An insertion-ordered map from `NameSegment` to `T`. /// /// Also stores a "label" for each entry, which may not be the same as the -/// FieldName in the case that the FieldName is a UUID. +/// NameSegment in the case that the NameSegment is a UUID. /// /// This is called "row" because it's a short name, and it corresponds to the idea /// of a row in a database, which is a map from fields to values. @@ -17,11 +20,11 @@ use crate::{tt::prelude::*, zero::LabelSegment}; /// Create this using the [FromIterator] implementation. #[derive(Clone, Derivative, PartialEq, Eq, From)] #[derivative(Default(bound = ""))] -pub struct Row(IndexMap); +pub struct Row(IndexMap); -impl Index for Row { +impl Index for Row { type Output = T; - fn index(&self, index: FieldName) -> &Self::Output { + fn index(&self, index: NameSegment) -> &Self::Output { self.get(index).unwrap() } } @@ -30,22 +33,22 @@ impl Row { /// Lookup the field `name` if it exists. /// /// Also see the [Index] implementation, which just `unwrap`s this. - pub fn get(&self, name: FieldName) -> Option<&T> { + pub fn get(&self, name: NameSegment) -> Option<&T> { self.0.get(&name).map(|p| &p.1) } /// Lookup the field `name` by mutable reference. - pub fn get_mut(&mut self, name: FieldName) -> Option<&mut T> { + pub fn get_mut(&mut self, name: NameSegment) -> Option<&mut T> { self.0.get_mut(&name).map(|p| &mut p.1) } /// Lookup the field `name` if it exists, and get its value and label. - pub fn get_with_label(&self, name: FieldName) -> Option<&(LabelSegment, T)> { + pub fn get_with_label(&self, name: NameSegment) -> Option<&(LabelSegment, T)> { self.0.get(&name) } /// Iterate through the fields in insertion order. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } @@ -60,7 +63,7 @@ impl Row { } /// Return whether the row contains the given field. - pub fn has(&self, field_name: FieldName) -> bool { + pub fn has(&self, field_name: NameSegment) -> bool { self.0.contains_key(&field_name) } @@ -75,13 +78,13 @@ impl Row { } /// Insert a new field. - pub fn insert(&mut self, field: FieldName, label: LabelSegment, value: T) { + pub fn insert(&mut self, field: NameSegment, label: LabelSegment, value: T) { self.0.insert(field, (label, value)); } } -impl FromIterator<(FieldName, (LabelSegment, T))> for Row { - fn from_iter>(iter: I) -> Self { +impl FromIterator<(NameSegment, (LabelSegment, T))> for Row { + fn from_iter>(iter: I) -> Self { Row(iter.into_iter().collect()) } } diff --git a/packages/document-types/src/v0/api.rs b/packages/document-types/src/v0/api.rs index 5ab7838eb..c75b00961 100644 --- a/packages/document-types/src/v0/api.rs +++ b/packages/document-types/src/v0/api.rs @@ -57,6 +57,9 @@ pub enum LinkType { #[serde(rename = "diagram-in")] DiagramIn, + #[serde(rename = "instance-of")] + InstanceOf, + #[serde(rename = "instantiation")] Instantiation, } @@ -75,6 +78,7 @@ pub(crate) mod arbitrary { proptest::sample::select(&[ LinkType::AnalysisOf, LinkType::DiagramIn, + LinkType::InstanceOf, LinkType::Instantiation, ]) .boxed() diff --git a/packages/document-types/src/v0/instance_judgment.rs b/packages/document-types/src/v0/instance_judgment.rs new file mode 100644 index 000000000..83923889d --- /dev/null +++ b/packages/document-types/src/v0/instance_judgment.rs @@ -0,0 +1,131 @@ +use serde::{Deserialize, Serialize}; +use tsify::Tsify; +use uuid::Uuid; + +use super::api::Link; +use super::model::{Mor, Ob}; +use super::theory::{Modality, ObOp}; + +/// Declares a generator of an instance of a model. +/// +/// The generator lies in the fiber over an object of the codomain model. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)] +pub struct InstanceGenDecl { + /// Human-readable label for generator. + pub name: String, + + /// Globally unique identifier of generator. + pub id: Uuid, + + /// Object of the codomain model that the generator lies over, if defined. + pub over: Option, +} + +/// Imports another instance of the codomain model into this instance. +/// +/// The generators of the imported instance become available in equations +/// under qualified names, projecting out of the import. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)] +pub struct InstanceImport { + /// Human-readable label for the import. + pub name: String, + + /// Globally unique identifier of the import. + pub id: Uuid, + + /// Link to the instance document to import, if defined. + pub instance: Option, +} + +/// Declares an equation between two terms in an instance. +/// +/// The two sides are [instance terms](InstanceTm), built from generators by +/// the actions of the codomain model's morphisms. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)] +pub struct InstanceEqnDecl { + /// Human-readable label for equation. + pub name: String, + + /// Globally unique identifier of equation. + pub id: Uuid, + + /// The left-hand side of the equation, if defined. + pub lhs: Option, + + /// The right-hand side of the equation, if defined. + pub rhs: Option, +} + +/// A term in the language of an instance of a model. +/// +/// Terms are given at the syntax level: applications may nest freely, as in +/// `add(sub([x, y]), z)`. Elaboration normalizes a term to a single morphism +/// action applied once to a base of generators. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[serde(tag = "tag", content = "content")] +#[tsify(into_wasm_abi, from_wasm_abi, missing_as_null)] +pub enum InstanceTm { + /// Reference to a generator by qualified name. + /// + /// Generators of imported instances are referenced by paths through the + /// import, e.g. `"."`. + Generator(String), + + /// The action of a morphism of the codomain model on an argument term. + App { + /// The acting morphism. + mor: Mor, + + /// The argument, lying over the morphism's domain. + arg: Box, + }, + + /// List of terms, each possibly ill-defined, in a list modality. + /// + /// Lies over a list object of the codomain model. + List { + /// The list modality. + modality: Modality, + + /// The terms in the list. + terms: Vec>, + }, + + /// Application of an object operation to a term. + /// + /// Lies over the operation applied to the fiber of the argument, e.g. a + /// term over a tensor product of objects. + ObApp { + /// The object operation. + op: ObOp, + + /// The argument term. + tm: Box, + }, +} + +/// A judgment defining part of an instance of a model of a double theory. +/// +/// Instance notebooks target presentations of instances +/// via fibered generators plus equations between morphism actions on them, in +/// contrast to [diagram judgments](super::diagram_judgment::DiagramJudgment), +/// which present instances less efficiently via a model morphism. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Tsify)] +#[serde(tag = "tag")] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub enum InstanceJudgment { + /// Declares a generator of the instance. + #[serde(rename = "generator")] + Generator(InstanceGenDecl), + + /// Imports another instance of the codomain model. + #[serde(rename = "import")] + Import(InstanceImport), + + /// Declares an equation between two instance terms. + #[serde(rename = "equation")] + Equation(InstanceEqnDecl), +} diff --git a/packages/document-types/src/v0/mod.rs b/packages/document-types/src/v0/mod.rs index fb177c539..c36df2736 100644 --- a/packages/document-types/src/v0/mod.rs +++ b/packages/document-types/src/v0/mod.rs @@ -3,6 +3,7 @@ pub mod api; pub mod cell; pub mod diagram_judgment; pub mod document; +pub mod instance_judgment; pub mod model; pub mod model_judgment; pub mod notebook; diff --git a/packages/document-types/src/v1/mod.rs b/packages/document-types/src/v1/mod.rs index 06d8a8699..37c6810ee 100644 --- a/packages/document-types/src/v1/mod.rs +++ b/packages/document-types/src/v1/mod.rs @@ -1,6 +1,8 @@ use crate::v0; -pub use v0::{analysis, api, cell, diagram_judgment, model, model_judgment, path, theory}; +pub use v0::{ + analysis, api, cell, diagram_judgment, instance_judgment, model, model_judgment, path, theory, +}; pub mod document; pub mod notebook; @@ -10,6 +12,7 @@ pub use api::*; pub use cell::*; pub use diagram_judgment::*; pub use document::*; +pub use instance_judgment::*; pub use model::*; pub use model_judgment::*; pub use notebook::*; diff --git a/packages/document-types/src/v2/document.rs b/packages/document-types/src/v2/document.rs index 6f142093e..7c4b4ab1c 100644 --- a/packages/document-types/src/v2/document.rs +++ b/packages/document-types/src/v2/document.rs @@ -36,6 +36,21 @@ pub struct DiagramDocumentContent { pub version: String, } +/// This is the content of an instance document, presenting an instance of the +/// model that the document's `instanceOf` link points to. +/// +/// Not yet a variant of [`Document`]: wiring into the document enum (and the +/// frontend) is deferred until instance notebooks elaborate. +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct InstanceDocumentContent { + pub name: String, + #[serde(rename = "instanceOf")] + pub instance_of: Link, + pub notebook: Notebook, + pub version: String, +} + #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct AnalysisDocumentContent { diff --git a/packages/document-types/src/v2/mod.rs b/packages/document-types/src/v2/mod.rs index ce20e6a8a..b8a8b4dd1 100644 --- a/packages/document-types/src/v2/mod.rs +++ b/packages/document-types/src/v2/mod.rs @@ -1,6 +1,8 @@ use crate::v1; -pub use v1::{analysis, api, diagram_judgment, model, model_judgment, path, theory}; +pub use v1::{ + analysis, api, diagram_judgment, instance_judgment, model, model_judgment, path, theory, +}; pub mod cell; pub mod document; @@ -11,6 +13,7 @@ pub use api::*; pub use cell::*; pub use diagram_judgment::*; pub use document::*; +pub use instance_judgment::*; pub use model::*; pub use model_judgment::*; pub use notebook::*; diff --git a/rfc/preview.sh b/rfc/preview.sh new file mode 100755 index 000000000..63d3a5065 --- /dev/null +++ b/rfc/preview.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Lightweight RFC previewer: render a Quarto-flavored .md to standalone HTML +# with MathJax, without invoking Quarto. +# +# ./preview.sh 0009 # build once and open +# ./preview.sh 0009 --watch # serve + live-reload on save +set -euo pipefail +cd "$(dirname "$0")" + +num="${1:?usage: preview.sh [--watch]}" +mode="${2:-}" +src="${num}.md" +out="${num}.preview.html" +port=8765 + +# Live-reload snippet: poll this page's Last-Modified over HTTP and reload only +# when it changes, preserving scroll position across reloads. +reload_js="$(cat <<'EOF' + +EOF +)" + +build() { + local tmp after mfile + tmp="$(mktemp -t rfc-preview-XXXX).md" + after="$(mktemp -t rfc-reload-XXXX).html" + mfile="$(mktemp -t rfc-macros-XXXX).js" + printf '%s\n' "$reload_js" > "$after" + + # Collect \newcommand macros from each {{< include FILE.qmd >}} as KaTeX + # `macros` option entries ("\\name": "body"), mirroring how Quarto feeds + # _macros.qmd to KaTeX. KaTeX infers arity from #1.. in the body, so the + # optional [n] is dropped; backslashes/quotes are escaped for the JS string. + : > "$mfile" + grep -oE '\{\{< include [^ ]+\.qmd' "$src" | awk '{print $3}' | while read -r inc; do + [ -f "$inc" ] && perl -ne ' + next if /^\s*