Examples

The following examples are designed to get you started using Cowl. They are ordered by complexity, so it is recommended that you read them sequentially.

Reading ontologies

Related documentation: ontology reading

 1/*
 2 * This introductory example shows how to read an ontology
 3 * and log its axioms and annotations.
 4 *
 5 * @note Most errors are not handled for the sake of simplicity.
 6 *
 7 * @author Ivano Bilenchi
 8 *
 9 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
10 * @copyright <https://swot.sisinflab.poliba.it>
11 * @copyright SPDX-License-Identifier: EPL-2.0
12 */
13#include "cowl.h"
14#include "ulib.h"
15#include <stdio.h>
16#include <stdlib.h>
17
18#define ONTO "example_pizza.owl"
19
20int main(void) {
21    // The library must always be initialized before use.
22    cowl_init();
23
24    // Read an ontology from file.
25    CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
26
27    if (!onto) {
28        // The ontology could not be read.
29        fprintf(stderr, "Failed to load ontology " ONTO "\n");
30        return EXIT_FAILURE;
31    }
32
33    // Do stuff with the ontology. In this case it is simply logged
34    // to the standard output using the default writer.
35    cowl_ontology_to_stream(onto, uostream_std());
36
37    // Release the ontology.
38    cowl_release(onto);
39
40    return EXIT_SUCCESS;
41}

Error handling

Related documentation: error handling

 1/*
 2 * This example is the same as the previous one,
 3 * except all errors are handled and logged.
 4 *
 5 * @author Ivano Bilenchi
 6 *
 7 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
 8 * @copyright <https://swot.sisinflab.poliba.it>
 9 * @copyright SPDX-License-Identifier: EPL-2.0
10 */
11#include "cowl.h"
12#include "ulib.h"
13#include <errno.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17
18#define ONTO "example_pizza.owl"
19
20static void log_error(char const *msg) {
21    fprintf(stderr, "Error: %s\n", msg);
22}
23
24static void log_cowl_error(CowlError const *error) {
25    // Cowl integrates with the uLib I/O API. This is usually preferable as it allows
26    // writing most objects, including errors, to output streams without needing to
27    // convert them to strings first (i.e. without additional allocations).
28    UOStream *stream = uostream_stderr();
29    cowl_write_literal(stream, "Error: ");
30    cowl_write_error(stream, error);
31    cowl_write_literal(stream, "\n");
32}
33
34int main(void) {
35    // API initialization can fail due to low memory.
36    if (cowl_is_err(cowl_init())) {
37        log_error("API initialization failure");
38        return EXIT_FAILURE;
39    }
40
41    // Cowl objects are allocated on the heap, so we need to check for NULL.
42    CowlReader *reader = cowl_reader_functional();
43
44    if (!reader) {
45        log_error("allocation failure");
46        return EXIT_FAILURE;
47    }
48
49    UString const path = ustring_literal(ONTO);
50
51    // The returned ontology is NULL if an error occurs during reading,
52    // e.g. due to I/O or syntax errors. More details about the error can be obtained
53    // by inspecting the return code.
54    cowl_ret ret;
55    CowlOntology *onto = cowl_reader_read_ontology_from_path(reader, path, &ret);
56
57    if (!onto) {
58        if (ret == COWL_ERR_IO) {
59            // An I/O error occurred, e.g. the file does not exist or is not
60            // readable. Further details can be obtained by inspecting `errno`.
61            log_error(errno ? strerror(errno) : "unknown I/O error");
62        } else if (ret == COWL_ERR_SYNTAX) {
63            // In case of syntax errors, we can log the last one.
64            log_cowl_error(cowl_reader_last_error(reader));
65        } else {
66            // Some other error occurred.
67            log_error("ontology read failure");
68        }
69        return EXIT_FAILURE;
70    }
71
72    // Stream operations can fail, so we need to check for errors.
73    ret = cowl_ontology_to_stream(onto, uostream_std());
74
75    if (cowl_is_err(ret)) {
76        log_error("ontology write failure");
77        return EXIT_FAILURE;
78    }
79
80    cowl_release_all(onto, reader);
81    return EXIT_SUCCESS;
82}

Querying ontologies

Related documentation: ontology querying

