Keeping track of the metadata on and sequencing of the examples.


§1. Examples are created in no particular order, and their allocation numbers do not necessarily correspond to the numbering displayed in the final documentation produced. We'll occasionally refer to ENO, or "example numbering order", for this internal ordering.

Since a single example can appear in multiple volumes, in different places in each, we must record where it occurs in each one. In some forms of output, examples aren't given in full until several sections after the one they belong to (for example, to hold them back to the end of the current chapter, or even the current volume), so we also need to remember where it will actually go.

typedef struct example {
    struct filename *ex_filename;
    struct text_stream *ex_outline;
    struct text_stream *ex_public_name;
    struct text_stream *ex_rubric;
    struct text_stream *ex_rubric_pared_down;
    struct text_stream *ex_stars;
    struct text_stream *ex_sort_key;
    int ex_star_count;
    struct section *example_belongs_to_section[MAX_VOLUMES];  e.g., an example might belong to section 7
    struct section *example_displayed_at_section[MAX_VOLUMES];  but be held back and appear at end of section 23
    int example_position[MAX_VOLUMES];  sequence, counting from 0
    CLASS_DEFINITION
} example;

§2. Examples are referenced both by a flat array (in ENO order) and in a hash of their names:

define MAX_EXAMPLES 1000
example *examples[MAX_EXAMPLES];
dictionary *examples_by_name = NULL;

§3. These are used temporarily during recipe book construction.

dictionary *recipe_location = NULL;
dictionary *recipe_sort_prefix = NULL;
dictionary *recipe_subheading_of = NULL;
dictionary *recipe_translates_as = NULL;

§4. Example scanning. Each Example has its own file, which consists of a three-line header, and then some rawtext. The following scanner goes through a whole directory to look for example files, and then scans their headers, ignoring the text below for the time being. A sample:

    *** Plural assertions
    (Clothing kinds; Get Me to the Church on Time)
    Using kinds of clothing to prevent the player from wearing several...

Note that the title of the work appears after the semicolon on line 2.

An exception to this is the (Recipes).txt file, which is not an example, but is instead a layout plan for how examples appear in volume 1 of the Inform documentation.

void Examples::scan_examples(void) {
    linked_list *L = Directories::listing(indoc_settings->examples_directory);
    text_stream *entry;
    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
        if (Platform::is_folder_separator(Str::get_last_char(entry))) continue;
        filename *exloc = Filenames::in(indoc_settings->examples_directory, entry);
        if (Regexp::match(NULL, entry, L"%(Recipes%)%c*")) Scan the Recipe Book catalogue4.2
        else Scan a regular example4.1;
    }
    Use the Recipe Book catalogue to place examples in the RB4.3;
    volume *V;
    LOOP_OVER(V, volume) {
        Work out the sequence of examples within this volume4.4;
        Work out where each example is displayed within this volume4.6;
    }
}

§4.1. Scan a regular example4.1 =

    example *E = CREATE(example);
    if (no_examples >= MAX_EXAMPLES)
        Errors::fatal("too many examples");
    examples[no_examples++] = E;
    examples_helper_state ehs;
    ehs.E = E;
    ehs.ef = exloc;
    TextFiles::read(exloc, FALSE, "can't read example file",
        TRUE, Examples::examples_helper, NULL, &ehs);

§5.

typedef struct examples_helper_state {
    struct example *E;
    struct filename *ef;
} examples_helper_state;

void Examples::examples_helper(text_stream *line, text_file_position *tfp, void *v_ehs) {
    examples_helper_state *ehs = (examples_helper_state *) v_ehs;
    example *E = ehs->E;
    Str::trim_white_space_at_end(line);
    match_results mr = Regexp::create_mr();
    if (tfp->line_count == 1) Scan line 1 of the example header5.1;
    if (tfp->line_count == 2) Scan line 2 of the example header5.2;
    if (tfp->line_count == 3) Scan line 3 of the example header5.3;
    Regexp::dispose_of(&mr);
}

