Declensions are sets of inflected variations of a common stem according to grammatical case.
§1. The traditional term "declension" refers to the set of inflected forms of a word which does not serve as a verb: nouns, adjectives and pronouns all have "declensions". These forms generally vary according to gender, number and also "case", which expresses context.
The inflections module uses the term "declension" in a more limited sense: it is just the set of variations by case. Variations by gender and number are taken care of by what are less elegantly called Lexical Clusters.
At any rate, a declension object is a set of wordings, one for each case:
typedef struct declension { NATURAL_LANGUAGE_WORDS_TYPE *within_language; struct wording wording_cased[MAX_GRAMMATICAL_CASES]; lcon_ti lcon_cased[MAX_GRAMMATICAL_CASES]; } declension;
- The structure declension is accessed in 3/lc2 and here.
§2. Cases in a language are itemised in the special nonterminal <grammatical-case-names>:
int Declensions::no_cases(NATURAL_LANGUAGE_WORDS_TYPE *nl) { nonterminal *nt = <grammatical-case-names>; for (production_list *pl = nt->first_pl; pl; pl = pl->next_pl) { if ((pl->definition_language == NULL) || (pl->definition_language == nl)) { int c = 0; for (production *pr = pl->first_pr; pr; pr = pr->next_pr) c++; if (c >= MAX_GRAMMATICAL_CASES) Declensions::error(nt, nl, I"too many cases"); return c; } } Declensions::error(nt, nl, I"not provided for this language"); return -1; }
§3. The following is useful for debugging:
void Declensions::writer(OUTPUT_STREAM, declension *D, declension *AD) { nonterminal *nt = <grammatical-case-names>; int nc = Declensions::no_cases(D->within_language); for (production_list *pl = nt->first_pl; pl; pl = pl->next_pl) { if ((pl->definition_language == NULL) || (pl->definition_language == D->within_language)) { int c = 0; for (production *pr = pl->first_pr; pr; pr = pr->next_pr) { if ((pr->first_pt == NULL) || (pr->first_pt->ptoken_category != FIXED_WORD_PTC) || (pr->first_pt->next_pt != NULL)) Declensions::error(nt, D->within_language, I"too complex"); if (c > 0) WRITE(", "); WRITE("%w: %W %W", Vocabulary::get_exemplar(pr->first_pt->ve_pt, TRUE), AD->wording_cased[c], D->wording_cased[c]); c++; if (c >= nc) break; } WRITE("\n"); return; } } Declensions::error(nt, D->within_language, I"not provided for this language"); }
§4. And this function extracts the right form for a given case c:
wording Declensions::in_case(declension *D, int c) { if ((c < 0) || (c >= Declensions::no_cases(D->within_language))) internal_error("case out of range"); return D->wording_cased[c]; }
§5. So much for using declensions; now to generate them. They are inflected from the stem by special Preform nonterminals:
declension Declensions::of_noun(wording W, NATURAL_LANGUAGE_WORDS_TYPE *nl, int gen, int num) { nl = DefaultLanguage::get(nl); declension D = Declensions::decline_inner(W, nl, gen, num, <noun-declension>); Fix the origin5.1; return D; } declension Declensions::of_article(wording W, NATURAL_LANGUAGE_WORDS_TYPE *nl, int gen, int num) { nl = DefaultLanguage::get(nl); declension D = Declensions::decline_inner(W, nl, gen, num, <article-declension>); Fix the origin5.1; return D; }
§5.1. If a word comes from a given file and line number in the source text, then we will say that so does any inflected form of it:
Fix the origin5.1 =
for (int c = 0; c < Declensions::no_cases(nl); c++) LOOP_THROUGH_WORDING(i, D.wording_cased[c]) Lexer::set_word_location(i, Lexer::word_location(Wordings::first_wn(W)));
- This code is used in §5 (twice).
§6. For the format of the table expressed by the nonterminal nt, see What This Module Does.
declension Declensions::decline_inner(wording W, NATURAL_LANGUAGE_WORDS_TYPE *nl, int gen, int num, nonterminal *nt) { nl = DefaultLanguage::get(nl); declension D; D.within_language = nl; int count = 0; for (production_list *pl = nt->first_pl; pl; pl = pl->next_pl) { if ((pl->definition_language == NULL) || (pl->definition_language == nl)) { count++; for (production *pr = pl->first_pr; pr; pr = pr->next_pr) { if ((pr->first_pt == NULL) || (pr->first_pt->ptoken_category != FIXED_WORD_PTC) || (pr->first_pt->next_pt == NULL) || (pr->first_pt->next_pt->ptoken_category != NONTERMINAL_PTC)) Declensions::error(nt, nl, I"line malformed"); inchar32_t *gender_letter = Vocabulary::get_exemplar(pr->first_pt->ve_pt, FALSE); if ((gender_letter[0] == '*') || ((gender_letter[0] == 'm') && (gen == MASCULINE_GENDER)) || ((gender_letter[0] == 'f') && (gen == FEMININE_GENDER)) || ((gender_letter[0] == 'n') && (gen == NEUTER_GENDER))) Decline according to this row in declension NT6.1; } } } if (count == 0) Declensions::error(nt, nl, I"noun declensions seem not to be provided for this language"); else Declensions::error(nt, nl, I"noun declension table exists but was unterminated"); return D; }
§6.1. Decline according to this row in declension NT6.1 =
int found = FALSE; nonterminal *gnt = pr->first_pt->next_pt->nt_pt; if (pr->first_pt->next_pt->next_pt == NULL) { D = Declensions::decline_from_irregulars(W, nl, gnt, gen, num, &found); } else { if ((pr->first_pt->next_pt->next_pt->ptoken_category != NONTERMINAL_PTC) || (pr->first_pt->next_pt->next_pt->next_pt != NULL)) Declensions::error(nt, nl, I"line must end with two nonterminals"); nonterminal *tnt = pr->first_pt->next_pt->next_pt->nt_pt; D = Declensions::decline_from_groups(W, nl, gnt, tnt, gen, num, &found); } if (found) return D;
- This code is used in §6.
§7. This is for the two-token form of row, gender table:
declension Declensions::decline_from_irregulars(wording W, NATURAL_LANGUAGE_WORDS_TYPE *nl, nonterminal *gnt, int gen, int num, int *found) { *found = FALSE; declension D; D.within_language = nl; if (Wordings::length(W) == 1) for (production_list *pl = gnt->first_pl; pl; pl = pl->next_pl) if ((pl->definition_language == NULL) || (pl->definition_language == nl)) for (production *pr = pl->first_pr; pr; pr = pr->next_pr) { vocabulary_entry *stem = pr->first_pt->ve_pt; if (stem == Lexer::word(Wordings::first_wn(W))) { *found = TRUE; int c = 0, nc = Declensions::no_cases(nl); for (ptoken *pt = pr->first_pt->next_pt; pt; pt = pt->next_pt) { if (pt->ptoken_category != FIXED_WORD_PTC) Declensions::error(gnt, nl, I"nonterminals are not allowed in irregular declensions"); if (((num == SINGULAR_NUMBER) && (c < nc)) || ((num == PLURAL_NUMBER) && (c >= nc))) { TEMPORARY_TEXT(stem) TEMPORARY_TEXT(result) WRITE_TO(stem, "%W", W); Inflect::follow_suffix_instruction(result, stem, Vocabulary::get_exemplar(pt->ve_pt, TRUE)); D.wording_cased[c%nc] = Feeds::feed_text(result); D.lcon_cased[c%nc] = Declensions::lcon(gen, num, c%nc); DISCARD_TEXT(stem) DISCARD_TEXT(result) } c++; } if (c < 2*nc) Declensions::error(gnt, nl, I"too few cases in irregular declension"); if (c > 2*nc) Declensions::error(gnt, nl, I"too many cases in irregular declension"); return D; } } return D; }
§8. And this is for the three-token form of row, gender grouper table:
declension Declensions::decline_from_groups(wording W, NATURAL_LANGUAGE_WORDS_TYPE *nl, nonterminal *gnt, nonterminal *nt, int gen, int num, int *found) { declension D; D.within_language = nl; TEMPORARY_TEXT(from) WRITE_TO(from, "%+W", W); match_avinue *group_trie = PreformUtilities::define_trie(gnt, TRIE_END, DefaultLanguage::get(nl)); inchar32_t *result = Tries::search_avinue(group_trie, from); DISCARD_TEXT(from) if (result == NULL) { *found = FALSE; } else { *found = TRUE; int group; Set the group number8.1; for (production_list *pl = nt->first_pl; pl; pl = pl->next_pl) if ((pl->definition_language == NULL) || (pl->definition_language == nl)) for (production *pr = pl->first_pr; pr; pr = pr->next_pr) { if ((pr->first_pt == NULL) || (pr->first_pt->ptoken_category != NONTERMINAL_PTC) || (pr->first_pt->next_pt != NULL)) Declensions::error(nt, nl, I"noun declension nonterminal malformed"); if (--group == 0) return Declensions::decline_from(W, nl, pr->first_pt->nt_pt, gen, num); } Declensions::error(nt, nl, I"noun declension nonterminal has too few groups"); } return D; }
§8.1. Set the group number8.1 =
group = (int) (result[0] - '0'); if ((group <= 0) || (group > 9)) Declensions::error(nt, nl, I"noun declension grouper result not a group number"); if (result[1]) { int u = (int) (result[1] - '0'); if ((u < 0) || (u > 9)) Declensions::error(nt, nl, I"noun declension grouper result not a group number"); group = group*10 + u; if (result[2]) Declensions::error(nt, nl, I"noun declension grouper result too high"); }
- This code is used in §8.
§9. We have now found the actual declension table NT; if there are \(N\) cases in the language, there will be \(2N\) productions in this table, each of which consists of a single word giving the rewriting instruction to use.
declension Declensions::decline_from(wording W, NATURAL_LANGUAGE_WORDS_TYPE *nl, nonterminal *nt, int gen, int num) { int c = 0, nc = Declensions::no_cases(nl); declension D; D.within_language = nl; for (production_list *pl = nt->first_pl; pl; pl = pl->next_pl) { if ((pl->definition_language == NULL) || (pl->definition_language == nl)) { for (production *pr = pl->first_pr; pr; pr = pr->next_pr) { if ((pr->first_pt == NULL) || (pr->first_pt->ptoken_category != FIXED_WORD_PTC) || (pr->first_pt->next_pt != NULL)) Declensions::error(nt, nl, I"declension too complex"); if (((c < nc) && (num == SINGULAR_NUMBER)) || ((c >= nc) && (num == PLURAL_NUMBER))) { TEMPORARY_TEXT(stem) TEMPORARY_TEXT(result) WRITE_TO(stem, "%+W", W); Inflect::follow_suffix_instruction(result, stem, Vocabulary::get_exemplar(pr->first_pt->ve_pt, TRUE)); D.wording_cased[c%nc] = Feeds::feed_text(result); D.lcon_cased[c%nc] = Declensions::lcon(gen, num, c%nc); DISCARD_TEXT(stem) DISCARD_TEXT(result) } c++; } if (c < 2*nc) Declensions::error(nt, nl, I"too few cases in declension"); if (c > 2*nc) Declensions::error(nt, nl, I"too many cases in declension"); return D; } } Declensions::error(nt, nl, I"declension unavailable"); return D; }
lcon_ti Declensions::lcon(int gen, int num, int c) { lcon_ti l = Lcon::base(); l = Lcon::set_number(l, num); l = Lcon::set_gender(l, gen); l = Lcon::set_case(l, c); return l; }
void Declensions::error(nonterminal *nt, NATURAL_LANGUAGE_WORDS_TYPE *nl, text_stream *err) { #ifdef PREFORM_ERROR_INFLECTIONS_CALLBACK PREFORM_ERROR_INFLECTIONS_CALLBACK(nt, nl, err); #endif #ifndef PREFORM_ERROR_INFLECTIONS_CALLBACK WRITE_TO(STDERR, "Declension error in Preform syntax "); if (nt) WRITE_TO(STDERR, "in the nonterminal '%V'", nt->nonterminal_id); WRITE_TO(STDERR, ": %S\n", err); internal_error("halted with Preform syntax error"); #endif }