Basic queries

 1/*
 2 * In this example we will be logging the direct atomic subclasses of a class.
 3 *
 4 * @note Most errors are not handled for the sake of simplicity.
 5 *
 6 * @author Ivano Bilenchi
 7 *
 8 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
 9 * @copyright <https://swot.sisinflab.poliba.it>
10 * @copyright SPDX-License-Identifier: EPL-2.0
11 */
12#include "cowl.h"
13#include "ulib.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#define ONTO "example_pizza.owl"
18#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
19#define CLASS_NAME "Food"
20
21// Iterator body, invoked for each class expression matching the query.
22static cowl_ret for_each_cls(void *std_out, CowlAny *cls) {
23    // We are only interested in atomic classes. Note that due to pseudo-inheritance
24    // this check ensures that the concrete type of 'exp' is CowlClass.
25    if (cowl_cls_exp_get_type(cls) == COWL_CET_CLASS) {
26        cowl_write_string(std_out, cowl_get_rem(cls));
27        cowl_write_literal(std_out, "\n");
28    }
29    return COWL_CONTINUE;
30}
31
32int main(void) {
33    cowl_init();
34
35    CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
36
37    if (!onto) {
38        fprintf(stderr, "Failed to load ontology " ONTO "\n");
39        return EXIT_FAILURE;
40    }
41
42    // Get the class whose atomic subclasses we are interested in.
43    CowlClass *cls = cowl_class_from_literal(NS CLASS_NAME);
44
45    // Run the query.
46    cowl_write_literal(uostream_std(), "Subclasses of " CLASS_NAME ":\n");
47
48    CowlIterator iter = { uostream_std(), for_each_cls };
49    cowl_ontology_iterate_sub_classes(onto, cls, &iter);
50
51    // Cleanup.
52    cowl_release_all(cls, onto);
53
54    return EXIT_SUCCESS;
55}

Recursive queries

 1/*
 2 * In this example we will be logging the atomic subclasses of a class recursively.
 3 *
 4 * @note Most errors are not handled for the sake of simplicity.
 5 *
 6 * @author Ivano Bilenchi
 7 *
 8 * @copyright Copyright (c) 2019 SisInf Lab, Polytechnic University of Bari
 9 * @copyright <https://swot.sisinflab.poliba.it>
10 * @copyright SPDX-License-Identifier: EPL-2.0
11 */
12#include "cowl.h"
13#include "ulib.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#define ONTO "example_pizza.owl"
18#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
19#define CLASS_NAME "Food"
20
21// Custom context struct for the query.
22typedef struct CustomContext {
23    CowlOntology *onto;
24    UOStream *stream;
25} CustomContext;
26
27// Iterator body, invoked for each class expression matching the query.
28static cowl_ret for_each_cls(void *ptr, CowlAny *cls) {
29    if (cowl_cls_exp_get_type(cls) != COWL_CET_CLASS) return COWL_CONTINUE;
30
31    // Log the IRI remainder.
32    CustomContext *ctx = ptr;
33    cowl_write_string(ctx->stream, cowl_get_rem(cls));
34    cowl_write_literal(ctx->stream, "\n");
35
36    // Recurse.
37    CowlIterator iter = { ctx, for_each_cls };
38    return cowl_ontology_iterate_sub_classes(ctx->onto, cls, &iter);
39}
40
41int main(void) {
42    cowl_init();
43
44    CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
45
46    if (!onto) {
47        fprintf(stderr, "Failed to load ontology " ONTO "\n");
48        return EXIT_FAILURE;
49    }
50
51    CowlClass *cls = cowl_class_from_literal(NS CLASS_NAME);
52    cowl_write_literal(uostream_std(), "Recursive subclasses of " CLASS_NAME ":\n");
53
54    // Since we are going to perform a recursive query,
55    // we need the ontology to be part of the context.
56    CustomContext ctx = { onto, uostream_std() };
57    CowlIterator iter = { &ctx, for_each_cls };
58    cowl_ontology_iterate_sub_classes(onto, cls, &iter);
59
60    cowl_release_all(cls, onto);
61    return EXIT_SUCCESS;
62}

