To define sequentially numbered values for families of constants.

§1. The idea here is that each enumeration set is a sequence of named constants with a given postfix: for example, HARRY_ST, NEVILLE_ST, ANGELINA_ST form the *_ST set. By definition, the postfix part is the portion of the name following the final underscore, so in this case ST.

Each set of constants begins at a given value (typically 0) and then increments sequentially in definition order.

typedef struct enumeration_set {
    struct text_stream *postfix;
    struct text_stream *stub;
    int first_value;
    int next_free_value;
    struct source_line *last_observed_at;
    CLASS_DEFINITION
} enumeration_set;

§2. There won't be enough sets to make a hash table worth the overhead, so compare all against all:

enumeration_set *Enumerations::find(text_stream *post) {
    enumeration_set *es = NULL;
    LOOP_OVER(es, enumeration_set)
        if (Str::eq(post, es->postfix))
            return es;
    return NULL;
}

§3. The following is called when an enumeration is found. If from has a sensible value, this is the start of a new enumeration set; otherwise it's a further constant in what ought to be an existing set.

void Enumerations::define(OUTPUT_STREAM, text_stream *symbol,
    text_stream *from, source_line *L) {
    TEMPORARY_TEXT(pf)
    Find the postfix in this symbol name3.1;
    enumeration_set *es = Enumerations::find(pf);
    if (from == NULL) Continue existing set3.2
    else Begin new set3.3;
    DISCARD_TEXT(pf)
    if (es) es->last_observed_at = L;
}

§3.1. So for instance HARRY_ST to ST:

Find the postfix in this symbol name3.1 =

    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, symbol, U"%c*_(%C+?)")) Str::copy(pf, mr.exp[0]);
    else {
        Main::error_in_web(I"enumeration constants must belong to a _FAMILY", L);
        WRITE_TO(pf, "BOGUS");
    }
    Regexp::dispose_of(&mr);

§3.2. Continue existing set3.2 =

    if (es) {
        if (es->stub) WRITE("(%S+", es->stub);
        WRITE("%d", es->next_free_value++);
        if (es->stub) WRITE(")");
    } else Main::error_in_web(I"this enumeration _FAMILY is unknown", L);

§3.3. Begin new set3.3 =

    if (es) Main::error_in_web(I"this enumeration _FAMILY already exists", L);
    else {
        es = CREATE(enumeration_set);
        es->postfix = Str::duplicate(pf);
        es->stub = NULL;
        if ((Str::len(from) < 8) &&
            ((Regexp::match(NULL, from, U"%d+")) ||
                (Regexp::match(NULL, from, U"-%d+")))) {
            es->first_value = Str::atoi(from, 0);
            es->next_free_value = es->first_value + 1;
        } else {
            es->stub = Str::duplicate(from);
            es->first_value = 0;
            es->next_free_value = 1;
        }
    }
    if (es->stub) WRITE("(%S+", es->stub);
    WRITE("%d", es->first_value);
    if (es->stub) WRITE(")");

§4. For each set, a further constant is defined to give the range; for example, we would have NO_DEFINED_ST_VALUES set to 3. This is notionally placed in the code at the last line on which an *_ST value was defined.

void Enumerations::define_extents(OUTPUT_STREAM, tangle_target *target, programming_language *lang) {
    enumeration_set *es;
    LOOP_OVER(es, enumeration_set) {
        TEMPORARY_TEXT(symbol)
        TEMPORARY_TEXT(value)
        WRITE_TO(symbol, "NO_DEFINED_%S_VALUES", es->postfix);
        WRITE_TO(value, "%d", es->next_free_value - es->first_value);
        LanguageMethods::start_definition(OUT, lang, symbol, value,
            es->last_observed_at->owning_section, es->last_observed_at);
        LanguageMethods::end_definition(OUT, lang,
            es->last_observed_at->owning_section, es->last_observed_at);
        DISCARD_TEXT(symbol)
        DISCARD_TEXT(value)
    }
}