The mechanism by which Inform records the characteristics of different kinds.


§1. Constructors are divided into four groups:

define PUNCTUATION_GRP 1 /* used in the construction of other kinds only */
define PROTOCOL_GRP 2 /* such as arithmetic value */
define BASE_CONSTRUCTOR_GRP 3 /* such as number */
define PROPER_CONSTRUCTOR_GRP 4 /* with positive arity, such as "list of ..." */

§2. Besides all the properties of kinds used in this module, Inform also needs to store further metadata in order to be able to make the extensive run-time code needed to support all these kinds in actual programs. All of this means that a kind_constructor object is a great big rag-bag of properties, some set by commands in Neptune files, others set by calls from Inform.

So, deep breath:

define MAX_KIND_CONSTRUCTION_ARITY 2
typedef struct kind_constructor {
    struct noun *dt_tag; /* text of name */
    int group; /* one of the four values above */

    /* A: how this came into being */
    int is_incompletely_defined; /* newly defined and ambiguous as yet */
    struct parse_node *where_defined_in_source_text; /* if so */

    /* B: constructing kinds */
    int constructor_arity; /* 0 for base, 1 for unary, 2 for binary */
    int variance[MAX_KIND_CONSTRUCTION_ARITY]; /* must be COVARIANT or CONTRAVARIANT */
    int tupling[MAX_KIND_CONSTRUCTION_ARITY]; /* extent to which tupling is permitted */
    struct kind *cached_kind; /* cached result of Kinds::base_construction */

    /* C: compatibility with other kinds */
    struct parse_node *superkind_set_at; /* where it says, e.g., "A rabbit is a kind of animal" */
    struct kind_constructor_casting_rule *first_casting_rule; /* list of these */
    struct kind_constructor_instance_rule *first_instance_rule; /* list of these */

    /* D: how constant values of this kind are expressed */
    struct literal_pattern *ways_to_write_literals; /* list of ways to write this */
    struct table *named_values_created_with_table; /* alternatively... */
    int next_free_value; /* to make distinguishable instances of this kind */
    int constant_compilation_method; /* one of the *_CCM values */
    int forbid_assertion_creation; /* an enumeration which cannot be explicitly created? */

    /* E: knowledge about values of this kind */
    struct inference_subject *base_as_infs; /* inferences about properties */
    struct text_stream *default_value; /* used for built-in types only */

    /* F: behaviour as a property as well */
    int can_coincide_with_property; /* allowed to coincide in name with a property */
    struct property *coinciding_property; /* property of the same name, if any */

    /* G: performing arithmetic */
    struct text_stream *comparison_routine; /* for instance, when sorting table or list entries */
    int dimensionless;
    struct dimensional_rules dim_rules; /* how arithmetic operations work here */
    struct unit_sequence dimensional_form; /* dimensions of this kind */
    int dimensional_form_fixed; /* whether they are derived */
    struct kind_constructor *relative_kind;

    /* H: representing this kind at run-time */
    struct text_stream *explicit_identifier; /* to become an Inter identifier */
    int class_number; /* for classes of object */
    #ifdef CORE_MODULE
    struct kind_constructor_compilation_data compilation_data;
    #endif

    /* I: storing values at run-time */
    int short_block_size; /* if stored as a block value, size in words of the SB */
    int long_block_size; /* if stored as a block value, minimum number of LB fields */
    int flexible_long_block_size; /* if stored as a block value, typical number of LB fields */
    int can_exchange; /* with external files and therefore other story files */
    struct text_stream *distinguish_function; /* Inter routine to see if values distinguishable */
    struct kind_constructor_comparison_schema *first_comparison_schema; /* list of these */
    struct text_stream *loop_domain_schema; /* how to compile a loop over the instances */
    struct linked_list *instances; /* if enumerated explicitly in a Neptune file */

    /* J: printing and parsing values at run-time */
    struct text_stream *print_identifier; /* an Inter identifier used for compiling printing rules */
    struct text_stream *ACTIONS_identifier; /* ditto but for ACTIONS testing command */
    struct command_grammar *understand_as_values; /* used when parsing such values */
    struct text_stream *understand_function; /* routine name, when not compiled automatically */
    struct text_stream *recognise_function; /* for recognising an explicit value as preposition */

    /* K: pointer-value handling functions at run-time */
    struct text_stream *create_function;
    struct text_stream *cast_function;
    struct text_stream *copy_function;
    struct text_stream *copy_short_block_function;
    struct text_stream *quick_copy_function;
    struct text_stream *destroy_function;
    struct text_stream *make_mutable_function;
    struct text_stream *hash_function;
    struct text_stream *long_block_size_function;
    struct text_stream *serialise_function;
    struct text_stream *unserialise_function;
    struct linked_list *arithmetic_schemas[NO_DEFINED_OPERATION_VALUES]; /* of arithmetic_schema */
    int arithmetic_modulus;

    /* L: indexing and documentation */
    struct text_stream *specification_text; /* text for pseudo-property */
    struct text_stream *index_default_value; /* and its description in the Kinds index */
    struct text_stream *index_maximum_value; /* ditto */
    struct text_stream *index_minimum_value; /* ditto */
    int index_priority; /* from 1 (highest) to LOWEST_INDEX_PRIORITY (lowest) */
    int linguistic; /* divide off as having linguistics content */
    int indexed_grey_if_empty; /* shaded grey in the Kinds index */
    struct text_stream *documentation_reference; /* documentation symbol, if any */

    CLASS_DEFINITION
} kind_constructor;

