To summarise wordings into alphanumeric identifiers of the kind used by standard programming languages.


§1. Validity of identifiers. In code compiled by I7, a valid identifier is a sequence of 1 to 31 characters, which must be alphanumeric or else underscores, except that the leading character must not be a 0:

int Identifiers::valid(inchar32_t *p) {
    if ((Wide::len(p) == 0) || (Wide::len(p) > 31)) return FALSE;
    for (int i=0; p[i]; i++)
        if ((Characters::isdigit(p[i]) == 0) && (Characters::isalpha(p[i]) == 0)
            && (p[i] != '_'))
            return FALSE;
    if (Characters::isdigit(p[0])) return FALSE;
    return TRUE;
}

§2. The following flattens characters into shape:

void Identifiers::purify(text_stream *identifier) {
    LOOP_THROUGH_TEXT(pos, identifier) {
        inchar32_t x = Str::get(pos);
        if (!(((x >= '0') && (x <= '9')) ||
            ((x >= 'a') && (x <= 'z')) || ((x >= 'A') && (x <= 'Z')) || (x == '_')))
            Str::put(pos, '_');
    }
}

§3. Automatically composed identifiers. The following routines are no longer used by Inform, but retained in case useful for other projects.

The idea here is that we want an identifier based on a natural language wording, but which passed the above validity tests, and which does not lead to namespace collisions. Such identifiers are composed in a pattern which uses an identifying letter (e.g., A for Action), a unique ID number (preventing name-clashes) and then a truncated alphanumeric-safe form of the words used in the textual description, if any. For example, an object called "apple crumble" might have identifier O100_apple_crumble. Any other object also called "apple crumble" would have a different identifier since the number parts would be different.

Beginning with the identifying letter ensures that we do not open with a 0 digit.

We truncate to 28 characters in length so that other routines can concatenate our identifier with up to 3 further characters, if they choose.

void Identifiers::compose(text_stream *identifier, int nature_character,
    int id_number, wording W) {
    Str::clear(identifier);
    WRITE_TO(identifier, "%c%d", nature_character, id_number);
    if (Wordings::nonempty(W)) {
        LOOP_THROUGH_WORDING(j, W) {
             identifier is at this point 32 chars or fewer in length: add at most 30 more
            if (Wide::len(Lexer::word_text(j)) > 30)
                WRITE_TO(identifier, " etc");
            else WRITE_TO(identifier, " %N", j);
            if (Str::len(identifier) > 32) break;
        }
    }
    Str::truncate(identifier, 28);  it was at worst 62 chars in size, but is now truncated to 28
    Identifiers::purify(identifier);
}

void Identifiers::compose_numberless(text_stream *identifier, text_stream *prefix,
    wording W) {
    Str::copy(identifier, prefix);
    if (Wordings::nonempty(W)) {
        LOOP_THROUGH_WORDING(j, W) {
             identifier is at this point 32 chars or fewer in length: add at most 30 more
            if (Wide::len(Lexer::word_text(j)) > 30)
                WRITE_TO(identifier, " etc");
            else WRITE_TO(identifier, " %N", j);
            if (Str::len(identifier) > 32) break;
        }
    }
    Str::truncate(identifier, 28);  it was at worst 62 chars in size, but is now truncated to 28
    Identifiers::purify(identifier);
}