Advanced queries

 1/*
 2 * In this example we will be logging axioms of different types referencing
 3 * multiple entities.
 4 *
 5 * @note Most errors are not handled for the sake of simplicity.
 6 *
 7 * @author Ivano Bilenchi
 8 *
 9 * @copyright Copyright (c) 2024 SisInf Lab, Polytechnic University of Bari
10 * @copyright <https://swot.sisinflab.poliba.it>
11 * @copyright SPDX-License-Identifier: EPL-2.0
12 */
13#include "cowl.h"
14#include "cowl_axiom_filter.h"
15#include "cowl_axiom_flags.h"
16#include "cowl_obj_prop.h"
17#include "cowl_writer.h"
18#include "ulib.h"
19#include <stdio.h>
20#include <stdlib.h>
21
22#define ONTO "example_pizza.owl"
23#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
24#define CLASS_NAME "ThinAndCrispyBase"
25#define PROPERTY_NAME "hasBase"
26
27int main(void) {
28    cowl_init();
29
30    CowlOntology *onto = cowl_ontology_from_path(ustring_literal(ONTO), NULL);
31
32    if (!onto) {
33        fprintf(stderr, "Failed to load ontology " ONTO "\n");
34        return EXIT_FAILURE;
35    }
36
37    cowl_write_literal(uostream_std(), "Matching axioms:\n");
38    CowlClass *cls = cowl_class_from_literal(NS CLASS_NAME);
39    CowlObjProp *prop = cowl_obj_prop_from_literal(NS PROPERTY_NAME);
40
41    // We want to log all SubClassOf and EquivalentClasses axioms that
42    // reference both the class and the property.
43
44    // Note that this query can be done via other functions as well,
45    // though using a CowlAxiomFilter is usually more efficient, as it
46    // is used to determine the best indexing strategy for the query.
47    CowlAxiomFlags types = COWL_AF_SUB_CLASS | COWL_AF_EQUIV_CLASSES;
48    CowlAxiomFilter filter = cowl_axiom_filter(types);
49    cowl_axiom_filter_add_primitive(&filter, cls);
50    cowl_axiom_filter_add_primitive(&filter, prop);
51
52    // Other than using custom iterators, Cowl provides some built-in ones
53    // for common tasks. Here we use a set iterator: all axioms matching
54    // the filter will be collected in a set, removing any duplicates.
55    UHash(CowlObjectPtr) set = uhset(CowlObjectPtr);
56    CowlIterator iter = cowl_iterator_set(&set, false);
57    cowl_ontology_iterate_axioms_matching(onto, &filter, &iter);
58
59    // [Optional] Adding the pizza prefix to the default prefix map
60    // allows for more concise IRIs when logging the axioms.
61    CowlPrefixMap *pm = cowl_get_prefix_map();
62    UString const prefix = ustring_literal("pizza");
63    UString const ns = ustring_literal(NS);
64    cowl_prefix_map_add_raw(pm, prefix, ns, false);
65
66    // We can now log the axioms by iterating over the set.
67    UOStream *stream = uostream_std();
68    uhash_foreach (CowlObjectPtr, &set, item) {
69        cowl_write(stream, *item.key);
70        cowl_write_literal(stream, "\n");
71    }
72
73    uhash_deinit(CowlObjectPtr, &set);
74    cowl_release_all(cls, prop, onto);
75    return EXIT_SUCCESS;
76}

Editing and writing ontologies