§3. A few of the settings connect pairs of kinds together, so structures like the following are also needed.

typedef struct kind_constructor_casting_rule {
    struct text_stream *cast_from_kind_unparsed; /* to the one which has the rule */
    struct kind_constructor *cast_from_kind; /* to the one which has the rule */
    struct kind_constructor_casting_rule *next_casting_rule;
} kind_constructor_casting_rule;

§4. And this is the analogous structure for recording conformance:

typedef struct kind_constructor_instance_rule {
    struct text_stream *instance_of_this_unparsed;
    struct kind_constructor *instance_of_this;
    struct kind_constructor_instance_rule *next_instance_rule;
} kind_constructor_instance_rule;

§5. And this is the analogous structure for giving Inter schemas to compare data of two different kinds:

typedef struct kind_constructor_comparison_schema {
    struct text_stream *comparator_unparsed;
    struct kind_constructor *comparator;
    struct text_stream *comparison_schema;
    struct kind_constructor_comparison_schema *next_comparison_schema;
} kind_constructor_comparison_schema;

§6. And this is where explicit instances are recorded:

typedef struct kind_constructor_instance {
    struct text_stream *natural_language_name;
    struct text_stream *identifier;
    int value;
    int value_specified;
} kind_constructor_instance;

§7. The "tupling" of an argument is the extent to which an argument can be allowed to hold a variable-length list of kinds, rather than a single one. There aren't actually many possibilities.

define NO_TUPLING 0 /* a single kind */
define ALLOW_NOTHING_TUPLING 1 /* a single kind, or "nothing" */
define ARBITRARY_TUPLING 10000 /* a list of kinds of any length */

§8. Constant compilation modes are:

define NONE_CCM 1 /* constant values of this kind cannot exist */
define LITERAL_CCM 2 /* a numerical annotation decides the value */
define NAMED_CONSTANT_CCM 3 /* an instance annotation decides the value */
define SPECIAL_CCM 4 /* special code specific to the kind of value is needed */

