Module:Fr-headword

local export = {} local pos_functions = {} local rfind = mw.ustring.find local rmatch = mw.ustring.match local rsubn = mw.ustring.gsub local rsplit = mw.text.split

local lang = require("Module:languages").getByCode("fr") local suffix_categories = { ["adjectives"] = true, ["adverbs"] = true, ["nouns"] = true, ["verbs"] = true, ["prepositional phrases"] = true, }

-- version of rsubn that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end

local function track(page) require("Module:debug").track("fr-headword/" .. page) return true end

-- mw.title.new returns nil if there are weird chars in -- the pagename. local function exists(pagename) local title = mw.title.new(pagename) return title and title.exists end

local function add_suffix(list, suffix) local newlist = {} for _, item in ipairs(list) do		local form if suffix == "s" then if rfind(item, "[sx]$") then form = item elseif rfind(item, "al$") then form = rsub(item, "al$", "aux") else form = item .. suffix end elseif suffix == "e" then if rfind(item, "e$") then form = item elseif rfind(item, "en$") then form = item .. "ne" elseif rfind(item, "er$") then form = rsub(item, "er$", "ère") elseif rfind(item, "el$") then form = item .. "le" elseif rfind(item, "et$") then form = item .. "te" elseif rfind(item, "on$") then form = item .. "ne" elseif rfind(item, "ieur$") then form = item .. "e" elseif rfind(item, "teur$") then form = rsub(item, "teur$", "trice") elseif rfind(item, "eu[rx]$") then form = rsub(item, "eu[rx]$", "euse") elseif rfind(item, "if$") then form = rsub(item, "if$", "ive") elseif rfind(item, "c$") then form = rsub(item, "c$", "que") else form = item .. suffix end else form = item .. suffix end table.insert(newlist, form) end return newlist end

local no_split_words = { ["c'est"] = true, ["quelqu'un"] = true, ["aujourd'hui"] = true, }

-- Auto-add links to a "space word" (after splitting on spaces). We split off -- final punctuation, and then split on hyphens if split_dash is given, and -- also split on apostrophes, including the apostrophe in the link to its left -- (so we auto-split "l'eau" as "l'eau). local function add_space_word_links(space_word, split_dash)	local space_word_no_punct, punct = rmatch(space_word, "^(.*)([,;:?!])$")	space_word_no_punct = space_word_no_punct or space_word	punct = punct or ""	local words	-- don't split prefixes and suffixes	if not split_dash or rfind(space_word_no_punct, "^%-") or rfind(space_word_no_punct, "%-$") then		words = {space_word_no_punct}	else		words = rsplit(space_word_no_punct, "%-")	end	local linked_words = {}	for _, word in ipairs(words) do		if not no_split_words[word] and rfind(word, "'") then			word = rsub(word, "([^']+')", "%1")			word = rsub(word, "%]([^%[%]]*)$", "]%1")		else			word = "" .. word .. ""		end		table.insert(linked_words, word)	end	return table.concat(linked_words, "-") .. punct end

-- Auto-add links to a lemma. We split on spaces, and also on hyphens -- if split_dash is given or the word has no spaces. In addition, we split -- on apostrophes, including the apostrophe in the link to its left -- (so we auto-split "de l'eau" as "de l'eau"). We don't always -- split on hyphens because of cases like "boire du petit-lait" where -- "petit-lait" should be linked as a whole, but provide the option to do it -- for cases like "croyez-le ou non". If there's no space, however, then -- it makes sense to split on hyphens by default (e.g. for "avant-avant-hier"). -- Cases where only some of the hyphens should be split can always be handled -- by explicitly specifying the head (e.g. "Nord-Pas-de-Calais"). local function add_lemma_links(lemma, split_dash) if not rfind(lemma, " ") then split_dash = true end local words = rsplit(lemma, " ") local linked_words = {} for _, word in ipairs(words) do		table.insert(linked_words, add_space_word_links(word, split_dash)) end local retval = table.concat(linked_words, " ") -- If we ended up with a single link consisting of the entire lemma, -- remove the link. local unlinked_retval = rmatch(retval, "^%[%[([^%[%]]*)%]%]$") return unlinked_retval or retval end