§5.1. Scan line 1 of the example header5.1 =

    if (Regexp::match(&mr, line, L" *(%*+) (%c*)")) {
        text_stream *asterisk_text = mr.exp[0];
        text_stream *sname = mr.exp[1];
        E->ex_stars = Str::duplicate(asterisk_text);
        int starc = 0;
        if (Str::eq_wide_string(E->ex_stars, L"*")) starc=1;
        if (Str::eq_wide_string(E->ex_stars, L"**")) starc=2;
        if (Str::eq_wide_string(E->ex_stars, L"***")) starc=3;
        if (Str::eq_wide_string(E->ex_stars, L"****")) starc=4;
        if (starc == 0) {
            Errors::in_text_file("star count for example must be * to ****", tfp);
            starc = 1;
        }
        E->ex_star_count = starc;

        section *S = Dictionaries::read_value(volumes[0]->sections_by_name, sname);
        if (S) E->example_belongs_to_section[0] = S;
        else {
            E->example_belongs_to_section[0] = NULL;
            Errors::in_text_file("example belongs to an unknown section", tfp);
        }
        E->ex_filename = ehs->ef;
    } else {
        Errors::in_text_file("example has a malformed first line", tfp);
    }

§5.2. Scan line 2 of the example header5.2 =

    if (Regexp::match(&mr, line, L" *%((%c*?)%)")) {
        match_results mr2 = Regexp::create_mr();

        E->ex_rubric = Str::duplicate(mr.exp[0]);
        TEMPORARY_TEXT(rb)
        Str::copy(rb, E->ex_rubric);
        if (Regexp::match(&mr2, rb, L"(%c*?) *-- *(%c*)")) Str::copy(rb, mr2.exp[1]);
        if (Regexp::match(&mr2, rb, L"(%c*); *(%c*?)")) Str::copy(rb, mr2.exp[0]);
        if (Regexp::match(&mr2, rb, L"(%c*?): *(%c*?)")) Str::copy(rb, mr2.exp[1]);
        E->ex_rubric_pared_down = Str::duplicate(rb);
        DISCARD_TEXT(rb)

        TEMPORARY_TEXT(name)
        Str::copy(name, E->ex_rubric);
        if (Regexp::match(&mr2, name, L"%c*;(%c*?)")) Str::copy(name, mr2.exp[0]);
        if (Regexp::match(&mr2, name, L"(%c*?): (%d+). %c*")) {
            Str::clear(name);
            WRITE_TO(name, "%S %S", mr2.exp[0], mr2.exp[1]);
        }
        Str::trim_white_space(name);
        E->ex_public_name = Str::duplicate(name);

        if (examples_by_name == NULL) examples_by_name = Dictionaries::new(100, FALSE);
        Dictionaries::create(examples_by_name, name);
        Dictionaries::write_value(examples_by_name, name, E);
        DISCARD_TEXT(name)

        Regexp::dispose_of(&mr2);
    } else {
        Errors::in_text_file("example has a malformed second line", tfp);
    }

§5.3. Scan line 3 of the example header5.3 =

    E->ex_outline = Str::duplicate(line);

§4.2. The RB catalogue has a rather arcane format: see the file itself to be

Scan the Recipe Book catalogue4.2 =

    examples_rb_helper_state erbhs;
    erbhs.current_rch = Str::new();
    erbhs.current_rcsh = Str::new();
    erbhs.no_recipe_headings = 0;
    erbhs.no_recipe_subheadings = 0;
    TextFiles::read(exloc, FALSE, "can't read Recipe Book catalogue file",
        TRUE, Examples::examples_rb_helper, NULL, &erbhs);

§6.

typedef struct examples_rb_helper_state {
    struct text_stream *current_rch;
    struct text_stream *current_rcsh;
    int no_recipe_headings;
    int no_recipe_subheadings;
} examples_rb_helper_state;

void Examples::examples_rb_helper(text_stream *line, text_file_position *tfp, void *v_erbhs) {
    examples_rb_helper_state *erbhs = (examples_rb_helper_state *) v_erbhs;
    Str::trim_white_space(line);
    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, line, L" *(%c*?) *== *(%c*?)")) Scan a translation line6.1
    else if (Regexp::match(&mr, line, L">(%c*)")) Scan a major heading6.2
    else if (Regexp::match(&mr, line, L"%*(%c*)")) Scan a minor heading6.3
    else if (Str::len(line) > 0) Scan an example name6.4;
    Regexp::dispose_of(&mr);
}