Related documentation: ontology editing, ontology writing

 1/*
 2 * This example demonstrates ontology editing and serialization to file.
 3 *
 4 * @note Most errors are not handled for the sake of simplicity.
 5 *
 6 * @author Ivano Bilenchi
 7 *
 8 * @copyright Copyright (c) 2022 SisInf Lab, Polytechnic University of Bari
 9 * @copyright <https://swot.sisinflab.poliba.it>
10 * @copyright SPDX-License-Identifier: EPL-2.0
11 */
12#include "cowl.h"
13#include "ulib.h"
14#include <stdio.h>
15#include <stdlib.h>
16
17#define IN_PATH "example_pizza.owl"
18#define OUT_PATH "example_pizza_new.owl"
19#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
20
21int main(void) {
22    cowl_init();
23
24    // We will be editing the pizza ontology by adding the Porcini pizza.
25    printf("Reading ontology " IN_PATH "... ");
26    CowlOntology *onto = cowl_ontology_from_path(ustring_literal(IN_PATH), NULL);
27
28    if (onto) {
29        puts("done!");
30    } else {
31        puts("failed");
32        return EXIT_FAILURE;
33    }
34
35    // Declaration(Class(pizza:PorciniTopping))
36    CowlClass *porcini_topping = cowl_class_from_literal(NS "PorciniTopping");
37    CowlAnyAxiom *axiom = cowl_decl_axiom(porcini_topping, NULL);
38    cowl_ontology_add_axiom(onto, axiom);
39    cowl_release(axiom);
40
41    // Declaration(Class(pizza:Porcini))
42    CowlClass *porcini = cowl_class_from_literal(NS "Porcini");
43    axiom = cowl_decl_axiom(porcini, NULL);
44    cowl_ontology_add_axiom(onto, axiom);
45    cowl_release(axiom);
46
47    // SubClassOf(pizza:PorciniTopping pizza:MushroomTopping)
48    CowlClass *mushroom_topping = cowl_class_from_literal(NS "MushroomTopping");
49    axiom = cowl_sub_cls_axiom(porcini_topping, mushroom_topping, NULL);
50    cowl_ontology_add_axiom(onto, axiom);
51    cowl_release_all(axiom, mushroom_topping);
52
53    // SubClassOf(pizza:Porcini pizza:NamedPizza)
54    CowlClass *named_pizza = cowl_class_from_literal(NS "NamedPizza");
55    axiom = cowl_sub_cls_axiom(porcini, named_pizza, NULL);
56    cowl_ontology_add_axiom(onto, axiom);
57    cowl_release_all(axiom, named_pizza);
58
59    // SubClassOf(pizza:Porcini
60    // ObjectSomeValuesFrom(pizza:hasTopping pizza:MozzarellaTopping))
61    CowlObjProp *has_topping = cowl_obj_prop_from_literal(NS "hasTopping");
62    CowlClass *mozzarella_topping = cowl_class_from_literal(NS "MozzarellaTopping");
63    CowlObjQuant *obj_quant = cowl_obj_some(has_topping, mozzarella_topping);
64    axiom = cowl_sub_cls_axiom(porcini, obj_quant, NULL);
65    cowl_ontology_add_axiom(onto, axiom);
66    cowl_release_all(axiom, obj_quant, mozzarella_topping);
67
68    // SubClassOf(pizza:Porcini
69    // ObjectSomeValuesFrom(pizza:hasTopping pizza:PorciniTopping))
70    obj_quant = cowl_obj_some(has_topping, porcini_topping);
71    axiom = cowl_sub_cls_axiom(porcini, obj_quant, NULL);
72    cowl_ontology_add_axiom(onto, axiom);
73    cowl_release_all(axiom, obj_quant);
74
75    // SubClassOf(pizza:Porcini ObjectAllValuesFrom(pizza:hasTopping
76    // ObjectUnionOf(pizza:MozzarellaTopping pizza:PorciniTopping)))
77    CowlNAryCls *closure = cowl_obj_union_of(mozzarella_topping, porcini_topping);
78    obj_quant = cowl_obj_all(has_topping, closure);
79    axiom = cowl_sub_cls_axiom(porcini, obj_quant, NULL);
80    cowl_ontology_add_axiom(onto, axiom);
81    cowl_release_all(axiom, obj_quant, closure);
82    cowl_release_all(porcini_topping, porcini, has_topping);
83
84    // Serialize the edited ontology to a new file.
85    printf("Writing ontology " OUT_PATH "... ");
86    cowl_ret ret = cowl_ontology_to_path(onto, ustring_literal(OUT_PATH));
87
88    if (cowl_is_err(ret)) {
89        puts("failed");
90    } else {
91        puts("done!");
92    }
93
94    cowl_release(onto);
95    return EXIT_SUCCESS;
96}

Ontology streams

Related documentation: input streams, output streams