-- The main entry point. -- This is the only function that can be invoked from a template. function export.show(frame) local PAGENAME = mw.title.getCurrentTitle.text local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.") local params = { ["head"] = {list = true}, ["suff"] = {type = "boolean"}, ["splitdash"] = {type = "boolean"}, }

if rfind(PAGENAME, " ") then track("space") end

if pos_functions[poscat] then for key, val in pairs(pos_functions[poscat].params) do			params[key] = val end end

local parargs = frame:getParent.args local args = require("Module:parameters").process(parargs, params)

local heads = args["head"] if pos_functions[poscat] and pos_functions[poscat].param1_is_head and args[1] then -- FIXME! REMOVE ME! Hack to correspond to previous template code in. if args[1] == "-" then track("arg1dash") else table.insert(heads, 1, args[1]) end end local auto_linked_head = add_lemma_links(PAGENAME, args["splitdash"]) if #heads == 0 then heads = {auto_linked_head} else for _, head in ipairs(heads) do			if head == auto_linked_head then track("redundant-head") end end end local data = {lang = lang, pos_category = poscat, categories = {}, heads = heads, genders = {}, inflections = {}, categories = {}} if args["suff"] then data.pos_category = "suffixes" if suffix_categories[poscat] then local singular_poscat = poscat:gsub("s$", "") table.insert(data.categories, lang:getCanonicalName .. " " .. singular_poscat .. "-forming suffixes") else error("No category exists for suffixes forming " .. poscat .. ".") end end if pos_functions[poscat] then pos_functions[poscat].func(args, data) end return require("Module:headword").full_headword(data) end

local allowed_genders = { ["m"] = true, ["f"] = true, ["m-p"] = true, ["f-p"] = true, ["m-s"] = true, ["f-s"] = true, }