§6.1. Scan a translation line6.1 =

    if (recipe_translates_as == NULL) recipe_translates_as = Dictionaries::new(100, TRUE);
    text_stream *trans = Dictionaries::create_text(recipe_translates_as, mr.exp[0]);
    Str::copy(trans, mr.exp[1]);

§6.2. Scan a major heading6.2 =

    Str::copy(erbhs->current_rch, mr.exp[0]);
    erbhs->no_recipe_headings++;
    Str::clear(erbhs->current_rcsh);
    erbhs->no_recipe_subheadings = 0;

§6.3. Scan a minor heading6.3 =

    Str::copy(erbhs->current_rcsh, mr.exp[0]);
    erbhs->no_recipe_subheadings++;

§6.4. Scan an example name6.4 =

    if (recipe_subheading_of == NULL) recipe_subheading_of = Dictionaries::new(100, TRUE);
    text_stream *rso = Dictionaries::create_text(recipe_subheading_of, line);
    Str::copy(rso, erbhs->current_rcsh);

    if (recipe_location == NULL) recipe_location = Dictionaries::new(100, TRUE);
    text_stream *rl = Dictionaries::create_text(recipe_location, line);
    Str::copy(rl, erbhs->current_rcsh);
    if (Str::eq_wide_string(line, L"About the examples")) Str::copy(rl, I"PREFACE");
    if (Str::eq_wide_string(line, L"Basic room, container, and supporter descriptions"))
        Str::copy(rl, I"PREFACE");

    if (recipe_sort_prefix == NULL) recipe_sort_prefix = Dictionaries::new(100, TRUE);
    text_stream *rsp = Dictionaries::create_text(recipe_sort_prefix, line);
    WRITE_TO(rsp, "%02d_%02d", erbhs->no_recipe_headings, erbhs->no_recipe_subheadings);

§4.3. Use the Recipe Book catalogue to place examples in the RB4.3 =

    volume *V;
    LOOP_OVER(V, volume) {
        if (V->allocation_id == 0) continue;  placings in WWI are already made
        example *E;
        LOOP_OVER(E, example) {
            text_stream *to_find = E->ex_rubric_pared_down;
            text_stream *sname = Dictionaries::get_text(recipe_location, to_find);
            if (sname == NULL) Errors::with_text("recipe book lookup failed (1): %S", to_find);
            else {
                section *S = (section *) Dictionaries::read_value(V->sections_by_name, sname);
                if (S == NULL) Errors::with_text(
                    "recipe book lookup failed: %S refers to nonexistent section", to_find);
                else {
                    E->example_belongs_to_section[V->allocation_id] = S;
                }
            }
        }
    }

§4.4. At this point, then, we know which section every example belongs to. But we still have to put them in order within those sections: we want 1-star examples first, then 2-star, and so on. The following does that. In the first volume, examples of equal star rating are in essentially random order, but in subsequent volumes, they appear in that same order, since this means their example numbers as shown in the documentation are increasing; which looks tidy.

As noted above, example_sequence and example_position are essentially inverse permutations.

Work out the sequence of examples within this volume4.4 =

    example *E;
    LOOP_OVER(E, example) {
        V->examples_sequence[E->allocation_id] = E;
        int last_resort = E->allocation_id;
        if (V->allocation_id > 0) last_resort = E->example_position[0];
        E->ex_sort_key = Str::new();
        WRITE_TO(E->ex_sort_key, "%08d-%08d-%08d",
            E->example_belongs_to_section[V->allocation_id]->allocation_id,
            E->ex_star_count,
            last_resort);
    }

    qsort(V->examples_sequence, (size_t) no_examples, sizeof(example *),
        Examples::sort_comparison);

    for (int n=0; n<no_examples; n++) {
        example *E = V->examples_sequence[n];
        E->example_position[V->allocation_id] = n + 1;  to count from 1 when displayed
    }

§4.5.