Input streams

 1/*
 2 * In this example we will be logging the direct atomic subclasses
 3 * of a certain class, but we will do so without instantiating a
 4 * CowlOntology object.
 5 *
 6 * @note Most errors are not handled for the sake of simplicity.
 7 *
 8 * @author Ivano Bilenchi
 9 *
10 * @copyright Copyright (c) 2023 SisInf Lab, Polytechnic University of Bari
11 * @copyright <https://swot.sisinflab.poliba.it>
12 * @copyright SPDX-License-Identifier: EPL-2.0
13 */
14#include "cowl.h"
15#include "ulib.h"
16#include <stdio.h>
17#include <stdlib.h>
18
19#define ONTO "example_pizza.owl"
20#define NS "http://www.co-ode.org/ontologies/pizza/pizza.owl#"
21#define CLASS_NAME "Food"
22
23// Change handler, invoked for each construct read from the ontology document.
24static cowl_ret handle_axiom(void *target_class, CowlChange change) {
25    // We are only interested in subclass axioms.
26    if (change.part != COWL_PART_AXIOM) return COWL_OK;
27
28    CowlAnyAxiom *axiom = change.value;
29    if (cowl_axiom_get_type(axiom) != COWL_AT_SUB_CLASS) return COWL_OK;
30
31    // We are only interested in axioms where the superclass is the target class.
32    CowlAnyClsExp *cls = cowl_sub_cls_axiom_get_super(axiom);
33    if (!cowl_equals(target_class, cls)) return COWL_OK;
34
35    // We are only interested in axioms where the subclass is atomic.
36    cls = cowl_sub_cls_axiom_get_sub(axiom);
37    if (cowl_cls_exp_get_type(cls) != COWL_CET_CLASS) return COWL_OK;
38
39    // Log the IRI remainder.
40    puts(cowl_string_get_cstring(cowl_get_rem(cls)));
41    return COWL_OK;
42}
43
44int main(void) {
45    cowl_init();
46
47    CowlClass *target_class = cowl_class_from_literal(NS CLASS_NAME);
48
49    // Configure the change handler for the incoming stream.
50    CowlChangeHandler handler = {
51        .ctx = target_class,
52        .handle = handle_axiom,
53    };
54
55    // Process the ontology document as a stream of changes.
56    puts("Atomic subclasses of " CLASS_NAME ":");
57    CowlReader *reader = cowl_get_reader();
58    if (cowl_reader_read_path(reader, ustring_literal(ONTO), handler)) {
59        return EXIT_FAILURE;
60    }
61
62    cowl_release(target_class);
63    return EXIT_SUCCESS;
64}