§9. We keep track of the newest-created base kind of value (which isn't a kind of object) here:

kind *latest_base_kind_of_value = NULL;

§10. Arithmetic schemas record:

typedef struct arithmetic_schema {
    struct text_stream *operands_unparsed[2];
    struct kind_constructor *operands[2];
    struct text_stream *schema;
    CLASS_DEFINITION
} arithmetic_schema;

§11. Creation. Constructors come from two sources. Built-in ones like number or list of K come from commands in Neptune Files, while source-created ones ("Air pressure is a kind of value") result in calls here from Kinds::new_base -- which, as the name suggests, can only make base kinds, not proper constructors.

Here super will be the super-constructor, the one which this will construct subkinds of. In practice this will be NULL when CON_VALUE is created, and then CON_VALUE for kinds like "number" or this one:

Weight is a kind of value.

but will be the constructor for "door" for kinds like this one:

Portal is a kind of door.

kind_constructor *KindConstructors::new(kind_constructor *super,
    text_stream *source_name, text_stream *initialisation_macro, int group) {
    kind_constructor *con = CREATE(kind_constructor);
    kind_constructor **pC = FamiliarKinds::known_con(source_name);
    if (pC) *pC = con;

    int copied = FALSE;
    if (super == Kinds::get_construct(K_value)) Fill in a new constructor11.1
    else { Copy the new constructor from its superconstructor11.2; copied = TRUE; }
    con->group = group;

    con->explicit_identifier = Str::duplicate(source_name);
    #ifdef CORE_MODULE
    con->compilation_data = RTKindConstructors::new_compilation_data(con);
    KindSubjects::new(con);
    #endif
    con->where_defined_in_source_text = current_sentence;

    kind **pK = FamiliarKinds::known_kind(source_name);
    if (pK) *pK = Kinds::base_construction(con);
    return con;
}

§11.1. If our new constructor is wholly new, and isn't a subkind of something else, we need to initialise the entire data structure; but note that, having done so, we apply any defaults set in Neptune files.

default LOWEST_INDEX_PRIORITY 100

Fill in a new constructor11.1 =

    con->dt_tag = NULL;
    con->group = 0; /* which is invalid, so the interpreter needs to set it */

    /* A: how this came into being */
    con->is_incompletely_defined = FALSE;
    con->where_defined_in_source_text = NULL; /* but will be filled in imminently */

    /* B: constructing kinds */
    con->constructor_arity = 0; /* by default a base constructor */
    for (int i=0; i<MAX_KIND_CONSTRUCTION_ARITY; i++) {
        con->variance[i] = COVARIANT;
        con->tupling[i] = NO_TUPLING;
    }
    con->cached_kind = NULL;

    /* C: compatibility with other kinds */
    con->superkind_set_at = NULL;
    con->first_casting_rule = NULL;
    con->first_instance_rule = NULL;

    /* D: how constant values of this kind are expressed */
    con->ways_to_write_literals = NULL;
    con->named_values_created_with_table = NULL;
    con->next_free_value = 1;
    con->constant_compilation_method = NONE_CCM;
    con->forbid_assertion_creation = FALSE;

    /* E: knowledge about values of this kind */
    con->base_as_infs = NULL; /* but will be filled in imminently, in almost all cases */
    con->default_value = Str::new();

    /* F: behaviour as a property as well */
    con->can_coincide_with_property = FALSE;
    con->coinciding_property = NULL;

    /* G: performing arithmetic */
    con->dimensionless = TRUE;
    con->comparison_routine = Str::new();
    WRITE_TO(con->comparison_routine, "UnsignedCompare");
    if ((con == CON_KIND_VARIABLE) || (con == CON_INTERMEDIATE) ||
        ((Str::eq_wide_string(source_name, U"NUMBER_TY")) ||
            (Str::eq_wide_string(source_name, U"REAL_NUMBER_TY"))))
        con->dimensional_form =
            Kinds::Dimensions::fundamental_unit_sequence(NULL);
    else
        con->dimensional_form =
            Kinds::Dimensions::fundamental_unit_sequence(Kinds::base_construction(con));
    con->dimensional_form_fixed = FALSE;
    Kinds::Dimensions::dim_initialise(&(con->dim_rules));
    con->relative_kind = NULL;

    /* H: representing this kind at run-time */
    con->explicit_identifier = Str::new();
    con->class_number = 0;

    /* I: storing values at run-time */
    con->long_block_size = 0;
    con->flexible_long_block_size = 0;
    con->short_block_size = 1;
    con->can_exchange = FALSE;
    con->first_comparison_schema = NULL;
    con->distinguish_function = NULL;
    con->loop_domain_schema = NULL;
    con->instances = NEW_LINKED_LIST(kind_constructor_instance);

    /* J: printing and parsing values at run-time */
    con->print_identifier = Str::new();
    con->ACTIONS_identifier = Str::new();

    con->understand_as_values = NULL;
    con->understand_function = NULL;
    con->recognise_function = NULL;

    /* K: pointer-value handling functions at run-time */
    con->create_function = NULL;
    con->cast_function = NULL;
    con->copy_function = NULL;
    con->copy_short_block_function = NULL;
    con->quick_copy_function = NULL;
    con->destroy_function = NULL;
    con->make_mutable_function = NULL;
    con->hash_function = NULL;
    con->long_block_size_function = NULL;
    con->serialise_function = NULL;
    con->unserialise_function = NULL;
    for (int op=0; op<NO_DEFINED_OPERATION_VALUES; op++)
        con->arithmetic_schemas[op] = NEW_LINKED_LIST(arithmetic_schema);
    con->arithmetic_modulus = 0;

    /* L: indexing and documentation */
    con->specification_text = NULL;
    con->index_default_value = I"--";
    con->index_maximum_value = I"--";
    con->index_minimum_value = I"--";
    con->index_priority = LOWEST_INDEX_PRIORITY;
    if ((group == PUNCTUATION_GRP) || (group == PROTOCOL_GRP))
        con->index_priority = 0;
    con->linguistic = FALSE;
    con->indexed_grey_if_empty = FALSE;
    con->documentation_reference = NULL;

    kind_macro_definition *set_defaults = NULL;
    switch (group) {
        case PUNCTUATION_GRP: set_defaults = NeptuneMacros::parse_name(I"#PUNCTUATION"); break;
        case PROTOCOL_GRP: set_defaults = NeptuneMacros::parse_name(I"#PROTOCOL"); break;
        case BASE_CONSTRUCTOR_GRP: set_defaults = NeptuneMacros::parse_name(I"#BASE"); break;
        case PROPER_CONSTRUCTOR_GRP: set_defaults = NeptuneMacros::parse_name(I"#CONSTRUCTOR"); break;
    }
    if (set_defaults) NeptuneMacros::play_back(set_defaults, con, NULL);

    if (Str::len(initialisation_macro) > 0)
        NeptuneMacros::play_back(NeptuneMacros::parse_name(initialisation_macro), con, NULL);

§11.2. However, if we create our constructor as a subkind, like so:

A turtle is a kind of animal.

then we copy the entire "animal" constructor to initialise the "turtle" one.

Note that the weak ID number is one of the things copied; this is deliberate. It means that all kinds of object share the same weak ID as "object".

Copy the new constructor from its superconstructor11.2 =

    int I = con->allocation_id;
    void *N = con->next_structure;
    void *P = con->prev_structure;
    *con = *super;
    con->allocation_id = I;
    con->next_structure = N;
    con->prev_structure = P;
    con->cached_kind = NULL; /* otherwise the superkind's cache is used by mistake */
    con->explicit_identifier = Str::new(); /* otherwise this will be called OBJECT_TY by mistake */

§12. The noun. It's a requirement that the following be called soon after the creation of the constructor:

void KindConstructors::attach_noun(kind_constructor *con, noun *nt) {
    if ((con == NULL) || (nt == NULL)) internal_error("bad noun attachment");
    con->dt_tag = nt;
}

wording KindConstructors::get_name(kind_constructor *con, int plural_form) {
    if (con->dt_tag) {
        noun *nt = con->dt_tag;
        if (nt) return Nouns::nominative(nt, plural_form);
    }
    return EMPTY_WORDING;
}

wording KindConstructors::get_name_in_play(kind_constructor *con, int plural_form,
    NATURAL_LANGUAGE_WORDS_TYPE *nl) {
    if (con->dt_tag) {
        noun *nt = con->dt_tag;
        if (nt) return Nouns::nominative_in_language(nt, plural_form, nl);
    }
    return EMPTY_WORDING;
}

noun *KindConstructors::get_noun(kind_constructor *con) {
    if (con == NULL) return NULL;
    return con->dt_tag;
}

text_stream *KindConstructors::name_in_template_code(kind_constructor *con) {
    return con->explicit_identifier;
}

§13. We also need to parse this, occasionally (if we needed this more than a small and bounded number of times we'd want a faster method, but we don't):

kind_constructor *KindConstructors::parse(text_stream *sn) {
    if (sn == NULL) return NULL;
    kind_constructor *con;
    LOOP_OVER(con, kind_constructor)
        if (Str::eq(sn, con->explicit_identifier))
            return con;
    return NULL;
}

§14. Transformations. Conversions of an existing constructor to make it a unit or enumeration also require running macros in the kind interpreter:

int KindConstructors::convert_to_unit(kind_constructor *con) {
    if (con->is_incompletely_defined == TRUE) {
        NeptuneMacros::play_back(NeptuneMacros::parse_name(I"#UNIT"), con, NULL);
        return TRUE;
    }
    if (KindConstructors::is_arithmetic(con)) return TRUE; /* i.e., if it succeeded */
    return FALSE;
}

int KindConstructors::convert_to_enumeration(kind_constructor *con) {
    if (con->is_incompletely_defined == TRUE) {
        NeptuneMacros::play_back(NeptuneMacros::parse_name(I"#ENUMERATION"), con, NULL);
        if (con->linguistic)
            NeptuneMacros::play_back(NeptuneMacros::parse_name(I"#LINGUISTIC"), con, NULL);
        return TRUE;
    }
    if (KindConstructors::is_an_enumeration(con)) return TRUE; /* i.e., if it succeeded */
    return FALSE;
}

§15. And similarly:

void KindConstructors::convert_to_real(kind_constructor *con) {
    NeptuneMacros::play_back(NeptuneMacros::parse_name(I"#REAL"), con, NULL);
}

§16. A few base kinds are marked as "linguistic", which simply enables us to fence them tidily off in the index.

void KindConstructors::mark_as_linguistic(kind_constructor *con) {
    con->linguistic = TRUE;
}

§17. For construction purposes.

kind **KindConstructors::cache_location(kind_constructor *con) {
    if (con) return &(con->cached_kind);
    return NULL;
}

int KindConstructors::arity(kind_constructor *con) {
    if (con == NULL) return 0;
    if (con->group == PROPER_CONSTRUCTOR_GRP) return con->constructor_arity;
    return 0;
}

int KindConstructors::tupling(kind_constructor *con, int b) {
    return con->tupling[b];
}

int KindConstructors::variance(kind_constructor *con, int b) {
    return con->variance[b];
}

int KindConstructors::is_base(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if (con->group == BASE_CONSTRUCTOR_GRP) return TRUE;
    return FALSE;
}

int KindConstructors::is_proper_constructor(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if (con->group == PROPER_CONSTRUCTOR_GRP) return TRUE;
    return FALSE;
}

§18. Questions about constructors. The rest of Inform is not encouraged to poke at constructors directly; it ought to ask questions about kinds instead (see "Using Kinds"). However:

int KindConstructors::is_definite(kind_constructor *con) {
    if ((con->group == BASE_CONSTRUCTOR_GRP) ||
        (con->group == PROPER_CONSTRUCTOR_GRP))
            return TRUE;
    if ((con == CON_VOID) || (con == CON_NIL) || (con == CON_INTERMEDIATE))
        return TRUE;
    return FALSE;
}

int KindConstructors::is_understandable(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if ((KindConstructors::is_definite(con)) &&
        (KindConstructors::compatible(con,
            Kinds::get_construct(K_understandable_value), FALSE))) return TRUE;
    return FALSE;
}

int KindConstructors::is_arithmetic(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if ((KindConstructors::is_definite(con)) &&
        (KindConstructors::compatible(con,
            Kinds::get_construct(K_arithmetic_value), FALSE))) return TRUE;
    return FALSE;
}

int KindConstructors::is_arithmetic_and_real(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if ((KindConstructors::is_definite(con)) &&
        (KindConstructors::compatible(con,
            Kinds::get_construct(K_real_arithmetic_value), FALSE))) return TRUE;
    return FALSE;
}

int KindConstructors::is_an_enumeration(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if ((KindConstructors::is_definite(con)) &&
        (KindConstructors::compatible(con,
            Kinds::get_construct(K_enumerated_value), FALSE))) return TRUE;
    return FALSE;
}

§19. All floating-point kinds use a common comparison function: the one for K_real_number.

int KindConstructors::uses_signed_comparisons(kind_constructor *kc) {
    if (kc == NULL) return FALSE;
    if (Str::eq_wide_string(kc->comparison_routine, U"signed")) return TRUE;
    return FALSE;
}

text_stream *KindConstructors::get_comparison_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    if ((KindConstructors::is_arithmetic_and_real(kc)) && (K_real_number))
        return K_real_number->construct->comparison_routine;
    if (Str::eq_wide_string(kc->comparison_routine, U"signed")) return NULL;
    return kc->comparison_routine;
}

§20. Cast and instance lists. Each constructor has a list of other constructors (all of the PROTOCOL_GRP group) which it's an instance of: value, word value, arithmetic value, and so on.

int KindConstructors::find_cast(kind_constructor *from, kind_constructor *to) {
    if (to) {
        kind_constructor_casting_rule *dtcr;
        for (dtcr = to->first_casting_rule; dtcr; dtcr = dtcr->next_casting_rule) {
            if (Str::len(dtcr->cast_from_kind_unparsed) > 0) {
                dtcr->cast_from_kind =
                    KindConstructors::parse(dtcr->cast_from_kind_unparsed);
                Str::clear(dtcr->cast_from_kind_unparsed);
            }
            if (from == dtcr->cast_from_kind)
                return TRUE;
        }
    }
    return FALSE;
}

§21. Each constructor has a list of other constructors (all of the BASE_CONSTRUCTOR_GRP group or PROPER_CONSTRUCTOR_GRP) which it can cast to.

int KindConstructors::find_instance(kind_constructor *from, kind_constructor *to) {
    kind_constructor_instance_rule *dti;
    for (dti = from->first_instance_rule; dti; dti = dti->next_instance_rule) {
        if (Str::len(dti->instance_of_this_unparsed) > 0) {
            dti->instance_of_this =
                KindConstructors::parse(dti->instance_of_this_unparsed);
            Str::clear(dti->instance_of_this_unparsed);
        }
        if (dti->instance_of_this == to) return TRUE;
        if (KindConstructors::find_instance(dti->instance_of_this, to)) return TRUE;
    }
    return FALSE;
}