int Examples::sort_comparison(const void *ent1, const void *ent2) {
    const example *E1 = *((const example **) ent1);
    const example *E2 = *((const example **) ent2);
    return Str::cmp(E1->ex_sort_key, E2->ex_sort_key);
}

§4.6. In some granularities, examples are held back to the end of the chapter, or even the end of the volume, to appear. This is where that's worked out.

Work out where each example is displayed within this volume4.6 =

    example *E;
    LOOP_OVER(E, example) {
        section *S = E->example_belongs_to_section[V->allocation_id];
        section *hang_here = NULL;
        for (int p = 0; p < V->vol_section_count; p++) {
            section *HS = V->sections[p];
            if (((indoc_settings->examples_granularity == SECTION_GRANULARITY) && (S == HS))
                ||
                ((indoc_settings->examples_granularity == CHAPTER_GRANULARITY) && (HS->in_which_chapter == S->in_which_chapter))
                ||
                (indoc_settings->examples_granularity == BOOK_GRANULARITY))
                hang_here = HS;
        }
        if (hang_here)
            E->example_displayed_at_section[V->allocation_id] = hang_here;
        else
            Errors::fatal("miscalculated example ownership");
    }

§7. Rendering example cues. An example cue is a rendered chunk describing and naming an example. The text of the example may or may not follow: if it doesn't, the description is a link which opens it. Depending on the examples mode, the text of the example may be either (a) included in the file and always visible, (b) included but hidden by default until an icon is clicked, or (c) stored in an external, that is, separate file.

void Examples::render_example_cue(OUTPUT_STREAM, example *E, volume *V, int writing_index) {
    if (indoc_settings->format == PLAIN_FORMAT) Render example cue in plain text7.1
    else if (indoc_settings->format == HTML_FORMAT) Render example cue in HTML7.2;
}

§7.1. Render example cue in plain text7.1 =

    WRITE("\nExample %d (%S): %S\n%S\n\n",
        E->example_position[V->allocation_id], E->ex_stars, E->ex_public_name, E->ex_outline);

§7.2. Render example cue in HTML7.2 =

    if (writing_index ==  FALSE) {
        TEMPORARY_TEXT(anchor)
        WRITE_TO(anchor, "e%d", E->allocation_id);
        HTML::anchor(OUT, anchor);
        DISCARD_TEXT(anchor)
    }

    if (indoc_settings->navigation->simplified_examples == FALSE) Render the example cue left surround7.2.1;

    TEMPORARY_TEXT(url)
    TEMPORARY_TEXT(onclick)
    Examples::open_example_url(url, E, V, V, writing_index);
    Examples::open_example_onclick(onclick, E, V, V, writing_index);
    HTML::begin_link_with_class_onclick(OUT, I"eglink", url, onclick);
    DISCARD_TEXT(url)
    DISCARD_TEXT(onclick)

    Render the example difficulty asterisks7.2.2;
    Render the example name7.2.3;
    HTML::end_link(OUT);

    HTML_TAG("br");
    WRITE("%S", E->ex_outline);

    if (indoc_settings->navigation->simplified_examples == FALSE) Render the example cue right surround7.2.4;
    WRITE("\n");

§7.2.1. The "surround" is an table-implemented area which contains the descriptive panel about the example. It has one row of three cells:

    [ ( 22 ) ]  [ Example: Whatever ]  [ RB ]

holding the "oval", the icon with the example number; the description of the example, including its name; and the cross-link to the same example in the other book.

Render the example cue left surround7.2.1 =

    HTML_OPEN_WITH("table", "class=\"egcue\"");
    HTML_OPEN("tr");

    HTML_OPEN_WITH("td", "class=\"egcellforoval\"");  The Oval begins
    HTML::begin_div_with_class_S(OUT, I"egovalfornumber overstruckimage", __FILE__, __LINE__);
    TEMPORARY_TEXT(url)
    TEMPORARY_TEXT(onclick)
    Examples::open_example_url(url, E, V, V, writing_index);
    Examples::open_example_onclick(onclick, E, V, V, writing_index);
    HTML::begin_link_with_class_onclick(OUT, I"eglink", url, onclick);
    DISCARD_TEXT(url)
    DISCARD_TEXT(onclick)
    WRITE("<b>%d</b>", E->example_position[0]);
    HTML::end_link(OUT);
    HTML::end_div(OUT);
    HTML_CLOSE("td");  The Oval ends

    HTML_OPEN_WITH("td", "class=\"egnamecell\"");
    HTML_OPEN_WITH("p", "class=\"egcuetext\"");  The Descriptive Panel Area begins

