Module:ang-adjective: difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
link strutils |
width > max-width |
||
(17 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
--[=[ |
|||
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 m_links = require("Module:links") |
||
local strutils = require("Module:string utilities") |
local strutils = require("Module:string utilities") |
||
local m_table = require("Module:table") |
|||
local lang = require("Module:languages").getByCode("ang") |
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 export = {} |
||
local MACRON = u(0x0304) |
|||
local long_vowel = "āēīōūȳǣ" .. MACRON -- No precomposed œ + macron |
|||
local short_vowel = "aeiouyæœ" |
|||
local short_vowel_c = "[" .. short_vowel .. "]" |
|||
local vowel = long_vowel .. short_vowel |
|||
local vowel_c = "[" .. vowel .. "]" |
|||
local long_vowel_c = "[" .. long_vowel .. "]" |
|||
local cons_c = "[^" .. vowel .. "]" |
|||
local cases = { "nom", "acc", "gen", "dat", "ins" } |
local cases = { "nom", "acc", "gen", "dat", "ins" } |
||
Line 12: | Line 53: | ||
for _, case in ipairs(cases) do |
for _, case in ipairs(cases) do |
||
for _, number in ipairs(numbers) do |
for _, number in ipairs(numbers) do |
||
local accel_number = number == "sg" and "s" or "p" |
|||
for _, gender in ipairs(genders) do |
for _, gender in ipairs(genders) do |
||
slots[case .. "_" .. number .. "_" .. gender] = case .. "|" .. gender .. "|" .. |
slots[case .. "_" .. number .. "_" .. gender] = case .. "|" .. gender .. "|" .. accel_number |
||
end |
end |
||
end |
end |
||
end |
end |
||
local diphthongs = { |
|||
function export.make_table(frame) |
|||
["ea"] = true, |
|||
local parent_args = frame:getParent().args |
|||
["ēa"] = true, |
|||
local params = { |
|||
["eā"] = true, |
|||
["eo"] = true, |
|||
} |
|||
["ēo"] = true, |
|||
for slot, _ in pairs(slots) do |
|||
["eō"] = true, |
|||
["io"] = true, |
|||
["īo"] = true, |
|||
["iō"] = true, |
|||
["ie"] = true, |
|||
["īe"] = true, |
|||
["iē"] = true, |
|||
} |
|||
local short_to_long_vowel = { |
|||
["a"] = "ā", |
|||
["e"] = "ē", |
|||
["i"] = "ī", |
|||
["o"] = "ō", |
|||
["u"] = "ū", |
|||
["y"] = "ȳ", |
|||
["æ"] = "ǣ", |
|||
["œ"] = "œ̄", -- 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[XY], or sonority_classes[X] > sonority_classes[Y], |
|||
-- 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 = { |
|||
["b"] = 1, |
|||
["c"] = 1, |
|||
["ċ"] = 1, |
|||
["d"] = 1, |
|||
["f"] = 1, |
|||
["g"] = 1, |
|||
["ġ"] = 1, |
|||
["k"] = 1, |
|||
["p"] = 1, |
|||
["t"] = 1, |
|||
["þ"] = 1, |
|||
["ð"] = 1, |
|||
["x"] = 1, |
|||
["z"] = 1, |
|||
["h"] = 2, |
|||
["n"] = 3, |
|||
["m"] = 3, |
|||
["l"] = 4, |
|||
["r"] = 5, |
|||
["w"] = 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[x .. y] or (sonority_classes[x] or 0) > (sonority_classes[y] 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[i] .. chars[i + 1]] then |
|||
table.insert(vowels, chars[i] .. chars[i + 1]) |
|||
i = i + 2 |
|||
else |
|||
table.insert(vowels, chars[i]) |
|||
i = i + 1 |
|||
end |
|||
end |
end |
||
return vowels |
|||
end |
|||
local args = require("Module:parameters").process(parent_args, params) |
|||
-- 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[i]) |
|||
else |
|||
local vowels = break_vowels(vowel_cons[i]) |
|||
for j = 1, #vowels do |
|||
if j == 1 then |
|||
syllables[#syllables] = syllables[#syllables] .. vowels[j] |
|||
else |
|||
table.insert(syllables, vowels[j]) |
|||
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, "^(.-)([eē][ao])$") |
|||
if prefix then |
|||
return prefix .. gsub(vowel, "^e", "ē") |
|||
end |
|||
prefix, vowel = rmatch(stem, "^(.-)([iī][eo])$") |
|||
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[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 table_args = {title = args.title} |
||
local num = args.num |
|||
local accel_lemma = args["nom_sg_m"] and args["nom_sg_m"]:gsub(",.*", "") or nil |
|||
local accel_lemma = args["lemma"] or args["nom_sg_m"] and args["nom_sg_m"][1] or nil |
|||
local weakness = args["type"] == "strong" and "str|" or args["type"] == "weak" and "wk|" or "" |
|||
for slot, accel_form in pairs(slots) do |
for slot, accel_form in pairs(slots) do |
||
local table_arg = {} |
local table_arg = {} |
||
local forms = args[slot] |
local forms = args[slot] or {"—"} |
||
for _, form in ipairs(forms) do |
for _, form in ipairs(forms) do |
||
table.insert(table_arg, form == "—" and form or m_links.full_link{ |
table.insert(table_arg, form == "—" and form or m_links.full_link{ |
||
lang = lang, term = form, accel = { |
lang = lang, term = form, accel = { |
||
form = accel_form, |
form = weakness .. accel_form, |
||
lemma = accel_lemma, |
lemma = accel_lemma, |
||
} |
} |
||
Line 43: | Line 233: | ||
end |
end |
||
end |
end |
||
local table = [=[ |
|||
local sgtable = [=[! style="background: #EFEFFF; font-size: 90%;"|[[masculine|Masculine]] |
|||
<div class="NavFrame" style="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" |
|||
! style="width: 16%; background: #EFEFFF;" |[[singular|Singular]] |
|||
! style="background: #EFEFFF; font-size: 90%;"|[[masculine|Masculine]] |
|||
! style="background: #EFEFFF; font-size: 90%;"|[[feminine|Feminine]] |
! style="background: #EFEFFF; font-size: 90%;"|[[feminine|Feminine]] |
||
! style="background: #EFEFFF; font-size: 90%;"|[[neuter|Neuter]] |
! style="background: #EFEFFF; font-size: 90%;"|[[neuter|Neuter]] |
||
Line 77: | Line 262: | ||
| {ins_sg_f} |
| {ins_sg_f} |
||
| {ins_sg_n} |
| {ins_sg_n} |
||
]=] |
|||
|- |
|||
! style="background: #EFEFFF;" |[[plural|Plural]] |
|||
! style="background: #EFEFFF; font-size: 90%;"|[[masculine|Masculine]] |
local pltable = [=[! style="background: #EFEFFF; font-size: 90%;"|[[masculine|Masculine]] |
||
! style="background: #EFEFFF; font-size: 90%;"|[[feminine|Feminine]] |
! style="background: #EFEFFF; font-size: 90%;"|[[feminine|Feminine]] |
||
! style="background: #EFEFFF; font-size: 90%;"|[[neuter|Neuter]] |
! style="background: #EFEFFF; font-size: 90%;"|[[neuter|Neuter]] |
||
Line 107: | Line 292: | ||
| {ins_pl_f} |
| {ins_pl_f} |
||
| {ins_pl_n} |
| {ins_pl_n} |
||
]=] |
|||
|{\cl}</div></div>]=] |
|||
return strutils.format(table, table_args) |
|||
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 = [=[|{\cl}</div></div>]=] |
|||
local table = [=[{table_header}! style="width: 16%; background: #EFEFFF;" |[[singular|Singular]] |
|||
{sgtable}|- |
|||
! style="background: #EFEFFF;" |[[plural|Plural]] |
|||
{pltable}{table_footer}]=] |
|||
local sg_table = [=[{table_header}! style="width: 16%; background: #EFEFFF;" |[[singular|Singular]] |
|||
{sgtable}{table_footer}]=] |
|||
local pl_table = [=[{table_header}! style="background: #EFEFFF;" |[[plural|Plural]] |
|||
{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 |
end |
||
local function make_table_with_overrides(genargs, lemma, userargs, userpref) |
|||
return export |
|||
genargs.lemma = lemma |
|||
for slot, _ in pairs(slots) do |
|||
if userargs[userpref .. slot] then |
|||
genargs[slot] = rsplit(userargs[userpref .. slot], ", *") |
|||
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[slot] then |
|||
args[slot] = {} |
|||
end |
|||
for _, stem in ipairs(stems) do |
|||
local form = construct_stem_ending(stem, ending) |
|||
if type(form) == "string" then |
|||
m_table.insertIfNot(args[slot], form) |
|||
else |
|||
for _, f in ipairs(form) do |
|||
m_table.insertIfNot(args[slot], f) |
|||
end |
|||
end |
|||
end |
|||
end |
|||
local function compute_stem_for_ending(ending) |
|||
-- For Vim, so we get 4-space tabs |
|||
if ending == "" then |
|||
-- vim: set ts=4 sw=4 noet: |
|||
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("^[aeiou%-]") 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 = { |
|||
[1] = {required = true, default = "glæd"}, |
|||
["stem"] = {}, |
|||
["bare"] = {}, |
|||
["vstem"] = {}, |
|||
["cstem"] = {}, |
|||
["cstemn"] = {}, |
|||
["cstemr"] = {}, |
|||
["short"] = {}, |
|||
["contractable"] = {}, |
|||
["type"] = {}, |
|||
["h"] = {}, |
|||
["num"] = {}, |
|||
} |
|||
if parent_args["type"] == "strong" or parent_args["type"] == "weak" then |
|||
for slot, _ in pairs(slots) do |
|||
params[slot] = {} |
|||
end |
|||
else |
|||
for slot, _ in pairs(slots) do |
|||
params["str_" .. slot] = {} |
|||
params["wk_" .. slot] = {} |
|||
end |
|||
end |
|||
local args = require("Module:parameters").process(parent_args, params) |
|||
local lemma = args[1] |
|||
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 .. "[ou]$") 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[iī][cċ]$") then |
|||
short = "opt" |
|||
elseif is_long(syllables[#syllables - 1], syllables[#syllables]) or |
|||
#syllables > 2 and not is_long(syllables[#syllables - 1], syllables[#syllables]) and |
|||
not is_long(syllables[#syllables - 2], syllables[#syllables - 1]) 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 [eou] + single consonant, and not -ed. |
|||
--In addition, the preceding syllable should be long. |
|||
(rfind(stem, "is[cċ]$") or rfind(stem, "i[gġ]$") or |
|||
(rfind(stem, cons_c .. "[eou]" .. cons_c .. "$") and not rfind(stem, "ed$"))) and |
|||
is_long(syllables[#syllables - 2], syllables[#syllables - 1]) then |
|||
local x, y = rmatch(syllables[#syllables - 1], "^" .. 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("[lr]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 .. "[lm]$") and not stem:find("[rl]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, "[lr]h$") then |
|||
-- sċeolh, þweorh |
|||
short = "h" |
|||
local prefix, final_cons = rmatch(stem, "^(.-)([lr])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["type"] then |
|||
adjtype = args["type"] |
|||
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["type"] = "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["type"] = "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 = { |
|||
["title"] = {default = "—"}, |
|||
["type"] = {}, |
|||
["lemma"] = {}, |
|||
} |
|||
for slot, _ in pairs(slots) do |
|||
params[slot] = {} |
|||
end |
|||
local args = require("Module:parameters").process(parent_args, params) |
|||
for k, v in pairs(args) do |
|||
if slots[k] then |
|||
args[k] = rsplit(v, ", *") |
|||
end |
|||
end |
|||
return make_table(args) |
|||
end |
|||
return export |
Latest revision as of 16:31, 2 September 2024
- The following documentation is located at Module:ang-adjective/documentation. [edit] Categories were auto-generated by Module:module categorization. [edit]
- Useful links: subpage list • links • transclusions • testcases • sandbox
--[=[
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 = "[" .. short_vowel .. "]"
local vowel = long_vowel .. short_vowel
local vowel_c = "[" .. vowel .. "]"
local long_vowel_c = "[" .. long_vowel .. "]"
local cons_c = "[^" .. vowel .. "]"
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 .. "_" .. number .. "_" .. gender] = case .. "|" .. gender .. "|" .. accel_number
end
end
end
local diphthongs = {
["ea"] = true,
["ēa"] = true,
["eā"] = true,
["eo"] = true,
["ēo"] = true,
["eō"] = true,
["io"] = true,
["īo"] = true,
["iō"] = true,
["ie"] = true,
["īe"] = true,
["iē"] = true,
}
local short_to_long_vowel = {
["a"] = "ā",
["e"] = "ē",
["i"] = "ī",
["o"] = "ō",
["u"] = "ū",
["y"] = "ȳ",
["æ"] = "ǣ",
["œ"] = "œ̄", -- 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[XY], or sonority_classes[X] > sonority_classes[Y],
-- 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 = {
["b"] = 1,
["c"] = 1,
["ċ"] = 1,
["d"] = 1,
["f"] = 1,
["g"] = 1,
["ġ"] = 1,
["k"] = 1,
["p"] = 1,
["t"] = 1,
["þ"] = 1,
["ð"] = 1,
["x"] = 1,
["z"] = 1,
["h"] = 2,
["n"] = 3,
["m"] = 3,
["l"] = 4,
["r"] = 5,
["w"] = 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[x .. y] or (sonority_classes[x] or 0) > (sonority_classes[y] 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[i] .. chars[i + 1]] then
table.insert(vowels, chars[i] .. chars[i + 1])
i = i + 2
else
table.insert(vowels, chars[i])
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[i])
else
local vowels = break_vowels(vowel_cons[i])
for j = 1, #vowels do
if j == 1 then
syllables[#syllables] = syllables[#syllables] .. vowels[j]
else
table.insert(syllables, vowels[j])
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, "^(.-)([eē][ao])$")
if prefix then
return prefix .. gsub(vowel, "^e", "ē")
end
prefix, vowel = rmatch(stem, "^(.-)([iī][eo])$")
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[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["lemma"] or args["nom_sg_m"] and args["nom_sg_m"][1] or nil
local weakness = args["type"] == "strong" and "str|" or args["type"] == "weak" and "wk|" or ""
for slot, accel_form in pairs(slots) do
local table_arg = {}
local forms = args[slot] 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[slot] = table.concat(table_arg, ", ")
end
end
local sgtable = [=[! style="background: #EFEFFF; font-size: 90%;"|[[masculine|Masculine]]
! style="background: #EFEFFF; font-size: 90%;"|[[feminine|Feminine]]
! style="background: #EFEFFF; font-size: 90%;"|[[neuter|Neuter]]
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[nominative case|Nominative]]
| {nom_sg_m}
| {nom_sg_f}
| {nom_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[accusative case|Accusative]]
| {acc_sg_m}
| {acc_sg_f}
| {acc_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[genitive case|Genitive]]
| {gen_sg_m}
| {gen_sg_f}
| {gen_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[dative case|Dative]]
| {dat_sg_m}
| {dat_sg_f}
| {dat_sg_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[instrumental case|Instrumental]]
| {ins_sg_m}
| {ins_sg_f}
| {ins_sg_n}
]=]
local pltable = [=[! style="background: #EFEFFF; font-size: 90%;"|[[masculine|Masculine]]
! style="background: #EFEFFF; font-size: 90%;"|[[feminine|Feminine]]
! style="background: #EFEFFF; font-size: 90%;"|[[neuter|Neuter]]
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[nominative case|Nominative]]
| {nom_pl_m}
| {nom_pl_f}
| {nom_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[accusative case|Accusative]]
| {acc_pl_m}
| {acc_pl_f}
| {acc_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[genitive case|Genitive]]
| {gen_pl_m}
| {gen_pl_f}
| {gen_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[dative case|Dative]]
| {dat_pl_m}
| {dat_pl_f}
| {dat_pl_n}
|-
! style="background: #EFEFFF; text-align: left; font-size: 90%;"|[[instrumental case|Instrumental]]
| {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 = [=[|{\cl}</div></div>]=]
local table = [=[{table_header}! style="width: 16%; background: #EFEFFF;" |[[singular|Singular]]
{sgtable}|-
! style="background: #EFEFFF;" |[[plural|Plural]]
{pltable}{table_footer}]=]
local sg_table = [=[{table_header}! style="width: 16%; background: #EFEFFF;" |[[singular|Singular]]
{sgtable}{table_footer}]=]
local pl_table = [=[{table_header}! style="background: #EFEFFF;" |[[plural|Plural]]
{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[userpref .. slot] then
genargs[slot] = rsplit(userargs[userpref .. slot], ", *")
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[slot] then
args[slot] = {}
end
for _, stem in ipairs(stems) do
local form = construct_stem_ending(stem, ending)
if type(form) == "string" then
m_table.insertIfNot(args[slot], form)
else
for _, f in ipairs(form) do
m_table.insertIfNot(args[slot], 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("^[aeiou%-]") 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 = {
[1] = {required = true, default = "glæd"},
["stem"] = {},
["bare"] = {},
["vstem"] = {},
["cstem"] = {},
["cstemn"] = {},
["cstemr"] = {},
["short"] = {},
["contractable"] = {},
["type"] = {},
["h"] = {},
["num"] = {},
}
if parent_args["type"] == "strong" or parent_args["type"] == "weak" then
for slot, _ in pairs(slots) do
params[slot] = {}
end
else
for slot, _ in pairs(slots) do
params["str_" .. slot] = {}
params["wk_" .. slot] = {}
end
end
local args = require("Module:parameters").process(parent_args, params)
local lemma = args[1]
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 .. "[ou]$") 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[iī][cċ]$") then
short = "opt"
elseif is_long(syllables[#syllables - 1], syllables[#syllables]) or
#syllables > 2 and not is_long(syllables[#syllables - 1], syllables[#syllables]) and
not is_long(syllables[#syllables - 2], syllables[#syllables - 1]) 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 [eou] + single consonant, and not -ed.
--In addition, the preceding syllable should be long.
(rfind(stem, "is[cċ]$") or rfind(stem, "i[gġ]$") or
(rfind(stem, cons_c .. "[eou]" .. cons_c .. "$") and not rfind(stem, "ed$"))) and
is_long(syllables[#syllables - 2], syllables[#syllables - 1]) then
local x, y = rmatch(syllables[#syllables - 1], "^" .. 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("[lr]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 .. "[lm]$") and not stem:find("[rl]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, "[lr]h$") then
-- sċeolh, þweorh
short = "h"
local prefix, final_cons = rmatch(stem, "^(.-)([lr])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["type"] then
adjtype = args["type"]
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["type"] = "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["type"] = "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 = {
["title"] = {default = "—"},
["type"] = {},
["lemma"] = {},
}
for slot, _ in pairs(slots) do
params[slot] = {}
end
local args = require("Module:parameters").process(parent_args, params)
for k, v in pairs(args) do
if slots[k] then
args[k] = rsplit(v, ", *")
end
end
return make_table(args)
end
return export