--[=[
This module contains functions for creating inflection tables for Old English
adjectives. It implements {{ang-adecl}}.
Author: Benwing2
External entry points:
show(): For {{ang-adecl}}
make_table(): For {{ang-decl-adj-table}} (no longer in existence)
]=]--
local m_links = require("Module:links")
local strutils = require("Module:string utilities")
local m_table = require("Module:table")
local lang = require("Module:languages").getByCode("ang")
local u = mw.ustring.char
local rsubn = mw.ustring.gsub
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rsplit = mw.text.split
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar, n)
local retval = rsubn(term, foo, bar, n)
return retval
end
-- like str:gsub() but discards all but the first return value
local function gsub(term, foo, bar, n)
local retval = term:gsub(foo, bar, n)
return retval
end
local export = {}
local MACRON = u(0x0304)
local long_vowel = "āēīōūȳǣ" .. MACRON -- No precomposed œ + macron
local short_vowel = "aeiouyæœ"
local short_vowel_c = ""
local vowel = long_vowel .. short_vowel
local vowel_c = ""
local long_vowel_c = ""
local cons_c = ""
local cases = { "nom", "acc", "gen", "dat", "ins" }
local numbers = { "sg", "pl" }
local genders = { "m", "f", "n" }
local slots = {}
for _, case in ipairs(cases) do
for _, number in ipairs(numbers) do
local accel_number = number == "sg" and "s" or "p"
for _, gender in ipairs(genders) do
slots = case .. "|" .. gender .. "|" .. accel_number
end
end
end
local diphthongs = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local short_to_long_vowel = {
= "ā",
= "ē",
= "ī",
= "ō",
= "ū",
= "ȳ",
= "ǣ",
= "œ̄", -- the long vowel is two chars
}
local end_syllable_ok_list = {
"ġn", "ġl",
"sp", "st", "sc", "sċ", "sk",
"ps", "ts", "cs", "þs", "ðs", "fs", "ks",
"pt", "ct", "ft"
}
local end_syllable_ok = require("Module:table").listToSet(end_syllable_ok_list)
-- This is used to determine whether a sequence of consonants XYZ is ok.
-- If X == Y, or end_syllable_ok, or sonority_classes > sonority_classes,
-- we consider this OK (although when X == Y, the sequence XY reduces to a single
-- consonant before another consonant). If a sesquence of consonants XYZ is OK,
-- contraction of a vowel in the sequence XYVZ is possible (e.g. in adjective and
-- verb forms), otherwise not.
local sonority_classes = {
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 1,
= 2,
= 3,
= 3,
= 4,
= 5,
= 6,
= 6,
}
local function contraction_possible(x, y)
-- If X or Y is unknown, treat contraction as not possible.
return x == y or end_syllable_ok or (sonority_classes or 0) > (sonority_classes or 100)
end
local function break_vowels(vowelseq)
local chars = rsplit(vowelseq, "")
local vowels = {}
local i = 1
while i <= #chars do
if i < #chars and diphthongs .. chars] then
table.insert(vowels, chars .. chars)
i = i + 2
else
table.insert(vowels, chars)
i = i + 1
end
end
return vowels
end
-- Break a word into "syllables" where all syllables but the last one
-- consist of zero or more consonants + a single vowel or dipthong,
-- and the last "syllable" contains zero or more consonants without
-- a vowel, representing any final consonants. (Even if there are no
-- final consonants, there will always be a last vowelless syllable,
-- in that case consisting of the empty string.)
local function break_into_syllables(word)
local vowel_cons = strutils.capturing_split(word, "(" .. vowel_c .. "+)")
local syllables = {}
for i = 1, #vowel_cons do
if i % 2 == 1 then
table.insert(syllables, vowel_cons)
else
local vowels = break_vowels(vowel_cons)
for j = 1, #vowels do
if j == 1 then
syllables = syllables .. vowels
else
table.insert(syllables, vowels)
end
end
end
end
return syllables
end
local function is_long(syllable, next_syllable)
if rfind(syllable, long_vowel_c) or
rfind(next_syllable, "^" .. cons_c .. cons_c) or
rfind(next_syllable, "^x") then
return true
else
return false
end
end
local function lengthen_final_vowel(stem)
local prefix, vowel = rmatch(stem, "^(.-)()$")
if prefix then
return prefix .. gsub(vowel, "^e", "ē")
end
prefix, vowel = rmatch(stem, "^(.-)()$")
if prefix then
return prefix .. gsub(vowel, "^i", "ī")
end
prefix, vowel = rmatch(stem, "^(.-)(" .. short_vowel_c .. ")$")
if prefix then
return prefix .. short_to_long_vowel
end
return stem
end
local function unpalatalize_final_c(stem)
if type(stem) == "table" then
local ret = {}
for _, s in ipairs(stem) do
table.insert(ret, unpalatalize_final_c(s))
end
return ret
end
if rfind(stem, "ċċ$") then
return rsub(stem, "ċċ$", "cc")
elseif rfind(stem, "sċ$") then
return stem
else
return rsub(stem, "ċ$", "c")
end
end
local function make_table(args)
local table_args = {title = args.title}
local num = args.num
local accel_lemma = args or args and args or nil
local weakness = args == "strong" and "str|" or args == "weak" and "wk|" or ""
for slot, accel_form in pairs(slots) do
local table_arg = {}
local forms = args or {"—"}
for _, form in ipairs(forms) do
table.insert(table_arg, form == "—" and form or m_links.full_link{
lang = lang, term = form, accel = {
form = weakness .. accel_form,
lemma = accel_lemma,
}
})
table_args = table.concat(table_arg, ", ")
end
end
local sgtable = ]
! style="background: #EFEFFF; font-size: 90%;"|]
! style="background: #EFEFFF; font-size: 90%;"|]
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {nom_sg_m}
| {nom_sg_f}
| {nom_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {acc_sg_m}
| {acc_sg_f}
| {acc_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {gen_sg_m}
| {gen_sg_f}
| {gen_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {dat_sg_m}
| {dat_sg_f}
| {dat_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {ins_sg_m}
| {ins_sg_f}
| {ins_sg_n}
]=]
local pltable = ]
! style="background: #EFEFFF; font-size: 90%;"|]
! style="background: #EFEFFF; font-size: 90%;"|]
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {nom_pl_m}
| {nom_pl_f}
| {nom_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {acc_pl_m}
| {acc_pl_f}
| {acc_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {gen_pl_m}
| {gen_pl_f}
| {gen_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {dat_pl_m}
| {dat_pl_f}
| {dat_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|]
| {ins_pl_m}
| {ins_pl_f}
| {ins_pl_n}
]=]
local table_header = [=[<div class="NavFrame" style="max-width: 50em;">
<div class="NavHead" style="background:#EFF7FF" >Declension of {title}</div>
<div class="NavContent">
{\op}| style="background: #F9F9F9; text-align:center; width:100%; line-height: 125%; border: 1px solid #CCCCFF;" cellpadding="3" cellspacing="1" class="inflection-table"
]=]
local table_footer = =]
local table = ]
{sgtable}|-
! style="background: #EFEFFF;" |]
{pltable}{table_footer}]=]
local sg_table = ]
{sgtable}{table_footer}]=]
local pl_table = ]
{pltable}{table_footer}]=]
local formatted_table = strutils.format(num == "sg" and sg_table or num == "pl" and pl_table or table,
{table_header = table_header, table_footer = table_footer,
sgtable = sgtable, pltable = pltable})
return strutils.format(formatted_table, table_args)
end
local function make_table_with_overrides(genargs, lemma, userargs, userpref)
genargs.lemma = lemma
for slot, _ in pairs(slots) do
if userargs then
genargs = rsplit(userargs, ", *")
end
end
genargs.num = userargs.num
return make_table(genargs)
end
local function compute_adj_args(adjtype, bare, vstem, cstemn, cstemr, short, h)
local args = {}
local function add_ending(slot, stems, ending, construct_stem_ending)
local function default_construct_stem_ending(stem, ending)
if ending == "-" then
return stem
else
return stem .. ending
end
end
construct_stem_ending = construct_stem_ending or default_construct_stem_ending
if type(stems) ~= "table" then
stems = {stems}
end
if not args then
args = {}
end
for _, stem in ipairs(stems) do
local form = construct_stem_ending(stem, ending)
if type(form) == "string" then
m_table.insertIfNot(args, form)
else
for _, f in ipairs(form) do
m_table.insertIfNot(args, f)
end
end
end
end
local function compute_stem_for_ending(ending)
if ending == "" then
return bare
elseif ending:find("^n") then
return cstemn
elseif ending:find("^r") then
return cstemr
else
return vstem
end
end
local function construct_h_stem_ending(stem, ending)
-- Delete a vowel at the beginning of the ending. But -um can
-- either assume the contracted form (-m) or the full form (-um).
if ending == "um" then
return {stem .. "m", stem .. "um"}
elseif ending:find("^") then
return stem .. gsub(ending, "^.", "")
else
return stem .. ending
end
end
local construct_stem_ending
if h then
construct_stem_ending = construct_h_stem_ending
end
local function add_row(slot_suffix, nom, acc, gen, dat, ins)
local function add(slot_prefix, ending)
if type(ending) ~= "table" then
ending = {ending}
end
for _, e in ipairs(ending) do
local stem = compute_stem_for_ending(e)
add_ending(slot_prefix .. "_" .. slot_suffix, stem, e,
construct_stem_ending)
end
end
add("nom", nom)
add("acc", acc)
add("gen", gen)
add("dat", dat)
add("ins", ins)
end
if adjtype == "strong" then
-- short == "h" is a special signal for adjectives in -h (esp. sċeolh, þweorh);
-- use the vowel stem but have no ending.
local short_ending = short == "opt" and {"", "u", "o"} or short == "h" and "-" or
short and {"u", "o"} or ""
add_row("sg_m", "", "ne", "es", "um", "e")
add_row("sg_f", short_ending, "e", "re", "re", "re")
add_row("sg_n", "", "", "es", "um", "e")
add_row("pl_m", "e", "e", "ra", "um", "um")
add_row("pl_f", {"a", "e"}, {"a", "e"}, "ra", "um", "um")
add_row("pl_n", short_ending, short_ending, "ra", "um", "um")
elseif adjtype == "weak" then
add_row("sg_m", "a", "an", "an", "an", "an")
add_row("sg_f", "e", "an", "an", "an", "an")
add_row("sg_n", "e", "e", "an", "an", "an")
add_row("pl_m", "an", "an", {"ra", "ena"}, "um", "um")
add_row("pl_f", "an", "an", {"ra", "ena"}, "um", "um")
add_row("pl_n", "an", "an", {"ra", "ena"}, "um", "um")
else
error("Unrecognized adjective type: '" .. adjtype .. "'")
end
return args
end
function export.show(frame)
local parent_args = frame:getParent().args
local params = {
= {required = true, default = "glæd"},
= {},
= {},
= {},
= {},
= {},
= {},
= {},
= {},
= {},
= {},
= {},
}
if parent_args == "strong" or parent_args == "weak" then
for slot, _ in pairs(slots) do
params = {}
end
else
for slot, _ in pairs(slots) do
params = {}
params = {}
end
end
local args = require("Module:parameters").process(parent_args, params)
local lemma = args
local stem, bare, vstem, cstemn, cstemr
local short = false
local contractable = false
local adjtype = "normal"
local h = false
if rfind(lemma, cons_c .. "a$") then
adjtype = "weak"
stem = args.stem or gsub(lemma, "a$", "")
bare = stem
else
bare = lemma
if rfind(lemma, cons_c .. "e$") then
short = true
stem = args.stem or gsub(lemma, "e$", "")
elseif rfind(lemma, cons_c .. "$") then
if lemma:find("o$") then
bare = {lemma, gsub(lemma, "o$", "u")}
else
bare = {lemma, gsub(lemma, "u$", "o")}
end
stem = args.stem or gsub(lemma, ".$", "w")
else
stem = args.stem or lemma
local syllables = break_into_syllables(stem)
if #syllables == 1 then
error("No vowels in stem: '" .. stem .. "'")
end
if rfind(stem, vowel_c .. ".*l$") then
short = "opt"
elseif is_long(syllables, syllables) or
#syllables > 2 and not is_long(syllables, syllables) and
not is_long(syllables, syllables) then
-- "long stem", no -u in fem. sg.
else
short = true
end
if #syllables > 2 and
--Special-case final -isċ and -iġ, which are contractable;
--otherwise, should end in + single consonant, and not -ed.
--In addition, the preceding syllable should be long.
(rfind(stem, "is$") or rfind(stem, "i$") or
(rfind(stem, cons_c .. "" .. cons_c .. "$") and not rfind(stem, "ed$"))) and
is_long(syllables, syllables) then
local x, y = rmatch(syllables, "^" .. cons_c .. "*(" .. cons_c .. ")(" .. cons_c .. ")")
if not x or contraction_possible(x, y) then
contractable = true
end
end
end
end
if args.contractable then
contractable = require("Module:yesno")(args.contractable)
end
if rfind(stem, "(" .. cons_c .. ")%1$") then
-- þicce, þynne, wann, ierre, etc.
cstemn = rsub(stem, ".$", "")
cstemr = cstemn
elseif rfind(stem, cons_c .. "r$") then
-- ġīfre
cstemn = gsub(stem, "r$", "er")
cstemr = gsub(stem, "r$", "")
elseif stem:find("n$") then
-- dyrne
cstemn = gsub(stem, "n$", "")
cstemr = stem
elseif rfind(stem, cons_c .. "n$") then
-- fǣcne
cstemn = gsub(stem, "n$", "")
cstemr = gsub(stem, "n$", "en")
elseif rfind(stem, cons_c .. "$") and not stem:find("m$") and not stem:find("rl$") then
-- ǣ-cnōsle
cstemn = gsub(stem, "(.)$", "e%1")
cstemr = cstemn
elseif rfind(stem, vowel_c .. "h$") then
-- hēah, wōh
h = true
short = "h"
vstem = lengthen_final_vowel(gsub(stem, "h$", ""))
cstemn = {vstem, vstem .. "n"}
cstemr = {vstem, vstem .. "r"}
elseif rfind(stem, "h$") then
-- sċeolh, þweorh
short = "h"
local prefix, final_cons = rmatch(stem, "^(.-)()h$")
vstem = lengthen_final_vowel(prefix) .. final_cons
cstemn = vstem
cstemr = vstem
elseif rfind(stem, cons_c .. "w$") then
-- nearu, stem nearw-, cstem nearo-
cstemn = rsub(stem, "(.)w$", "%1o")
cstemr = cstemn
elseif rfind(stem, vowel_c .. "$") then
-- frēo
h = true
short = "h"
vstem = lengthen_final_vowel(stem)
cstemn = vstem
cstemr = vstem
else
cstemn = stem
cstemr = stem
end
cstemn = unpalatalize_final_c(cstemn)
cstemr = unpalatalize_final_c(cstemr)
if vstem then
-- already set for vowel-final and h-final stems
elseif contractable then
local beginning, c1, v, c2 = rmatch(stem, "(.-)(" .. cons_c .. "*)(" .. vowel_c .. "+)(" .. cons_c .. "+)$")
if not c1 then
error("Stem '" .. stem .. "' isn't contractable")
end
local contracted_c1 = c1
if rfind(contracted_c1, "(.)%1$") then
contracted_c1 = rsub(contracted_c1, ".$", "")
end
vstem = {beginning .. c1 .. v .. c2, beginning .. unpalatalize_final_c(contracted_c1) .. c2}
elseif rfind(stem, "æ" .. cons_c .. "$") then
vstem = rsub(stem, "æ(" .. cons_c .. ")$", "a%1")
elseif rfind(stem, "ea" .. cons_c .. "$") then
vstem = rsub(stem, "ea(" .. cons_c .. ")$", "a%1")
else
vstem = stem
end
if args.short == "opt" then
short = "opt"
elseif args.short then
short = require("Module:yesno")(args.short)
end
if args.h then
h = require("Module:yesno")(args.h)
end
if args then
adjtype = args
end
if args.bare then
bare = rsplit(args.bare, ", *")
end
if args.vstem then
vstem = rsplit(args.vstem, ", *")
end
if args.cstem then
cstemn = rsplit(args.cstem, ", *")
cstemr = cstemn
end
if args.cstemn then
cstemn = rsplit(args.cstemn, ", *")
end
if args.cstemr then
cstemr = rsplit(args.cstemr, ", *")
end
local function make_title(title_type)
if args.title then
return args.title
end
return require("Module:links").full_link({lang = lang, alt = lemma}, "term") ..
" — " .. title_type
end
if adjtype == "strong" or adjtype == "weak" then
local forms = compute_adj_args(adjtype, bare, vstem, cstemn, cstemr, short, h)
forms.title = make_title(adjtype == "strong" and "Strong only" or "Weak only")
return make_table_with_overrides(forms, lemma, args, "")
else
local strong_forms = compute_adj_args("strong", bare, vstem, cstemn, cstemr, short, h)
strong_forms.title = make_title("Strong")
strong_forms = "strong"
local strong_table = make_table_with_overrides(strong_forms, lemma, args, "str_")
local weak_forms = compute_adj_args("weak", bare, vstem, cstemn, cstemr, short, h)
weak_forms.title = make_title("Weak")
weak_forms = "weak"
local weak_table = make_table_with_overrides(weak_forms, lemma, args, "wk_")
return strong_table .. weak_table
end
end
function export.make_table(frame)
local parent_args = frame:getParent().args
local params = {
= {default = "—"},
= {},
= {},
}
for slot, _ in pairs(slots) do
params = {}
end
local args = require("Module:parameters").process(parent_args, params)
for k, v in pairs(args) do
if slots then
args = rsplit(v, ", *")
end
end
return make_table(args)
end
return export