§7.2.2. Render the example difficulty asterisks7.2.2 =

    for (int starcc=0; starcc < E->ex_star_count; starcc++) {
        if (indoc_settings->navigation->simplified_examples) WRITE("*");
        else HTMLUtilities::asterisk_image(OUT, I"asterisk.png");
    }

§7.2.3. Render the example name7.2.3 =

    HTML_OPEN("b");
    TEMPORARY_TEXT(text_of_name)
    Str::copy(text_of_name, E->ex_rubric);
    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, text_of_name, L"%c*;(%c*?)")) Str::copy(text_of_name, mr.exp[0]);
    if (Regexp::match(&mr, text_of_name, L"(%c*?): (%d+)%c*")) {
        Str::clear(text_of_name);
        WRITE_TO(text_of_name, "%S %S", mr.exp[0], mr.exp[1]);
    }
    Str::trim_white_space(text_of_name);
    Rawtext::escape_HTML_characters_in(text_of_name);
    if (indoc_settings->navigation->simplified_examples == FALSE) {
        HTML_OPEN_WITH("span", "class=\"egbanner\"");
        WRITE("Example");
        HTML_CLOSE("span");
        HTML_OPEN_WITH("span", "class=\"egname\"");
        WRITE("%S", text_of_name);
        HTML_CLOSE("span");
    } else {
        WRITE("Example %d: %S", E->example_position[0], text_of_name);
    }
    DISCARD_TEXT(text_of_name)
    Regexp::dispose_of(&mr);
    HTML_CLOSE("b");

§7.2.4. Render the example cue right surround7.2.4 =

    HTML_CLOSE("p");
    HTML_CLOSE("td");  The Descriptive Panel Area ends
    HTML_OPEN_WITH("td", "class=\"egcrossref\"");
    if (no_volumes > 1)
        Render the cross-link to the same example in the other book7.2.4.1;
    HTML_CLOSE("td");
    HTML_CLOSE("tr");
    HTML_CLOSE("table");

§7.2.4.1. Render the cross-link to the same example in the other book7.2.4.1 =

    char *cross_to = "RB"; volume *V_to = volumes[1];
    if (V->allocation_id == 1) { cross_to = "WI"; V_to = volumes[0]; }
    HTML::comment(OUT, I"START IGNORE");
    HTML::begin_div_with_class_S(OUT, I"egovalforxref overstruckimage", __FILE__, __LINE__);
    TEMPORARY_TEXT(url)
    Examples::open_example_url(url, E, V, V_to, writing_index);
    HTML::begin_link(OUT, url);
    WRITE("<i>%s</i>", cross_to);
    HTML::end_link(OUT);
    HTML::end_div(OUT);
    HTML::comment(OUT, I"END IGNORE");

§8. The following is a URL for a link which opens the example. Note that in some cases this should work by a Javascript function call instead...

void Examples::open_example_url(OUTPUT_STREAM, example *E, volume *from_V, volume *V, int writing_index) {
    if ((indoc_settings->examples_mode == EXMODE_openable_internal) && (writing_index == 0) && (from_V == V))
        WRITE("#");
    else
        Examples::goto_example_url(OUT, E, V);
}

§9. ...and this is it, used for the onclick field:

void Examples::open_example_onclick(OUTPUT_STREAM, example *E, volume *from_V, volume *V, int writing_index) {
    if ((indoc_settings->examples_mode == EXMODE_openable_internal) &&
        (writing_index == 0) &&
        (from_V == V)) {
        WRITE("showExample('example%d'); return false;", E->allocation_id);
    }
}

§10. The actual URL holding the contents of an example are as follows:

void Examples::goto_example_url(OUTPUT_STREAM, example *E, volume *V) {
    WRITE("%S#e%d", E->example_belongs_to_section[V->allocation_id]->unanchored_URL, E->allocation_id);
}