§22. Each constructor has a list of explicitly-named instances from the Neptune file creating it (if any were: by default this will be empty):

linked_list *KindConstructors::instances(kind_constructor *kc) {
    if (kc == NULL) return FALSE;
    return kc->instances;
}

§23. Tedious access functions.

text_stream *KindConstructors::get_create_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->create_function;
}

text_stream *KindConstructors::get_cast_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->cast_function;
}

text_stream *KindConstructors::get_copy_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->copy_function;
}

text_stream *KindConstructors::get_copy_short_block_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->copy_short_block_function;
}

text_stream *KindConstructors::get_quick_copy_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->quick_copy_function;
}

text_stream *KindConstructors::get_destroy_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->destroy_function;
}

text_stream *KindConstructors::get_make_mutable_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->make_mutable_function;
}

text_stream *KindConstructors::get_hash_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->hash_function;
}

text_stream *KindConstructors::get_long_block_size_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->long_block_size_function;
}

text_stream *KindConstructors::get_serialise_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->serialise_function;
}

text_stream *KindConstructors::get_unserialise_fn_identifier(kind_constructor *kc) {
    if (kc == NULL) return NULL;
    return kc->unserialise_function;
}

text_stream *KindConstructors::get_arithmetic_schema(int op,
    kind_constructor *kc1, kind_constructor *kc2) {
    if ((op < 0) || (op >= NO_DEFINED_OPERATION_VALUES)) return NULL;
    text_stream *candidate;
    candidate = KindConstructors::get_arithmetic_schema_from(kc1, op, kc1, kc2);
    if (Str::len(candidate) > 0) return candidate;
    candidate = KindConstructors::get_arithmetic_schema_from(kc2, op, kc1, kc2);
    if (Str::len(candidate) > 0) return candidate;
    return NULL;
}

