A lexicon for nouns, adjectives and verbs found in an Inter tree.
§1. The lexicon is the part of the Index which gives an alphabetised list of adjectives, nouns, verbs and other words which can be used in descriptions of things: it's the nearest thing to an index of the meanings inside Inform. It brings together meanings relating to various different Inform structures under a single umbrella:
define COMMON_NOUN_TLEXE 1 a kind define PROPER_NOUN_TLEXE 2 an instance of "object" define ADJECTIVAL_PHRASE_TLEXE 3 the subject of a "Definition:" define ENUMERATED_CONSTANT_TLEXE 4 e.g., "green" if colour is a kind of value and green a colour define VERB_TLEXE 5 an ordinary verb define PREP_TLEXE 7 a "to be upon..." sort of verb define MVERB_TLEXE 9 a meaningless verb
typedef struct index_lexicon_entry { struct text_stream *lemma; int part_of_speech; one of those above char *category; textual description of said, e.g., "adjective" struct general_pointer entry_refers_to; depending on which part of speech char *gloss_note; gloss on the definition, or NULL if none is provided struct inter_package *lex_package; int link_to; word number in source text struct text_stream *reduced_to_lower_case; text converted to lower case for sorting struct index_lexicon_entry *sorted_next; next in lexicographic order CLASS_DEFINITION } index_lexicon_entry;
- The structure index_lexicon_entry is accessed in 3/ve2 and here.
§2. A lexicon is then a set of entries. These are accumulated in no particular order, then sorted.
typedef struct inter_lexicon { struct linked_list *unsorted; index_lexicon_entry *first; head of list in lexicographic order CLASS_DEFINITION } inter_lexicon;
- The structure inter_lexicon is accessed in 3/ve2 and here.
§3. Creating a lexicon. And this is where that happens:
inter_lexicon *IndexLexicon::stock(inter_tree *I, tree_inventory *inv) { inter_lexicon *lexicon = CREATE(inter_lexicon); lexicon->first = NULL; lexicon->unsorted = NEW_LINKED_LIST(index_lexicon_entry); Add verb entries3.1; Add preposition entries3.2; Add adjective entries3.3; Add common noun entries3.4; Add proper noun entries3.5; Create lower-case forms of all lexicon entries3.6; Sort the lexicon into alphabetical order3.7; return lexicon; }
InterNodeList::array_sort(inv->verb_nodes, MakeSynopticModuleStage::module_order); inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->verb_nodes) { index_lexicon_entry *lex; if (Metadata::read_numeric(pack, I"^meaningless")) lex = IndexLexicon::new_entry_with_package( Metadata::required_textual(pack, I"^infinitive"), MVERB_TLEXE, pack); else lex = IndexLexicon::new_entry_with_package( Metadata::required_textual(pack, I"^infinitive"), VERB_TLEXE, pack); lex->link_to = (int) Metadata::read_numeric(pack, I"^at"); ADD_TO_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted); }
- This code is used in §3.
§3.2. Add preposition entries3.2 =
inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->preposition_nodes) { index_lexicon_entry *lex = IndexLexicon::new_entry_with_package( Metadata::required_textual(pack, I"^text"), PREP_TLEXE, pack); lex->link_to = (int) Metadata::read_numeric(pack, I"^at"); }
- This code is used in §3.
§3.3. Add adjective entries3.3 =
inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->adjective_nodes) { text_stream *lemma = Metadata::required_textual(pack, I"^text"); if (Str::len(lemma) > 0) { index_lexicon_entry *lex = IndexLexicon::new_entry(lemma, ADJECTIVAL_PHRASE_TLEXE); lex->category = "adjective"; lex->lex_package = pack; ADD_TO_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted); } }
- This code is used in §3.
§3.4. Add common noun entries3.4 =
inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->kind_nodes) if (Metadata::read_optional_numeric(pack, I"^is_subkind_of_object")) { index_lexicon_entry *lex = IndexLexicon::new_entry( Metadata::required_textual(pack, I"^name"), COMMON_NOUN_TLEXE); lex->link_to = (int) Metadata::read_numeric(pack, I"^at"); lex->category = "noun"; lex->lex_package = pack; ADD_TO_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted); }
- This code is used in §3.
§3.5. Add proper noun entries3.5 =
inter_package *pack; LOOP_OVER_INVENTORY_PACKAGES(pack, i, inv->instance_nodes) { if (Metadata::read_optional_numeric(pack, I"^is_object")) { index_lexicon_entry *lex = IndexLexicon::new_entry( Metadata::required_textual(pack, I"^name"), PROPER_NOUN_TLEXE); lex->link_to = (int) Metadata::read_numeric(pack, I"^at"); lex->category = "noun"; lex->lex_package = pack; ADD_TO_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted); } else { index_lexicon_entry *lex = IndexLexicon::new_entry( Metadata::required_textual(pack, I"^name"), ENUMERATED_CONSTANT_TLEXE); lex->link_to = (int) Metadata::read_numeric(pack, I"^at"); lex->category = "noun"; lex->lex_package = pack; ADD_TO_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted); } }
- This code is used in §3.
§3.6. Before we can sort the lexicon, we need to turn its disparate forms of name into a single, canonical, lower-case representation.
Create lower-case forms of all lexicon entries3.6 =
index_lexicon_entry *lex; LOOP_OVER_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted) { Str::copy(lex->reduced_to_lower_case, lex->lemma); LOOP_THROUGH_TEXT(pos, lex->reduced_to_lower_case) Str::put(pos, Characters::tolower(Str::get(pos))); }
- This code is used in §3.
§3.7. The lexicon is sorted by insertion sort, which is not ideally fast, but which is convenient when dealing with linked lists: there are unlikely to be more than 1000 or so entries, so the speed penalty for insertion rather than (say) quicksort is not great.
Sort the lexicon into alphabetical order3.7 =
index_lexicon_entry *lex; LOOP_OVER_LINKED_LIST(lex, index_lexicon_entry, lexicon->unsorted) { index_lexicon_entry *lex2, *last_lex; if (lexicon->first == NULL) { lexicon->first = lex; lex->sorted_next = NULL; continue; } for (last_lex = NULL, lex2 = lexicon->first; lex2; last_lex = lex2, lex2 = lex2->sorted_next) if (Str::cmp(lex->reduced_to_lower_case, lex2->reduced_to_lower_case) < 0) { if (last_lex == NULL) lexicon->first = lex; else last_lex->sorted_next = lex; lex->sorted_next = lex2; goto Inserted; } last_lex->sorted_next = lex; lex->sorted_next = NULL; Inserted: ; }
- This code is used in §3.
§4. Lexicon entries are created by the following function:
index_lexicon_entry *IndexLexicon::new_entry(text_stream *lemma, int part) { index_lexicon_entry *lex = CREATE(index_lexicon_entry); lex->lemma = Str::duplicate(lemma); lex->part_of_speech = part; lex->entry_refers_to = NULL_GENERAL_POINTER; lex->category = NULL; lex->gloss_note = NULL; lex->reduced_to_lower_case = Str::new(); lex->lex_package = NULL; lex->link_to = 0; return lex; }
§5. Or by these specialisations:
index_lexicon_entry *IndexLexicon::new_entry_with_details(text_stream *lemma, int pos, char *category, char *gloss) { index_lexicon_entry *lex = IndexLexicon::new_entry(lemma, pos); lex->lemma = lemma; lex->category = category; lex->gloss_note = gloss; return lex; } index_lexicon_entry *IndexLexicon::new_entry_with_package(text_stream *infinitive, int part, inter_package *pack) { index_lexicon_entry *lex = IndexLexicon::new_entry(NULL, part); lex->lemma = infinitive; lex->category = "verb"; lex->lex_package = pack; return lex; }
§6. Listing a lexicon. Now for the bulk of the work. Entries appear in HTML paragraphs with hanging indentation and no interparagraph spacing, so we need to insert regular paragraphs between the As and the Bs, then between the Bs and the Cs, and so on. Each entry consists of the wording, then maybe some icons, then an explanation of what it is: for instance,
player's holdall [icon] noun, a kind of container
In a few cases, there is a further textual gloss to add.
void IndexLexicon::listing(OUTPUT_STREAM, inter_lexicon *lexicon, int proper_nouns_only, localisation_dictionary *LD) { index_lexicon_entry *lex; inchar32_t current_initial_letter = '?'; int verb_count = 0, proper_noun_count = 0, c; for (lex = lexicon->first; lex; lex = lex->sorted_next) if (lex->part_of_speech == PROPER_NOUN_TLEXE) proper_noun_count++; if (proper_nouns_only) { HTML::begin_html_table(OUT, NULL, TRUE, 0, 0, 0, 0, 0); HTML::first_html_column(OUT, 0); } for (c = 0, lex = lexicon->first; lex; lex = lex->sorted_next) { if (proper_nouns_only) { if (lex->part_of_speech != PROPER_NOUN_TLEXE) continue; } else { if (lex->part_of_speech == PROPER_NOUN_TLEXE) continue; } if ((proper_nouns_only) && (c == proper_noun_count/2)) HTML::next_html_column(OUT, 0); if (current_initial_letter != Str::get_first_char(lex->reduced_to_lower_case)) { if (c > 0) { HTML_OPEN("p"); HTML_CLOSE("p"); } current_initial_letter = Str::get_first_char(lex->reduced_to_lower_case); } c++; HTML_OPEN_WITH("p", "class=\"hang\""); Text of the actual lexicon entry6.1; Icon with link to documentation, source or verb table, if any6.2; switch(lex->part_of_speech) { case ADJECTIVAL_PHRASE_TLEXE: Definition of adjectival phrase entry6.5; break; case ENUMERATED_CONSTANT_TLEXE: Definition of enumerated instance entry6.6; break; case PROPER_NOUN_TLEXE: Definition of proper noun entry6.4; break; case COMMON_NOUN_TLEXE: Definition of common noun entry6.3; break; } if (lex->gloss_note) WRITE(" <i>%s</i>", lex->gloss_note); HTML_CLOSE("p"); } if (proper_nouns_only) { HTML::end_html_row(OUT); HTML::end_html_table(OUT); } }
§6.1. In traditional dictionary fashion, we present the text in what may not be the most normal ordering, in order to place the alphabetically important part first: thus "see, to be able to" rather than "to be able to see".1
1 Compare "Gallifreyan High Council, continual incidences of madness and treachery amongst the" in "Doctor Who: The Completely Useless Encyclopaedia", eds. Howarth and Lyons (1996). ↩
Text of the actual lexicon entry6.1 =
if (lex->part_of_speech == PREP_TLEXE) Localisation::roman_t(OUT, LD, I"Index.Elements.Lx.ToBe", lex->lemma); else WRITE("%S", lex->lemma);
- This code is used in §6.
§6.2. Main lexicon entries to do with verbs link further down the index page to the corresponding entries in the verb table. We want to use numbered anchors for these links, but we want to avoid colliding with numbered anchors already used for other purposes higher up on the Phrasebook index page. So we use a set of anchors numbered 10000 and up, which is guaranteed not to coincide with any of those.
We omit source links to an adjectival phrase because these are polymorphic, that is, the phrase may have multiple definitions in different parts of the source text: so any single link would be potentially misleading.
Icon with link to documentation, source or verb table, if any6.2 =
switch(lex->part_of_speech) { case COMMON_NOUN_TLEXE: IndexUtilities::link_to_documentation(OUT, lex->lex_package); break; case VERB_TLEXE: case PREP_TLEXE: IndexUtilities::below_link_numbered(OUT, 10000+verb_count++); break; } if ((lex->part_of_speech != ADJECTIVAL_PHRASE_TLEXE) && (lex->link_to > 0)) IndexUtilities::link(OUT, lex->link_to);
- This code is used in §6.
§6.3. Definition of common noun entry6.3 =
Begin definition text6.3.1; WRITE(", "); Localisation::roman_t(OUT, LD, I"Index.Elements.Lx.KindOf", Metadata::optional_textual(lex->lex_package, I"^index_superkind")); End definition text6.3.2;
- This code is used in §6.
§6.4. Simply the name of an instance.
Definition of proper noun entry6.4 =
Begin definition text6.3.1; WRITE("%S", Metadata::required_textual(lex->lex_package, I"^index_kind")); End definition text6.3.2;
- This code is used in §6.
§6.5. As mentioned above, an adjectival phrase can be multiply defined in different contexts. We want to quote all of those.
Definition of adjectival phrase entry6.5 =
Begin definition text6.3.1; WRITE(": %S", Metadata::required_textual(lex->lex_package, I"^index_entry")); End definition text6.3.2;
- This code is used in §6.
§6.6. Definition of enumerated instance entry6.6 =
Begin definition text6.3.1; WRITE(", "); Localisation::roman_t(OUT, LD, I"Index.Elements.Lx.ValueOf", Metadata::optional_textual(lex->lex_package, I"^index_kind")); End definition text6.3.2;
- This code is used in §6.
§6.3.1. Begin definition text6.3.1 =
WRITE(" ... <i>"); if ((proper_nouns_only == FALSE) && (lex->category)) WRITE("%s", lex->category);
§6.3.2. End definition text6.3.2 =
WRITE("</i>");