[IndexRules::] Index Rules. Utility functions for indexing rules, rulebooks and activities. @h Marked rulebooks and activities. The Inter hierarchy likely contains numerous rulebooks and activities, and we need to know which one is, for example, the "when play begins" rulebook. We don't want to recognise this by its name, since that might cause problems if the source language is not English. Instead, the Inform compiler marks certain special rulebook or activity packages with an |^index_id| metadatum: for example, this might be |"when_play_begins"|. Such markers are language-independent. Given an inventory |inv| of the Inter hierarchy, then, the following functions retrieve packages by marker text. This coild be done more efficiently with a dictionary, but it's not used often enough to make it worth the work. = inter_package *IndexRules::find_rulebook(tree_inventory *inv, text_stream *marker) { inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->rulebook_nodes) if (Str::eq(marker, Metadata::optional_textual(pack, I"^index_id"))) return pack; return NULL; } inter_package *IndexRules::find_activity(tree_inventory *inv, text_stream *marker) { inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->activity_nodes) if (Str::eq(marker, Metadata::optional_textual(pack, I"^index_id"))) return pack; return NULL; } @h Rule contexts. We sometimes want to index only some of the contents of a rulebook: those which fit a particular "context". This structure abstracts that idea: = typedef struct rule_context { struct inter_package *action_context; struct simplified_scene *scene_context; } rule_context; @ Either the rules have to take effect with the given action, or during the given scene: = rule_context IndexRules::action_context(inter_package *an) { rule_context rc; rc.action_context = an; rc.scene_context = NULL; return rc; } rule_context IndexRules::scene_context(simplified_scene *s) { rule_context rc; rc.action_context = NULL; rc.scene_context = s; return rc; } @ ...Or, of course, neither. This is the default rule context, and means there is no restriction, so that we should be indexing the entire rulebook. = rule_context IndexRules::no_rule_context(void) { rule_context rc; rc.action_context = NULL; rc.scene_context = NULL; return rc; } @ To implement this, we need to know if the premiss for a given rule contains explicit requirements matching the given context |rc|. This is something that only the compiler can know for sure, so the compiler has marked the rule package with |^action| and |^during| metadata to make it possible for us to decide now. = int IndexRules::phrase_fits_rule_context(inter_tree *I, inter_package *rule_pack, rule_context rc) { if (rule_pack == NULL) return FALSE; if (rc.action_context) { int passes = FALSE; inter_package *rel_pack; LOOP_THROUGH_SUBPACKAGES(rel_pack, rule_pack, I"_relevant_action") { inter_symbol *act_ds = Metadata::required_symbol(rel_pack, I"^action"); if (InterPackage::container(act_ds->definition) == rc.action_context) passes = TRUE; } if (passes == FALSE) return FALSE; } if (rc.scene_context) { inter_symbol *scene_symbol = Metadata::optional_symbol(rule_pack, I"^during"); if (scene_symbol == NULL) return FALSE; if (InterPackage::container(scene_symbol->definition) != rc.scene_context->pack) return FALSE; } return TRUE; } @ A rulebook is "conceptually empty" with respect to a context |rc| if it contains no rules which fit |rc|: = int IndexRules::is_contextually_empty(inter_tree *I, inter_package *rb_pack, rule_context rc) { if (rb_pack) { inter_package *entry; LOOP_THROUGH_SUBPACKAGES(entry, rb_pack, I"_rulebook_entry") if (IndexRules::phrase_fits_rule_context(I, entry, rc)) return FALSE; } return TRUE; } @ Actual emptiness is easier to spot: = int IndexRules::is_empty(inter_tree *I, inter_package *rb_pack) { if ((rb_pack) && (IndexRules::no_rules(rb_pack) > 0)) return FALSE; return TRUE; } int IndexRules::no_rules(inter_package *rb_pack) { return InterTree::no_subpackages(rb_pack, I"_rulebook_entry"); } @h Rulebook boxes. @d RULEBOOK_BOX_COLOUR "e0e0e0" = void IndexRules::rulebook_box(OUTPUT_STREAM, tree_inventory *inv, text_stream *titling_key, text_stream *doc_link, inter_package *rb_pack, text_stream *disclaimer_instead, int indent, int place_in_expandable_box, index_session *session) { if (rb_pack == NULL) return; localisation_dictionary *LD = Indexing::get_localisation(session); TEMPORARY_TEXT(textual_name) if (Str::len(titling_key) > 0) Localisation::roman(textual_name, LD, titling_key); else Localisation::roman(textual_name, LD, I"Index.Elements.RS.Nameless"); string_position start = Str::start(textual_name); Str::put(start, Characters::tolower(Str::get(start))); int n = IndexRules::no_rules(rb_pack); if (place_in_expandable_box) { int expand_id = IndexUtilities::extra_ID(session); HTML::open_indented_p(OUT, indent+1, "tight"); IndexUtilities::extra_link(OUT, expand_id); if (n == 0) HTML::begin_span(OUT, I"indexgrey"); WRITE("%S", textual_name); @; WRITE(" (%d rule%s)", n, (n==1)?"":"s"); if (n == 0) HTML::end_span(OUT); HTML_CLOSE("p"); IndexUtilities::extra_div_open(OUT, expand_id, indent+1, RULEBOOK_BOX_COLOUR); @; IndexUtilities::extra_div_close(OUT, RULEBOOK_BOX_COLOUR); } else { HTML::open_indented_p(OUT, indent, ""); HTML::open_coloured_box(OUT, RULEBOOK_BOX_COLOUR, ROUND_BOX_TOP+ROUND_BOX_BOTTOM); @; HTML::close_coloured_box(OUT, RULEBOOK_BOX_COLOUR, ROUND_BOX_TOP+ROUND_BOX_BOTTOM); HTML_CLOSE("p"); } DISCARD_TEXT(textual_name) } @ = HTML::begin_html_table(OUT, NULL, TRUE, 0, 4, 0, 0, 0); HTML::first_html_column(OUT, 0); HTML::open_indented_p(OUT, 1, "tight"); WRITE("%S", textual_name); @; HTML_CLOSE("p"); HTML::next_html_column_right_justified(OUT, 0); HTML::open_indented_p(OUT, 1, "tight"); PasteButtons::paste_text(OUT, textual_name); WRITE(" name"); HTML_CLOSE("p"); HTML::end_html_row(OUT); HTML::end_html_table(OUT); if (n == 0) { HTML::open_indented_p(OUT, 2, "tight"); Localisation::roman(OUT, LD, I"Index.Elements.RS.Empty"); HTML_CLOSE("p"); } else if (disclaimer_instead) { HTML::open_indented_p(OUT, 2, "tight"); WRITE("%S", disclaimer_instead); HTML_CLOSE("p"); } else { IndexRules::rulebook_list(OUT, inv->of_tree, rb_pack, NULL, IndexRules::no_rule_context(), session); } @ = if (Str::len(doc_link) > 0) DocReferences::link(OUT, doc_link); WRITE(" ... %S", Metadata::optional_textual(rb_pack, I"^focus")); IndexUtilities::link_package(OUT, rb_pack); @h Two ways to list a rulebook. Firstly, the whole contents: = int IndexRules::rulebook_list(OUTPUT_STREAM, inter_tree *I, inter_package *rb_pack, text_stream *billing, rule_context rc, index_session *session) { int resp_count = 0; int t = IndexRules::index_rulebook_inner(OUT, 0, I, rb_pack, billing, rc, &resp_count, session); if (t > 0) HTML_CLOSE("p"); return resp_count; } @ Secondly, just the contents relevant to a given action: = int IndexRules::index_action_rules(OUTPUT_STREAM, tree_inventory *inv, inter_package *an, inter_package *rb, text_stream *key, text_stream *desc, index_session *session) { int resp_count = 0; IndexUtilities::list_suppress_indexed_links(session); int t = IndexRules::index_rulebook_inner(OUT, 0, inv->of_tree, IndexRules::find_rulebook(inv, key), desc, IndexRules::action_context(an), &resp_count, session); if (rb) t += IndexRules::index_rulebook_inner(OUT, t, inv->of_tree, rb, desc, IndexRules::no_rule_context(), &resp_count, session); if (t > 0) HTML_CLOSE("p"); IndexUtilities::list_resume_indexed_links(session); return resp_count; } @ Either way, we end up here: = int IndexRules::index_rulebook_inner(OUTPUT_STREAM, int initial_t, inter_tree *I, inter_package *rb_pack, text_stream *billing, rule_context rc, int *resp_count, index_session *session) { localisation_dictionary *LD = Indexing::get_localisation(session); int suppress_outcome = FALSE, count = initial_t; if (rb_pack == NULL) return 0; if (Str::len(billing) > 0) { if (rc.action_context) suppress_outcome = TRUE; if (IndexRules::is_contextually_empty(I, rb_pack, rc)) suppress_outcome = TRUE; } inter_package *prev = NULL; inter_package *entry; LOOP_THROUGH_SUBPACKAGES(entry, rb_pack, I"_rulebook_entry") { if (IndexRules::phrase_fits_rule_context(I, entry, rc)) { if (count++ == 0) HTML::open_indented_p(OUT, 2, "indent"); else WRITE("
"); if ((Str::len(billing) > 0) && (IndexUtilities::showing_links(session))) @; WRITE("%S", billing); WRITE("    "); *resp_count += IndexRules::index_rule(OUT, I, entry, rb_pack, rc, session); } prev = entry; } if (suppress_outcome == FALSE) IndexRules::index_outcomes(OUT, I, rb_pack, LD); IndexRules::rb_index_placements(OUT, I, rb_pack, LD); return count; } @ As noted somewhere above, there's a notation for marking the relative specificity of adjacent rules in a listing: @ = text_stream *icon_name = NULL; /* redundant assignment to appease |gcc -O2| */ if ((prev == NULL) || (Str::len(Metadata::optional_textual(prev, I"^tooltip")) == 0)) { HTML::icon_with_tooltip(OUT, I"inform:/doc_images/rulenone.png", I"start of rulebook", NULL); } else { switch (Metadata::read_optional_numeric(prev, I"^specificity")) { case 0: icon_name = I"inform:/doc_images/ruleless.png"; break; case 1: icon_name = I"inform:/doc_images/ruleequal.png"; break; case 2: icon_name = I"inform:/doc_images/rulemore.png"; break; default: internal_error("unknown rule specificity"); } HTML::icon_with_tooltip(OUT, icon_name, Metadata::optional_textual(prev, I"^tooltip"), Metadata::optional_textual(prev, I"^law")); } @h Listing a single rule. = int IndexRules::index_rule(OUTPUT_STREAM, inter_tree *I, inter_package *R, inter_package *owner, rule_context rc, index_session *session) { localisation_dictionary *LD = Indexing::get_localisation(session); int no_responses_indexed = 0; int response_box_id = IndexUtilities::extra_ID(session); text_stream *name = Metadata::optional_textual(R, I"^name"); text_stream *italicised_text = Metadata::optional_textual(R, I"^index_name"); text_stream *first_line = Metadata::optional_textual(R, I"^first_line"); if (Str::len(italicised_text) > 0) @; @; if (Str::len(name) > 0) @; if ((Str::len(italicised_text) == 0) && (Str::len(name) == 0) && (Str::len(first_line) > 0)) @; @; @; @; @; return no_responses_indexed; } @ = if (rc.scene_context) { WRITE(""); Localisation::roman_t(OUT, LD, I"Index.Elements.RS.During", FauxScenes::scene_name(rc.scene_context)); WRITE("  "); } else { text_stream *during_text = Metadata::optional_textual(R, I"^during_text"); if (Str::len(during_text) > 0) { WRITE(""); Localisation::roman_t(OUT, LD, I"Index.Elements.RS.During", during_text); WRITE("  "); } } @ = WRITE("%S  ", italicised_text); @ @d MAX_PASTEABLE_RULE_NAME_LENGTH 500 @ = HTML::begin_span(OUT, I"indexdullred"); WRITE("%S", name); HTML::end_span(OUT); WRITE("  "); TEMPORARY_TEXT(S) WRITE_TO(S, "%S", name); PasteButtons::paste_text(OUT, S); WRITE(" name "); Str::clear(S); Localisation::roman_tt(S, LD, I"Index.Elements.RS.Response", name, Metadata::optional_textual(owner, I"^printed_name")); PasteButtons::paste_text(OUT, S); WRITE(" unlist"); DISCARD_TEXT(S) inter_symbol *R_symbol = Metadata::optional_symbol(R, I"^rule"); if (R_symbol) { int c = InterTree::no_subpackages(R, I"_response"); if (c > 0) { WRITE("  "); IndexUtilities::extra_link_with(OUT, response_box_id, "responses"); WRITE("%d", c); } } @ = inter_symbol *R_symbol = Metadata::optional_symbol(R, I"^rule"); if (R_symbol) { int c = 0; inter_package *resp_pack; LOOP_THROUGH_SUBPACKAGES(resp_pack, R, I"_response") { if (c == 0) IndexUtilities::extra_div_open_nested(OUT, response_box_id, 2); else HTML_TAG("br"); IndexRules::index_response(OUT, R, resp_pack, LD); c++; } if (c > 0) IndexUtilities::extra_div_close_nested(OUT); no_responses_indexed = c; } @ = WRITE("(%S)", first_line); @ = IndexUtilities::link_package(OUT, R); @ = inter_ti id = Metadata::read_optional_numeric(R, I"^index_number"); if (id > 0) { WRITE(" "); HTML::begin_span(OUT, I"smaller"); if (id >= 2) WRITE("%d", id - 2); else WRITE("primitive"); HTML::end_span(OUT); } @ = inter_symbol *R_symbol = Metadata::optional_symbol(R, I"^rule"); if (R_symbol) { inter_package *ac_pack; LOOP_THROUGH_SUBPACKAGES(ac_pack, R, I"_applicability_condition") { HTML_TAG("br"); IndexUtilities::link_package(OUT, ac_pack); WRITE(" %S", Metadata::required_textual(ac_pack, I"^index_text")); } } @ When we index a response, we also provide a paste button for the source text to assert a change: = void IndexRules::index_response(OUTPUT_STREAM, inter_package *rule_pack, inter_package *resp_pack, localisation_dictionary *LD) { int marker = (int) Metadata::read_numeric(resp_pack, I"^marker"); text_stream *text = Metadata::required_textual(resp_pack, I"^index_text"); WRITE("    "); HTML::begin_span(OUT, I"indexresponseletter"); WRITE("  %c   ", 'A' + marker); HTML::end_span(OUT); HTML::begin_span(OUT, I"indexresponsetext"); WRITE("%S", text); HTML::end_span(OUT); WRITE("  "); TEMPORARY_TEXT(S) WRITE_TO(S, "%S response (%c)", Metadata::required_textual(rule_pack, I"^name"), 'A' + marker); PasteButtons::paste_text(OUT, S); WRITE(" name"); WRITE(" "); Str::clear(S); TEMPORARY_TEXT(letter) WRITE_TO(letter, "%c", 'A' + marker); Localisation::roman_tt(S, LD, I"Index.Elements.RS.Response", Metadata::required_textual(rule_pack, I"^name"), letter); PasteButtons::paste_text(OUT, S); WRITE(" set"); DISCARD_TEXT(letter) DISCARD_TEXT(S) } @ = void IndexRules::index_outcomes(OUTPUT_STREAM, inter_tree *I, inter_package *rb_pack, localisation_dictionary *LD) { inter_package *ro_pack; LOOP_THROUGH_SUBPACKAGES(ro_pack, rb_pack, I"_rulebook_outcome") { HTML::open_indented_p(OUT, 2, "hanging"); WRITE(""); Localisation::roman(OUT, LD, I"Index.Elements.RS.Outcome"); WRITE("  "); int is_def = (int) Metadata::read_optional_numeric(ro_pack, I"^is_default"); if (is_def) WRITE(""); WRITE("%S", Metadata::optional_textual(ro_pack, I"^text")); if (is_def) WRITE(" (default)"); WRITE(" - "); if (Metadata::read_optional_numeric(ro_pack, I"^succeeds")) Localisation::roman(OUT, LD, I"Index.Elements.RS.Success"); else if (Metadata::read_optional_numeric(ro_pack, I"^fails")) Localisation::roman(OUT, LD, I"Index.Elements.RS.Failure"); else Localisation::roman(OUT, LD, I"Index.Elements.RS.NoOutcome"); WRITE(""); HTML_CLOSE("p"); } if (Metadata::read_optional_numeric(rb_pack, I"^default_succeeds")) { HTML::open_indented_p(OUT, 2, "hanging"); WRITE(""); Localisation::roman(OUT, LD, I"Index.Elements.RS.DefaultSuccess"); WRITE(""); HTML_CLOSE("p"); } if (Metadata::read_optional_numeric(rb_pack, I"^default_fails")) { HTML::open_indented_p(OUT, 2, "hanging"); WRITE(""); Localisation::roman(OUT, LD, I"Index.Elements.RS.DefaultFailure"); WRITE(""); HTML_CLOSE("p"); } } void IndexRules::rb_index_placements(OUTPUT_STREAM, inter_tree *I, inter_package *rb_pack, localisation_dictionary *LD) { inter_package *rp_pack; LOOP_THROUGH_SUBPACKAGES(rp_pack, rb_pack, I"_rulebook_placement") { WRITE("      "); HTML::begin_span(OUT, I"smaller"); WRITE("NB: %S", Metadata::optional_textual(rp_pack, I"^text")); IndexUtilities::link_package(OUT, rp_pack); HTML::end_span(OUT); HTML_TAG("br"); } } @h Activity boxes. This is all just meant to convey visually that the three constituent rulebooks of an activity are part of a single construct: @d ACTIVITY_BOX_COLOUR "e8e0c0" = void IndexRules::activity_box(OUTPUT_STREAM, inter_tree *I, inter_package *av_pack, int indent, index_session *session) { localisation_dictionary *LD = Indexing::get_localisation(session); int expand_id = IndexUtilities::extra_ID(session); inter_symbol *before_s = Metadata::required_symbol(av_pack, I"^before_rulebook"); inter_symbol *for_s = Metadata::required_symbol(av_pack, I"^for_rulebook"); inter_symbol *after_s = Metadata::required_symbol(av_pack, I"^after_rulebook"); inter_package *before_pack = InterPackage::container(before_s->definition); inter_package *for_pack = InterPackage::container(for_s->definition); inter_package *after_pack = InterPackage::container(after_s->definition); int n = IndexRules::no_rules(before_pack) + IndexRules::no_rules(for_pack) + IndexRules::no_rules(after_pack); TEMPORARY_TEXT(textual_name) text_stream *name = Metadata::optional_textual(av_pack, I"^name"); if (Str::len(name) > 0) WRITE_TO(textual_name, "%S", name); else WRITE_TO(textual_name, "nameless"); string_position start = Str::start(textual_name); Str::put(start, Characters::tolower(Str::get(start))); HTML::open_indented_p(OUT, indent+1, "tight"); IndexUtilities::extra_link(OUT, expand_id); if (n == 0) HTML::begin_span(OUT, I"indexgrey"); WRITE("%S", textual_name); @; WRITE(" (%d rule%s)", n, (n==1)?"":"s"); if (n == 0) HTML::end_span(OUT); HTML_CLOSE("p"); IndexUtilities::extra_div_open(OUT, expand_id, indent+1, ACTIVITY_BOX_COLOUR); HTML::begin_html_table(OUT, NULL, TRUE, 0, 4, 0, 0, 0); HTML::first_html_column(OUT, 0); HTML::open_indented_p(OUT, 1, "tight"); WRITE("%S", textual_name); @; HTML_CLOSE("p"); HTML::next_html_column_right_justified(OUT, 0); HTML::open_indented_p(OUT, 1, "tight"); TEMPORARY_TEXT(skeleton) Localisation::roman_t(skeleton, LD, I"Index.Elements.RS.BeforeActivity", textual_name); PasteButtons::paste_text(OUT, skeleton); WRITE(": b "); Str::clear(skeleton); Localisation::roman_t(skeleton, LD, I"Index.Elements.RS.ForActivity", textual_name); PasteButtons::paste_text(OUT, skeleton); WRITE(": f "); Str::clear(skeleton); Localisation::roman_t(skeleton, LD, I"Index.Elements.RS.AfterActivity", textual_name); PasteButtons::paste_text(OUT, skeleton); WRITE(": a"); DISCARD_TEXT(skeleton) HTML_CLOSE("p"); DISCARD_TEXT(textual_name) HTML::end_html_row(OUT); HTML::end_html_table(OUT); IndexRules::rulebook_list(OUT, I, before_pack, I"before", IndexRules::no_rule_context(), session); IndexRules::rulebook_list(OUT, I, for_pack, I"for", IndexRules::no_rule_context(), session); IndexRules::rulebook_list(OUT, I, after_pack, I"after", IndexRules::no_rule_context(), session); inter_package *xref_pack; LOOP_THROUGH_SUBPACKAGES(xref_pack, av_pack, I"_activity_xref") { HTML::open_indented_p(OUT, 2, "tight"); WRITE("NB: %S", Metadata::optional_textual(xref_pack, I"^text")); IndexUtilities::link_package(OUT, xref_pack); HTML_CLOSE("p"); } IndexUtilities::extra_div_close(OUT, ACTIVITY_BOX_COLOUR); } @ = IndexUtilities::link_to_documentation(OUT, av_pack); WRITE(" ... "); Localisation::roman(OUT, LD, I"Index.Elements.RS.Activity"); IndexUtilities::link_package(OUT, av_pack);