text_stream *KindConstructors::get_arithmetic_schema_from(kind_constructor *from,
    int op, kind_constructor *kc1, kind_constructor *kc2) {
    if (from == NULL) return NULL;
    arithmetic_schema *ars;
    LOOP_OVER_LINKED_LIST(ars, arithmetic_schema, from->arithmetic_schemas[op]) {
        for (int i=0; i<2; i++) {
            if (Str::len(ars->operands_unparsed[i]) > 0) {
                ars->operands[i] = KindConstructors::parse(ars->operands_unparsed[i]);
                Str::clear(ars->operands_unparsed[i]);
            }
        }
        if (((kc1 == ars->operands[0]) || ((kc1 == from) && (ars->operands[0] == NULL))) &&
            ((kc2 == ars->operands[1]) || ((kc2 == from) && (ars->operands[1] == NULL))))
            return ars->schema;
    }
    return NULL;
}

int KindConstructors::get_arithmetic_modulus(kind_constructor *kc) {
    if (kc == NULL) return 0;
    return kc->arithmetic_modulus;
}

int KindConstructors::is_dimensionless(kind_constructor *kc) {
    if (kc == NULL) return TRUE;
    return kc->dimensionless;
}

§24. Compatibility. The following tests if from is compatible with to.

int KindConstructors::compatible(kind_constructor *from, kind_constructor *to,
    int allow_casts) {
    if (to == from) return TRUE;
    if ((to == NULL) || (from == NULL)) return FALSE;
    if ((allow_casts) && (KindConstructors::find_cast(from, to))) return TRUE;
    if (KindConstructors::find_instance(from, to)) return TRUE;
    return FALSE;
}

§25. And more elaborately:

int KindConstructors::uses_block_values(kind_constructor *con) {
    if (con == NULL) return FALSE;
    if ((KindConstructors::is_definite(con)) &&
        (KindConstructors::compatible(con, Kinds::get_construct(K_pointer_value), FALSE)))
            return TRUE;
    return FALSE;
}

int KindConstructors::allow_word_as_pointer(kind_constructor *left,
    kind_constructor *right) {
    if (KindConstructors::uses_block_values(left) == FALSE) return FALSE;
    if (KindConstructors::uses_block_values(right) == TRUE) return FALSE;
    if (KindConstructors::compatible(right, left, TRUE)) return TRUE;
    return FALSE;
}