Output streams

  1/*
  2 * In this example we will be creating a new ontology document by using an
  3 * ontology output stream.
  4 *
  5 * @note Most errors are not handled for the sake of simplicity.
  6 *
  7 * @author Ivano Bilenchi
  8 *
  9 * @copyright Copyright (c) 2023 SisInf Lab, Polytechnic University of Bari
 10 * @copyright <https://swot.sisinflab.poliba.it>
 11 * @copyright SPDX-License-Identifier: EPL-2.0
 12 */
 13
 14#include "cowl.h"
 15#include "ulib.h"
 16#include <stdio.h>
 17#include <stdlib.h>
 18
 19#define PATH "porcini_pizza.owl"
 20
 21#define IMPORT_IRI "http://www.co-ode.org/ontologies/pizza"
 22#define IMPORT_NS IMPORT_IRI "/pizza.owl#"
 23
 24#define IRI "http://example.com/ontologies/porcini_pizza"
 25#define NS IRI "/porcini_pizza.owl#"
 26
 27int main(void) {
 28    cowl_init();
 29
 30    printf("Generating ontology " PATH "... ");
 31
 32    UOStream ostream;
 33    if (uostream_to_path(&ostream, PATH)) {
 34        // Initializing and writing to the stream may fail.
 35        // IO errors should be handled as fit for the application.
 36        goto err_io;
 37    }
 38
 39    CowlWriter *writer = cowl_get_writer();
 40
 41    // Optional: setup prefixes so that IRIs can be rendered in their prefixed form.
 42    CowlPrefixMap *map = cowl_prefix_map();
 43    cowl_prefix_map_add_raw(map, ustring_literal(""), ustring_literal(NS), false);
 44    cowl_prefix_map_add_raw(map, ustring_literal("pizza"),
 45                            ustring_literal(IMPORT_NS), false);
 46
 47    // Write the ontology header.
 48    CowlIRI *iri = cowl_iri_from_literal(IRI);
 49    CowlIRI *import_iri = cowl_iri_from_literal(IMPORT_IRI);
 50    UVec(CowlObjectPtr) imports = uvec(CowlObjectPtr);
 51    uvec_push(CowlObjectPtr, &imports, import_iri);
 52
 53    CowlOntologyHeader header = {
 54        .pm = map,
 55        .iri = iri,
 56        .imports = &imports,
 57    };
 58
 59    if (cowl_writer_write_header(writer, &ostream, header)) goto err_io;
 60
 61    cowl_release_all(iri, import_iri, map);
 62    uvec_deinit(CowlObjectPtr, &imports);
 63
 64    // Write the axioms.
 65    // Declaration(Class(:PorciniTopping))
 66    CowlClass *porcini = cowl_class_from_literal(NS "PorciniTopping");
 67    CowlAnyAxiom *axiom = cowl_decl_axiom(porcini, NULL);
 68    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
 69    cowl_release(axiom);
 70
 71    // Declaration(Class(:Porcini))
 72    CowlClass *porcini_pizza = cowl_class_from_literal(NS "Porcini");
 73    axiom = cowl_decl_axiom(porcini_pizza, NULL);
 74    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
 75    cowl_release(axiom);
 76
 77    // SubClassOf(:PorciniTopping pizza:MushroomTopping)
 78    CowlClass *mushroom = cowl_class_from_literal(IMPORT_NS "MushroomTopping");
 79    axiom = cowl_sub_cls_axiom(porcini, mushroom, NULL);
 80    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
 81    cowl_release_all(axiom, mushroom);
 82
 83    // SubClassOf(:Porcini pizza:NamedPizza)
 84    CowlClass *named_pizza = cowl_class_from_literal(IMPORT_NS "NamedPizza");
 85    axiom = cowl_sub_cls_axiom(porcini_pizza, named_pizza, NULL);
 86    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
 87    cowl_release_all(axiom, named_pizza);
 88
 89    // SubClassOf(:Porcini
 90    // ObjectSomeValuesFrom(pizza:hasTopping pizza:MozzarellaTopping))
 91    CowlObjProp *has_topping = cowl_obj_prop_from_literal(IMPORT_NS "hasTopping");
 92    CowlClass *mozzarella = cowl_class_from_literal(IMPORT_NS "MozzarellaTopping");
 93    CowlObjQuant *obj_quant = cowl_obj_some(has_topping, mozzarella);
 94    axiom = cowl_sub_cls_axiom(porcini_pizza, obj_quant, NULL);
 95    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
 96    cowl_release_all(axiom, obj_quant);
 97
 98    // SubClassOf(:Porcini
 99    // ObjectSomeValuesFrom(pizza:hasTopping :PorciniTopping))
100    obj_quant = cowl_obj_some(has_topping, porcini);
101    axiom = cowl_sub_cls_axiom(porcini_pizza, obj_quant, NULL);
102    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
103    cowl_release_all(axiom, obj_quant);
104
105    // SubClassOf(:Porcini ObjectAllValuesFrom(pizza:hasTopping
106    // ObjectUnionOf(pizza:MozzarellaTopping :PorciniTopping)))
107    CowlNAryCls *closure = cowl_obj_union_of(mozzarella, porcini);
108    obj_quant = cowl_obj_all(has_topping, closure);
109    axiom = cowl_sub_cls_axiom(porcini_pizza, obj_quant, NULL);
110    if (cowl_writer_write_axiom(writer, &ostream, axiom)) goto err_io;
111    cowl_release_all(axiom, obj_quant, closure);
112    cowl_release_all(porcini, porcini_pizza, has_topping, mozzarella);
113
114    // Finally, write the footer.
115    if (cowl_writer_write_footer(writer, &ostream)) goto err_io;
116    uostream_deinit(&ostream);
117
118    puts("done!");
119    return EXIT_SUCCESS;
120
121err_io:
122    puts("failed");
123    return EXIT_FAILURE;
124}