local function get_noun_pos(is_proper) return { params = { [1] = {},			["g"] = {list = true}, [2] = {list = true}, ["f"] = {list = true}, ["m"] = {list = true}, ["dim"] = {list = true}, },		func = function(args, data) local PAGENAME = mw.title.getCurrentTitle.text local function default_plural if rfind(PAGENAME, 'x$') then track("default-x") end if rfind(PAGENAME, 'z$') then track("default-z") end if rfind(PAGENAME, '[sxz]$') then return PAGENAME elseif rfind(PAGENAME, '[ae]u$') then return "x" elseif rfind(PAGENAME, 'al$') then return mw.ustring.sub(PAGENAME, 1, -3) .. 'aux' else return "s" end end

-- Gather genders local function insert_gender(g) if g == "mf" then table.insert(data.genders, "m") table.insert(data.genders, "f") else table.insert(data.genders, g)				end end insert_gender(args[1]) for _, g in ipairs(args.g) do				insert_gender(g) end

-- Gather all the plural parameters from the numbered parameters. local plurals = args[2] plurals.label = "plural" plurals.accel = {form = "p"} plurals.request = true -- Gather all the feminine parameters local feminines = args["f"] feminines.label = "feminine" -- Gather all the masculine parameters local masculines = args["m"] masculines.label = "masculine" -- Add categories for genders if #data.genders == 0 then table.insert(data.genders, "?") end local mode = nil for _, g in ipairs(data.genders) do				if g == "m-p" or g == "f-p" then mode = "p" end

if g == "?" and (is_proper or mw.title.getCurrentTitle.nsText == "Template") then -- allow unknown gender in template example and proper nouns, -- since there are currently so many proper nouns with -- unspecified gender elseif g and g ~= "" and not allowed_genders[g] then error("Unrecognized French gender: " .. g)				end

if g == "m" or g == "m-p" or g == "m-s" then table.insert(data.categories, "French masculine nouns") elseif g == "f" or g == "f-p" or g == "f-s" then table.insert(data.categories, "French feminine nouns") end end -- Decide how to show the plurals mode = mode or plurals[1] -- Plural is not attested if mode == "!" then table.insert(data.inflections, {label = "plural not attested"}) table.insert(data.categories, "French nouns with unattested plurals") -- Plural-only noun, doesn't have a plural elseif mode == "p" then table.insert(data.inflections, {label = "plural only"}) table.insert(data.categories, "French pluralia tantum") else -- Plural is unknown if mode == "?" then table.remove(plurals, 1) -- Remove the mode parameter -- Uncountable noun; may occasionally have a plural elseif mode == "-" then table.remove(plurals, 1) -- Remove the mode parameter table.insert(data.categories, "French uncountable nouns") -- If plural forms were given explicitly, then show "usually" if #plurals > 0 then track("count-uncount") table.insert(data.inflections, {label = "usually uncountable"}) table.insert(data.categories, "French countable nouns") else table.insert(data.inflections, {label = "uncountable"}) end -- Mixed countable/uncountable noun, always has a plural elseif mode == "~" then table.remove(plurals, 1) -- Remove the mode parameter table.insert(data.inflections, {label = "countable and uncountable"}) table.insert(data.categories, "French uncountable nouns") table.insert(data.categories, "French countable nouns") -- If no plural was given, add a default one now if #plurals == 0 then table.insert(plurals, default_plural) end -- Default proper noun; uncountable unless plural(s) specified elseif is_proper then if #plurals > 0 then table.insert(data.categories, "French countable nouns") else table.insert(data.categories, "French uncountable nouns") end -- The default, always has a plural else table.insert(data.categories, "French countable nouns") -- If no plural was given, add a default one now if #plurals == 0 then table.insert(plurals, default_plural) end end -- Process the plural forms for i, pl in ipairs(plurals) do					if pl == "*" then pl = PAGENAME elseif pl == "s" then pl = PAGENAME .. "s" elseif pl == "x" then pl = PAGENAME .. "x" end

if not exists(pl) then table.insert(data.categories, "French nouns with missing plurals") end plurals[i] = pl				end -- Add the plural forms; do this in some cases even if no plurals -- specified so we get a "please provide plural" message. if mode ~= "-" and (not is_proper or mode) or #plurals > 0 then table.insert(data.inflections, plurals) end end -- Add the feminine forms if #feminines > 0 then table.insert(data.inflections, feminines) for _, f in ipairs(feminines) do					if not exists(f) then table.insert(data.categories, "French nouns with missing forms") end end end -- Add the masculine forms if #masculines > 0 then table.insert(data.inflections, masculines) for _, m in ipairs(masculines) do					if not exists(m) then table.insert(data.categories, "French nouns with missing forms") end end end

-- Handle diminutives if #args.dim > 0 then local dims_infl = mw.clone(args.dim) dims_infl.label = "diminutive" dims_infl.accel = {form = "diminutive"} table.insert(data.inflections, dims_infl) end end } end

pos_functions["nouns"] = get_noun_pos(false)

pos_functions["proper nouns"] = get_noun_pos(true)

pos_functions["pronouns"] = { params = { ["head"] = {list = true}, [1] = {alias_of = "g"}, ["g"] = {list = true}, ["f"] = {list = true}, ["fqual"] = {list = true, allow_holes = true}, ["m"] = {list = true}, ["mqual"] = {list = true, allow_holes = true}, ["fp"] = {list = true}, ["fpqual"] = {list = true, allow_holes = true}, ["mp"] = {list = true}, ["mpqual"] = {list = true, allow_holes = true}, ["p"] = {list = true}, ["pqual"] = {list = true, allow_holes = true}, },	func = function(args, data) local PAGENAME = mw.title.getCurrentTitle.text -- Gather genders local function insert_gender(g) if g == "mf" then table.insert(data.genders, "m") table.insert(data.genders, "f") else table.insert(data.genders, g)			end end for _, g in ipairs(args.g) do			insert_gender(g) end

local function process_inflection(label, infls, quals) infls.label = label for i, infl in ipairs(infls) do				if quals[i] then infls[i] = {term = infl, qualifiers = {quals[i]}} end end end

-- Gather all inflections. process_inflection("masculine", args["m"], args["mqual"]) process_inflection("feminine", args["f"], args["fqual"]) process_inflection("masculine plural", args["mp"], args["mpqual"]) process_inflection("feminine plural", args["fp"], args["fpqual"]) process_inflection("plural", args["p"], args["pqual"])

-- Add categories for genders if #data.genders == 0 then table.insert(data.genders, "?") end

-- Validate/canonicalize genders for i, g in ipairs(data.genders) do if g == "m." then data.genders[i] = "m" elseif g == "f." then data.genders[i] = "f" elseif g == "?" and mw.title.getCurrentTitle.nsText == "Template" then -- allow unknown gender in template example elseif g == "?" then -- FIXME, remove this branch once we've added the required genders track("missing-pron-gender") elseif g and g ~= "" and not allowed_genders[g] then error("Unrecognized French gender: " .. g)			end end

-- Add the inflections if #args["m"] > 0 then table.insert(data.inflections, args["m"]) end if #args["f"] > 0 then table.insert(data.inflections, args["f"]) end if #args["mp"] > 0 then table.insert(data.inflections, args["mp"]) end if #args["fp"] > 0 then table.insert(data.inflections, args["fp"]) end if #args["p"] > 0 then table.insert(data.inflections, args["p"]) end end }

local function get_misc_pos return { param1_is_head = true, params = { [1] = {},		},		func = function(args, data) end } end

pos_functions["adverbs"] = get_misc_pos pos_functions["prepositions"] = get_misc_pos

pos_functions["phrases"] = get_misc_pos

pos_functions["prepositional phrases"] = get_misc_pos

pos_functions["proverbs"] = get_misc_pos

pos_functions["punctuation marks"] = get_misc_pos

pos_functions["diacritical marks"] = get_misc_pos

pos_functions["interjections"] = get_misc_pos

pos_functions["prefixes"] = get_misc_pos

pos_functions["abbreviations"] = get_misc_pos

pos_functions["adjectives"] = { params = { [1] = {},		["inv"] = {}, ["m2"] = {}, ["onlyg"] = {}, ["f"] = {list = true}, ["mp"] = {list = true}, ["fp"] = {list = true}, ["p"] = {list = true}, ["current"] = {list = true}, ["comp"] = {list = true}, ["sup"] = {list = true}, },	func = function(args, data) local PAGENAME = mw.title.getCurrentTitle.text if args.onlyg == "p" or args.onlyg == "m-p" or args.onlyg == "f-p" then table.insert(data.categories, "French pluralia tantum") end if args.onlyg == "s" or args.onlyg == "f-s" or args.onlyg == "f-s" then table.insert(data.categories, "French singularia tantum") end if args.onlyg then table.insert(data.categories, "French defective adjectives") end if args.onlyg == "p" then table.insert(data.inflections, {label = "plural only"}) if args[1] ~= "mf" then -- Handle feminine plurals if #args.fp > 0 then local fplurals_infl = mw.clone(args.fp) fplurals_infl.label = "feminine plural" fplurals_infl.accel = {form = "f|p"} table.insert(data.inflections, fplurals_infl) end end elseif args.onlyg == "s" then table.insert(data.inflections, {label = "singular only"}) if not (args[1] == "mf" or #args.f == 0 and rfind(PAGENAME, "e$")) then -- Handle feminines local feminines = #args.f > 0 and args.f or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "e") local feminines_infl = mw.clone(feminines) feminines_infl.label = "feminine singular" feminines_infl.accel = {form = "f|s"} table.insert(data.inflections, feminines_infl) end elseif args.onlyg == "m" then table.insert(data.genders, "m") table.insert(data.inflections, {label = "masculine only"}) -- Handle masculine plurals local mplurals = #args.mp > 0 and args.mp or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "s") local mplurals_infl = mw.clone(mplurals) mplurals_infl.label = "masculine plural" mplurals_infl.accel = {form = "m|p"} table.insert(data.inflections, mplurals_infl) elseif args.onlyg == "f" then table.insert(data.genders, "f") table.insert(data.inflections, {label = "feminine only"}) -- Handle feminine plurals local fplurals = #args.fp > 0 and args.fp or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "s") local fplurals_infl = mw.clone(fplurals) fplurals_infl.label = "feminine plural" fplurals_infl.accel = {form = "f|p"} table.insert(data.inflections, fplurals_infl) elseif args.onlyg then table.insert(data.genders, args.onlyg) table.insert(data.inflections, {label = "defective"}) else -- Gather genders local gender = args[1] -- Default to mf if base form ends in -e and no feminine, -- feminine plural or gender specified if not gender and #args.f == 0 and #args.fp == 0 and rfind(PAGENAME, "e$") then gender = "mf" end if #args.current > 0 then track("adj-current") end if args.inv then table.insert(data.inflections, {label = "invariable"}) end -- Handle plurals of mf adjectives local plurals = #args.p > 0 and args.p or {PAGENAME .. "s"} if not args.inv and gender == "mf" then local plurals_infl = mw.clone(plurals) plurals_infl.label = "plural" plurals_infl.accel = {form = "p"} table.insert(data.inflections, plurals_infl) end if not args.inv and gender ~= "mf" then -- Handle case of special masculine singular before vowel if args.m2 then local masc_before_vowel = {args.m2} masc_before_vowel.label = "masculine singular before vowel" masc_before_vowel.accel = {form = "m|s"} table.insert(data.inflections, masc_before_vowel) end -- Handle feminines local feminines = #args.f > 0 and args.f or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "e") local feminines_infl = mw.clone(feminines) feminines_infl.label = "feminine singular" feminines_infl.accel = {form = "f|s"} table.insert(data.inflections, feminines_infl) -- Handle masculine plurals local mplurals = #args.mp > 0 and args.mp or add_suffix(#args.current > 0 and args.current or {PAGENAME}, "s") local mplurals_infl = mw.clone(mplurals) mplurals_infl.label = "masculine plural" mplurals_infl.accel = {form = "m|p"} table.insert(data.inflections, mplurals_infl) -- Handle feminine plurals local fplurals = #args.fp > 0 and args.fp or add_suffix(feminines, "s") local fplurals_infl = mw.clone(fplurals) fplurals_infl.label = "feminine plural" fplurals_infl.accel = {form = "f|p"} table.insert(data.inflections, fplurals_infl) end end -- Handle comparatives if #args.comp > 0 then local comps_infl = mw.clone(args.comp) comps_infl.label = "comparative" comps_infl.accel = {form = "comparative"} table.insert(data.inflections, comps_infl) end -- Handle superlatives if #args.sup > 0 then local sups_infl = mw.clone(args.sup) sups_infl.label = "superlative" sups_infl.accel = {form = "superlative"} table.insert(data.inflections, sups_infl) end -- Check existence for key, val in pairs(data.inflections) do			for i, form in ipairs(val) do				if not exists(form) then table.insert(data.categories, "French adjectives with missing forms") return end end end end }

pos_functions["verbs"] = { param1_is_head = true, params = { [1] = {},		["type"] = {list = true}, },	func = function(args, data) local PAGENAME = mw.title.getCurrentTitle.text for _, ty in ipairs(args.type) do			local category, label if ty == "auxiliary" then category = "auxiliary verbs" elseif ty == "defective" then category = "defective verbs" label = "defective" elseif ty == "impersonal" then category = "impersonal verbs" label = "impersonal" elseif ty == "modal" then category = "modal verbs" elseif ty == "reflexive" then category = "reflexive verbs" elseif ty == "transitive" then label = "transitive" elseif ty == "intransitive" then label = "intransitive" elseif ty == "ambitransitive" or ty == "ambi" then label = "transitive and intransitive" end if category then table.insert(data.categories, "French " .. category) end if label then table.insert(data.inflections, {label = label}) end end end }

return export

-- For Vim, so we get 4-space tabs -- vim: set ts=4 sw=4 noet: