Translating kinds to and from textual descriptions.


§1. The K-grammar. This is a Preform grammar for textual descriptions of kinds. In effect it's a mini-language of its own, with a specification closer to traditional computer-science norms than Inform's free-wheeling approach — this is not an accident. It allows for awkward functional-programming needs in a way which vaguer natural language syntax would not.

All K-grammar nonterminals begin with the "k-" prefix, and their pointer results are to kind structures.

§2. For speed, we parse some kind names as single words, and others as common nouns, which is slower:

define KIND_SLOW_MC   0x00000008  e.g., weight
define KIND_FAST_MC   0x01000000  number, text, relation, rule, ...

§3. The K-grammar actually has two modes: normal, and phrase-token-mode. Normal mode is aptly named: it's almost always the one we're using. Phrase token mode is used only when parsing definitions of phrases, like so:

To repeat with (LV - nonexisting K variable) running from (V1 - arithmetic value of kind K) to (V2 - K)

Here the tokens "nonexisting K variable" and so on are parsed as specifications, but in such a way that any kinds mentioned are parsed in phrase-token-mode. The difference is that this enables them to refer to kind variables such as K which are still being defined; in normal mode, that would only be allowed if K already existed.

define NORMAL_KIND_PARSING 1
define PHRASE_TOKEN_KIND_PARSING 2
int kind_parsing_mode = NORMAL_KIND_PARSING;

§4. This tests which mode we are in, consuming no words:

<if-parsing-phrase-tokens> internal 0 {
    if (kind_parsing_mode != NORMAL_KIND_PARSING) return TRUE;
    ==> { fail nonterminal };
}

§5. And the following internal is in fact only <k-kind> but inside phrase token parsing mode; it's used when parsing the kind to be decided by a phrase (which, like phrase tokens, can involve the variables).

<k-kind-prototype> internal {
    int s = kind_parsing_mode;
    kind_parsing_mode = PHRASE_TOKEN_KIND_PARSING;
    int t = <k-kind>(W);
    kind_parsing_mode = s;
    if (t) { ==> { -, <<rp>> }; return TRUE; }
    ==> { fail nonterminal };
}

§6. And here is that "name of kind..." construction, which is valid only in phrase tokens.

<k-kind-as-name-token> ::=
    ( <k-kind-as-name-token> ) |             ==> { pass 1 }
    name of kind of <k-kind-abbreviating> |  ==> { pass 1 }
    name of kind <k-kind-abbreviating> |     ==> { pass 1 }
    name of kind of ... |                    ==> { -, NULL }
    name of kind ...                         ==> { -, NULL }

<k-kind-abbreviating> ::=
    ( <k-kind-abbreviating> ) |              ==> { pass 1 }
    <k-kind-of-kind> <k-formal-variable> |   ==> { -, Kinds::var_construction(R[2], RP[1]) }
    <k-kind>                                 ==> { pass 1 }

§7. So now we can begin properly. Every valid kind matches <k-kind>:

<k-kind> ::=
    ( <k-kind> ) |                                        ==> { pass 1 }
    ^<if-parsing-phrase-tokens> <k-kind-variable> |       ==> { pass 2 }
    <if-parsing-phrase-tokens> <k-variable-definition> |  ==> { pass 2 }
    <k-base-kind> |                                       ==> { pass 1 }
    <k-irregular-kind-construction> |                     ==> { pass 1 }
    <k-kind-construction>                                 ==> { pass 1 }

§8. And, as a convenient shorthand:

<k-kind-articled> ::=
    <indefinite-article> <k-kind> |  ==> { pass 2 }
    <k-kind>                         ==> { pass 1 }

§9. In phrase-token mode, kind variables are treated as formal symbols, not as the kinds which are their current values:

<k-variable-definition> ::=
    <k-formal-variable> |                         ==> { pass 1 }
    <k-kind-of-kind> of kind <k-formal-variable>  ==> { -, Kinds::var_construction(R[2], RP[1]) }

§10. Some base kinds with one-word names have that word flagged with a direct pointer to the kind, for speed of parsing. Names of base kinds, such as number or vehicle, can be registered in two different ways (according to whether they come from the source text or from template files), so we then make two further checks:

<k-base-kind> internal {
    kind *K = NULL;
    if (Wordings::empty(W)) { ==> { fail nonterminal }; }
    if (Wordings::length(W) == 1)
        K = Kinds::read_kind_marking_from_vocabulary(Lexer::word(Wordings::first_wn(W)));
    if (K == NULL) {
        if (<definite-article>(Wordings::first_word(W))) { ==> { fail nonterminal }; }
        parse_node *p = Lexicon::retrieve(KIND_SLOW_MC, W);
        if (p) {
            excerpt_meaning *em = Node::get_meaning(p);
            general_pointer m = Lexicon::get_data(em);
            if (m.run_time_type_code == noun_usage_CLASS) {
                noun_usage *nu = RETRIEVE_POINTER_noun_usage(m);
                m = nu->noun_used->meaning;
            }
            K = Kinds::base_construction(RETRIEVE_POINTER_kind_constructor(m));
        } else {
            p = Lexicon::retrieve(NOUN_MC, W);
            if (p) {
                noun_usage *nu = Nouns::disambiguate(p, TRUE);
                noun *nt = (nu)?(nu->noun_used):NULL;
                if (nt) K = Kinds::base_construction(
                    RETRIEVE_POINTER_kind_constructor(Nouns::meaning(nt)));
            }
        }
    }
    if (K) { ==> { -, K }; return TRUE; }
    ==> { fail nonterminal };
}

§11. "Object based rulebook" has been on a voyage of unhyphenation: in the early public beta of Inform 7, it was "object-based-rulebook" (at that time, built-in kinds had to have one-word names); then it became "object-based rulebook", when one-word adjectives were allowed to modify the names of built-in kinds; and now it is preferably "object based rulebook". But the previous syntax is permitted as an alias to keep old source text working. And similarly for the others here, except "either-or property", which is a 2010 addition.

<k-irregular-kind-construction> ::=
    indexed text |                                                   ==> { -, K_text }
    indexed texts |                                                  ==> { -, K_text }
    stored action |                                                  ==> Stored action11.1
    stored actions |                                                 ==> Stored action11.1
    object-based rulebook producing <indefinite-article> <k-kind> |  ==> Rulebook obj on 211.2
    object-based rulebook producing <k-kind> |                       ==> Rulebook obj on 111.3
    object-based rulebook |                                          ==> Rulebook obj on void11.4
    action-based rulebook |                                          ==> Action rulebook11.5
    object-based rule producing <indefinite-article> <k-kind> |      ==> Rule obj on 211.6
    object-based rule producing <k-kind> |                           ==> Rule obj on 111.7
    object-based rule |                                              ==> Rule obj on void11.8
    action-based rule |                                              ==> Action rule11.9
    either-or property                                               ==> Property on truth state11.10

§11.1. Stored action11.1 =

    #ifdef IF_MODULE
    if (K_stored_action == NULL) { ==> { fail production }; }
    ==> { -, K_stored_action };
    #endif
    #ifndef IF_MODULE
    ==> { fail production };
    #endif

§11.2. Rulebook obj on 211.2 =

    ==> { -, Kinds::binary_con(CON_rulebook, K_object, RP[2]) }

§11.3. Rulebook obj on 111.3 =

    ==> { -, Kinds::binary_con(CON_rulebook, K_object, RP[1]) }

§11.4. Rulebook obj on void11.4 =

    ==> { -, Kinds::binary_con(CON_rulebook, K_object, K_void) }

§11.5. Action rulebook11.5 =

    #ifdef IF_MODULE
    if (K_action_name == NULL) { ==> { fail production }; }
    ==> { -, Kinds::binary_con(CON_rulebook, K_action_name, K_void) };
    #endif
    #ifndef IF_MODULE
    ==> { fail production };
    #endif

§11.6. Rule obj on 211.6 =

    ==> { -, Kinds::binary_con(CON_rule, K_object, RP[2]) }

§11.7. Rule obj on 111.7 =

    ==> { -, Kinds::binary_con(CON_rule, K_object, RP[1]) }

§11.8. Rule obj on void11.8 =

    ==> { -, Kinds::binary_con(CON_rule, K_object, K_void) }

§11.9. Action rule11.9 =

    #ifdef IF_MODULE
    if (K_action_name == NULL) { ==> { fail production }; }
    ==> { -, Kinds::binary_con(CON_rule, K_action_name, K_void) };
    #endif
    #ifndef IF_MODULE
    ==> { fail production };
    #endif

§11.10. Property on truth state11.10 =

    ==> { -, Kinds::unary_con(CON_property, K_truth_state) }

§12. This loop looks a little slow, but there are only about 10 proper constructors.

<k-kind-construction> internal {
    kind_constructor *con;
    LOOP_OVER(con, kind_constructor)
        if (KindConstructors::arity(con) > 0) {
            wording X = W;
            wording Y = EMPTY_WORDING;
            if (Kinds::Textual::parse_constructor_name(con, &X, &Y))
                See if this partial kind-constructor match works out12.1;
        }
    ==> { fail nonterminal };
}

§12.1. So at this point we have a match of the fixed words in the constructor. For example, we might have "list of ...", where the X excerpt represents the "..." and the Y excerpt is empty; or "... based rule producing ...", where both X and Y are non-empty; or even something like "relation", where both X and Y are empty.

We try to match X and Y against kinds, filling in defaults if either is unspecified because a short form of the constructor is used (e.g., "relation" instead of "relation of ..." or "relation of ... to ...").

See if this partial kind-constructor match works out12.1 =

    kind *KX = K_value, *KY = K_value;
    if (con->variance[0] == CONTRAVARIANT)
        KX = K_nil;
    if ((KindConstructors::arity(con) == 2) && (con->variance[1] == CONTRAVARIANT))
        KY = K_nil;

    The rule and rulebook constructors default to actions for X12.1.1;
    if (Wordings::nonempty(X)) {
        int tupling = KindConstructors::tupling(con, 0);
        if ((tupling == 0) && (<k-single-term>(X))) KX = <<rp>>;
        else if ((tupling == 1) && (<k-optional-term>(X))) KX = <<rp>>;
        else if ((tupling >= 2) && (<k-tupled-term>(X))) KX = <<rp>>;
        else KX = NULL;
    }
    if (Wordings::nonempty(Y)) {
        int tupling = KindConstructors::tupling(con, 1);
        if ((tupling == 0) && (<k-single-term>(Y))) KY = <<rp>>;
        else if ((tupling == 1) && (<k-optional-term>(Y))) KY = <<rp>>;
        else if ((tupling >= 2) && (<k-tupled-term>(Y))) KY = <<rp>>;
        else KY = NULL;
    }
    The relation constructor defaults to Y matching X, if X is specified12.1.2;

    if ((KindConstructors::arity(con) == 1) && (KX)) {
        ==> { -, Kinds::unary_con(con, KX) }; return TRUE;
    }
    if ((KindConstructors::arity(con) == 2) && (KX) && (KY)) {
        ==> { -, Kinds::binary_con(con, KX, KY) }; return TRUE;
    }

§12.1.1. Ordinarily missing X or Y are filled in as "value", but...

The rule and rulebook constructors default to actions for X12.1.1 =

    if ((con == CON_rule) || (con == CON_rulebook)) {
        #ifdef IF_MODULE
        if (K_action_name) KX = K_action_name; else KX = K_void;
        #endif
        #ifndef IF_MODULE
        KX = K_void;
        #endif
        KY = K_void;
    }

§12.1.2. And...

The relation constructor defaults to Y matching X, if X is specified12.1.2 =

    if ((con == CON_relation) && (Wordings::empty(Y))) KY = KX;

§13. Where the materials used in construction are not quite kinds, but can be more varied.

<k-single-term> ::=
    ( <k-single-term> ) |              ==> { pass 1 }
    <article> <k-single-term> |        ==> { pass 2 }
    <k-kind>                           ==> { pass 1 }

<k-optional-term> ::=
    ( <k-optional-term> ) |            ==> { pass 1 }
    <article> <k-optional-term> |      ==> { pass 2 }
    nothing |                          ==> { -, K_nil }
    action |                           ==> The action term13.1
    <k-kind>                           ==> { pass 1 }

<k-tupled-term> ::=
    ( <k-tuple-list> ) |               ==> { pass 1 }
    nothing |                          ==> { -, K_void }
    <k-single-term>                    ==> { -, Kinds::binary_con(CON_TUPLE_ENTRY, RP[1], K_void) }

<k-tuple-list> ::=
    <k-single-term> , <k-tuple-list> | ==> { -, Kinds::binary_con(CON_TUPLE_ENTRY, RP[1], RP[2]) }
    <k-single-term>                    ==> { -, Kinds::binary_con(CON_TUPLE_ENTRY, RP[1], K_void) }

§13.1. The action term13.1 =

    #ifdef IF_MODULE
    ==> { -, K_action_name };
    #endif
    #ifndef IF_MODULE
    ==> { fail production };
    #endif

§14. The following looks at a word range and tries to find text making a kind construction: if it does, it adjusts the word ranges to the kind(s) being constructed on, and returns TRUE; if it fails, it returns FALSE. For instance, given "list of marbles", it adjusts the word range to "marbles" and returns TRUE.

int Kinds::Textual::parse_constructor_name(kind_constructor *con, wording *KW, wording *LW) {
    wording W = *KW;
    for (int p=1; p<=2; p++) {
        wording NW = KindConstructors::get_name(con, (p==1)?FALSE:TRUE);
        if (Wordings::nonempty(NW)) {
            int full_length = Wordings::length(NW);
            int k1 = Wordings::first_wn(NW);
            *KW = EMPTY_WORDING; *LW = EMPTY_WORDING;
            Try this as a constructor name14.1;
        }
    }
    *KW = EMPTY_WORDING; *LW = EMPTY_WORDING;
    return FALSE;
}

§14.1. Note that the name text for a constructor is likely to have a form like so:

    relation STROKE relation of k to l STROKE relation of k

with multiple possibilities divided by strokes; each possibility must be checked for.

Try this as a constructor name14.1 =

    int base = 0, length = full_length;
    int k;
    for (k=0; k<full_length; k++)
        if (Lexer::word(k1+k) == STROKE_V) {
            length = k - base;
            if (length > 0) Try one option among the constructor's names14.1.1;
            base = k+1;
        }
    length = full_length - base;
    if (length > 0) Try one option among the constructor's names14.1.1;

§14.1.1. Try one option among the constructor's names14.1.1 =

    if (Wordings::length(W) >= length) {
        int i, p, failed = FALSE;
        for (i=0, p=Wordings::first_wn(W); i<length; i++) {
            vocabulary_entry *ve = Lexer::word(k1+base+i);
            if ((ve == CAPITAL_K_V) || (ve == CAPITAL_L_V)) {
                int from = p;
                if (i == length-1) { p = Wordings::last_wn(W)+1; }
                else {
                    int bl = 0;
                    while (p <= Wordings::last_wn(W)) {
                        vocabulary_entry *nw = Lexer::word(p);
                        if (nw == OPENBRACKET_V) bl++;
                        else if (nw == CLOSEBRACKET_V) bl--;
                        else if (bl == 0) {
                            if (nw == Lexer::word(k1+base+i+1)) break;
                        }
                        p++;
                    }
                    if (p > Wordings::last_wn(W)) { failed = TRUE; break; }
                }
                if (ve == CAPITAL_K_V) { *KW = Wordings::new(from, p-1); }
                else { *LW = Wordings::new(from, p-1); }
            } else {
                if (ve != Lexer::word(p++)) { failed = TRUE; break; }
            }
        }
        if (p != Wordings::last_wn(W)+1) failed = TRUE;
        if (failed == FALSE) return TRUE;
    }

§15. Kinds of kind. This is actually just <k-kind> in disguise, but only lets the result through if it's a kind of kind, like "arithmetic value"; something like "number" or "list of texts" will fail.

<k-kind-of-kind> ::=
    <k-kind>        ==> { pass 1 }; if (Kinds::Behaviour::is_kind_of_kind(RP[1]) == FALSE) return FALSE;

§16. Parsing kind variables. As a small detour, here's how we deal with the pleasingly simple names A to Z for kind variables, converting them to and from the numbers 1 to 26:

int Kinds::Textual::parse_variable(vocabulary_entry *ve) {
    if (ve == NULL) return 0;
    return Kinds::Textual::parse_kind_variable_name(
        Vocabulary::get_exemplar(ve, TRUE), TRUE);
}

int Kinds::Textual::parse_kind_variable_name(inchar32_t *p, int allow_lower) {
    if (p == NULL) return 0;
    if ((p[1] == 0) || ((p[1] == 's') && (p[2] == 0))) {
        if ((p[0] >= 'A') && (p[0] <= 'Z')) return (int) (p[0] - 'A' + 1);
        if ((allow_lower) && (p[0] >= 'a') && (p[0] <= 'z')) return (int) (p[0] - 'a' + 1);
    }
    return 0;
}

int Kinds::Textual::parse_kind_variable_name_singular(inchar32_t *p) {
    if ((p) && (p[1] == 0) && (p[0] >= 'A') && (p[0] <= 'Z'))
        return (int) (p[0] - 'A' + 1);
    return 0;
}

§17. Kind variables are written with the letters A to Z. That provides for only 26 of them, but it's very, very rare to need more than 2, in practice.

The following nonterminal matches only those kind variables whose values are actually set, and it returns those values. This is how kind variables are parsed almost all of the time.

<k-kind-variable> internal 1 {
    int k = Kinds::Textual::parse_kind_variable_name(
        Lexer::word_raw_text(Wordings::first_wn(W)), FALSE);
    if (k != 0) {
        kind *K = Kinds::variable_from_context(k);
        if (K) { ==> { k, K }; return TRUE; }
    }
    ==> { fail nonterminal };
}

§18. But we can also formally parse A to Z as their own abstract identities; now they always parse, regardless of what might be stored in them, and they aren't replaced with their values (which they may not even have).

<k-formal-variable> internal 1 {
    int k = Kinds::Textual::parse_kind_variable_name(
        Lexer::word_raw_text(Wordings::first_wn(W)), FALSE);
    if (k != 0) {
        ==> { k, Kinds::var_construction(k, NULL) };
        return TRUE;
    }
    ==> { fail nonterminal };
}

§19. And it's also convenient to have:

<k-formal-variable-singular> internal 1 {
    int k = Kinds::Textual::parse_kind_variable_name_singular(
        Lexer::word_raw_text(Wordings::first_wn(W)));
    if (k != 0) {
        ==> { k, Kinds::var_construction(k, NULL) };
        return TRUE;
    }
    ==> { fail nonterminal };
}

§20. For efficiency's sake, we don't actually parse directly using this nonterminal, but it's needed all the same because of Preform's optimisations.

<k-kind-variable-texts> ::=
    a/as |
    b/bs |
    c/cs |
    d/ds |
    e/es |
    f/fs |
    g/gs |
    h/hs |
    i/is |
    j/js |
    k/ks |
    l/ls |
    m/ms |
    n/ns |
    o/os |
    p/ps |
    q/qs |
    r/rs |
    s/ss |
    t/ts |
    u/us |
    v/vs |
    w/ws |
    x/xs |
    y/ys |
    z/zs

§21. Textual descriptions. The following pretty-printer is inverse to the code which parses text and turns it into a kind structure, or very nearly so. We use common code to handle all of the reasons why we might want to spell out a kind in words — the log, the index, problem messages, comments in code, and so on. For example:

void Kinds::Textual::log(kind *K) {
    Kinds::Textual::write(DL, K);
}

void Kinds::Textual::logger(OUTPUT_STREAM, void *vK) {
    kind *K = (kind *) vK;
    Kinds::Textual::write(OUT, K);
}

void Kinds::Textual::writer(OUTPUT_STREAM, char *format_string, void *vK) {
    kind *K = (kind *) vK;
    Kinds::Textual::write(OUT, K);
}

§22. Thus we have a basic pretty-printer...

void Kinds::Textual::write(OUTPUT_STREAM, kind *K) {
    Kinds::Textual::write_inner(OUT, K, FALSE, FALSE);
}

void Kinds::Textual::write_plural(OUTPUT_STREAM, kind *K) {
    Kinds::Textual::write_inner(OUT, K, TRUE, FALSE);
}

§23. ...and also one which prefaces the kind name with an article.

void Kinds::Textual::write_articled(OUTPUT_STREAM, kind *K) {
    TEMPORARY_TEXT(TEMP)
    Kinds::Textual::write_inner(TEMP, K, FALSE, TRUE);
    ArticleInflection::preface_by_article(OUT, TEMP, DefaultLanguage::get(NULL));
    DISCARD_TEXT(TEMP)
}

§24. In all cases we make use of the following recursive method:

void Kinds::Textual::write_inner(OUTPUT_STREAM, kind *K, int plural_form, int substituting) {
    if (K == NULL) { WRITE("nothing"); return; }
    if (K == K_nil) { WRITE("nothing"); return; }
    if (K == K_void) { WRITE("nothing"); return; }
    kind_constructor *con = NULL;
    if (Kinds::is_proper_constructor(K)) con = Kinds::get_construct(K);
    Write punctuation kinds out to the stream24.1;
    if (con) Write constructor kinds out to the stream24.3
    else Write base kinds out to the stream24.2;
}

§24.1. Note that we do cheat in one case, in our resolve to have only a single written form for kinds in all contexts — we give a low-level description of an intermediate value in the debugging log. But such a thing can't be exposed higher up in Inform, so it's no loss to the index, problem messages, etc., to miss out on this detail.

Write punctuation kinds out to the stream24.1 =

    kind_constructor *con = Kinds::get_construct(K);
    if (con == CON_VOID) { WRITE("void"); return; }
    if (con == CON_NIL) { WRITE("nil"); return; }
    if (con == CON_TUPLE_ENTRY) { Describe a continuing tuple24.1.1; return; }
    if (con == CON_KIND_VARIABLE) { Describe a kind variable, either by name or by value24.1.2; return; }
    if (con == CON_INTERMEDIATE) {
        if (OUT == DL)
            LOG("$Q", Kinds::Behaviour::get_dimensional_form(K));
        else
            Kinds::Dimensions::index_unit_sequence(OUT,
                Kinds::Behaviour::get_dimensional_form(K), FALSE);
        return;
    }

§24.1.1. Describe a continuing tuple24.1.1 =

    kind *head = NULL, *tail = NULL;
    Kinds::binary_construction_material(K, &head, &tail);
    Kinds::Textual::write_inner(OUT, head, FALSE, substituting);
    if (Kinds::get_construct(tail) != CON_VOID) {
        WRITE(", ");
        Kinds::Textual::write_inner(OUT, tail, FALSE, substituting);
    }

§24.1.2. Describe a kind variable, either by name or by value24.1.2 =

    int vn = Kinds::get_variable_number(K);
    if ((substituting) && (vn > 0)) {
        kind *subst = Kinds::variable_from_context(vn);
        if (subst) { Kinds::Textual::write_inner(OUT, subst, plural_form, TRUE); return; }
    }
    WRITE("%c", 'A' + vn - 1);
    if (plural_form) WRITE("s");
    kind *S = Kinds::get_variable_stipulation(K);
    if ((S) && (Kinds::eq(S, K_value) == FALSE)) {
        WRITE(" ["); Kinds::Textual::write(OUT, S); WRITE("]"); }

§24.2. Write base kinds out to the stream24.2 =

    if (Kinds::eq(K, K_pointer_value)) WRITE("pointer value");
    else if (Kinds::eq(K, K_stored_value)) WRITE("stored value");
    else WRITE("%W", Kinds::Behaviour::get_name(K, plural_form));

§24.3. Write constructor kinds out to the stream24.3 =

    kind *first_base = NULL, *second_base = NULL;
    if (KindConstructors::arity(con) == 1)
        first_base = Kinds::unary_construction_material(K);
    else
        Kinds::binary_construction_material(K, &first_base, &second_base);
    Make a special case for either/or properties24.3.1;
    wording KW = Kinds::Behaviour::get_name(K, plural_form);
    int k1 = Wordings::first_wn(KW);
    int full_length = Wordings::length(KW);
    int from, to;
    Choose which form of the constructor to use when writing this out24.3.2;
    Actually write out the chosen form of the constructor24.3.3;

§24.3.1. Since Inform reads "either/or property" as syntactic sugar for "truth state valued property", we'll also write it: it's a much more familiar usage.

Make a special case for either/or properties24.3.1 =

    if ((con == CON_property) && (Kinds::eq(first_base, K_truth_state))) {
        WRITE("either/or property");
        return;
    }

§24.3.2. Choose which form of the constructor to use when writing this out24.3.2 =

    int k_present = 0, l_present = 0;  these initialisations have no effect but avoid warnings
    int choice_from[2][2], choice_to[2][2];
    Determine the possible forms for writing this constructor24.3.2.1;
    k_present = 1; l_present = 1;
    if ((con == CON_rule) || (con == CON_rulebook)) {
        #ifdef IF_MODULE
        if (Kinds::eq(first_base, K_action_name)) k_present = 0;
        #endif
    } else {
        if (Kinds::eq(first_base, K_nil)) k_present = 0;
        if (Kinds::eq(first_base, K_void)) k_present = 0;
    }
    if ((con == CON_property) && (Kinds::eq(first_base, K_value))) k_present = 0;
    if ((con == CON_table_column) && (Kinds::eq(first_base, K_value))) k_present = 0;
    if ((con == CON_relation) && (Kinds::eq(first_base, second_base))) l_present = 0;
    if (KindConstructors::arity(con) == 1) l_present = 0;
    else if (Kinds::eq(second_base, K_nil)) l_present = 0;
    else if (Kinds::eq(second_base, K_void)) l_present = 0;
    if (choice_from[k_present][l_present] == -1) {
        if ((k_present == 0) && (choice_from[1][l_present] >= 0)) k_present++;
        else if ((l_present == 0) && (choice_from[k_present][1] >= 0)) l_present++;
        else if ((k_present == 0) && (l_present == 0) && (choice_from[1][1] >= 0)) {
            k_present++; l_present++;
        }
    }
    from = choice_from[k_present][l_present];
    to = choice_to[k_present][l_present];
    if ((from < 0) || (from >= full_length) || (to < 0) || (to >= full_length) || (to<from)) {
        LOG("%W: %d, %d, %d\n", KW, from, to, full_length);
        internal_error("constructor form choice failed");
    }

§24.3.2.1. Determine the possible forms for writing this constructor24.3.2.1 =

    int from, i;
    choice_from[0][0] = -1; choice_from[0][1] = -1;
    choice_from[1][0] = -1; choice_from[1][1] = -1;
    for (i=0, from = -1; i<full_length; i++) {
        if (from == -1) { from = i; k_present = 0; l_present = 0; }
        if (Lexer::word(k1+i) == CAPITAL_K_V) k_present = 1;
        if (Lexer::word(k1+i) == CAPITAL_L_V) l_present = 1;
        if (Lexer::word(k1+i) == STROKE_V) End this constructor possibility24.3.2.1.1;
    }
    End this constructor possibility24.3.2.1.1;

§24.3.2.1.1. End this constructor possibility24.3.2.1.1 =

    choice_from[k_present][l_present] = from;
    choice_to[k_present][l_present] = i-1;
    from = -1;

§24.3.3. Actually write out the chosen form of the constructor24.3.3 =

    int i;
    for (i=from; i<=to; i++) {
        if (i > from) WRITE(" ");
        if (Lexer::word(k1+i) == CAPITAL_K_V)
            Kinds::Textual::desc_base(OUT, con, 0, first_base, substituting);
        else if (Lexer::word(k1+i) == CAPITAL_L_V)
            Kinds::Textual::desc_base(OUT, con, 1, second_base, substituting);
        else WRITE("%V", Lexer::word(k1+i));
    }

§25.

void Kinds::Textual::desc_base(OUTPUT_STREAM, kind_constructor *con,
    int b, kind *K, int substituting) {
    if (K == NULL) { WRITE("nothing"); return; }
    if (K == K_nil) { WRITE("nothing"); return; }
    if (K == K_void) { WRITE("nothing"); return; }
    int pluralised = TRUE;
    int tupled = KindConstructors::tupling(con, b);
    int bracketed = FALSE;
    if ((tupled > 1) && (Kinds::get_construct(K) == CON_TUPLE_ENTRY)) {
        kind *first_base = NULL, *second_base = NULL;
        Kinds::binary_construction_material(K, &first_base, &second_base);
        if ((second_base) && (Kinds::get_construct(second_base) == CON_TUPLE_ENTRY))
            bracketed = TRUE;
    }
    if ((b == 1) && (con == CON_phrase) && (Kinds::get_construct(K) == CON_phrase))
        bracketed = TRUE;
    if (bracketed) WRITE("(");
    if ((tupled > 1) || (con == CON_phrase)) pluralised = FALSE;
    Kinds::Textual::write_inner(OUT, K, pluralised, substituting);
    if (bracketed) WRITE(")");
}

§26. HTML. Amd this is used in indexing, for example.

void Kinds::Textual::write_as_HTML(OUTPUT_STREAM, kind *K) {
    if (K == NULL) return;
    wording W = Kinds::Behaviour::get_name(K, FALSE);
    if (Wordings::nonempty(W)) {
        if (Kinds::is_proper_constructor(K)) {
            Index the constructor text26.1;
        } else {
            WRITE("%W", W);
        }
    }
}

§26.1. Index the constructor text26.1 =

    int length = Wordings::length(W), w1 = Wordings::first_wn(W), tinted = TRUE;
    int i, first_stroke = -1, last_stroke = -1;
    for (i=0; i<length; i++) {
        if (Lexer::word(w1+i) == STROKE_V) {
            if (first_stroke == -1) first_stroke = i;
            last_stroke = i;
        }
    }
    int from = 0, to = length-1;
    if (last_stroke >= 0) from = last_stroke+1; else tinted = FALSE;
    if (tinted) HTML::begin_span(OUT, I"indexgrey");
    for (i=from; i<=to; i++) {
        int j, untinted = FALSE;
        for (j=0; j<first_stroke; j++)
            if (Lexer::word(w1+j) == Lexer::word(w1+i))
                untinted = TRUE;
        if (untinted) HTML::end_span(OUT);
        if (i>from) WRITE(" ");
        if (Lexer::word(w1+i) == CAPITAL_K_V) WRITE("K");
        else if (Lexer::word(w1+i) == CAPITAL_L_V) WRITE("L");
        else WRITE("%V", Lexer::word(w1+i));
        if (untinted) HTML::begin_span(OUT, I"indexgrey");
    }
    if (tinted) HTML::end_span(OUT);