Bước tới nội dung

Mô đun:fr-verb

Từ điển mở Wiktionary
--[=[

Author: originally Kc kennylau; rewritten by Benwing

This implements {{fr-conj-auto}}. It uses the following submodules:
* [[Module:fr-verb/core]] (helper for generating conjugations)
* [[Module:fr-verb/pron]] (helper for generating pronunciations of conjugations)
* [[Module:fr-conj]] (for constructing the table wikicode given the forms)
* [[Module:fr-pron]] (for generating pronunciations of stems)

FIXME:

1. (DONE) Use ‿ to join reflexive pronouns.
2. montre-toi needs a schwa in it.
3. (DONE) 'etre' and 'avoir_or_etre' tables should be moved to the template call.
3a. (DONE) Make sure aux= is supported at the template level.
4. Implement 'aspirated h'; not all vowel-initial verbs have elision with
   reflexive pronouns.
5. (REMOVED THE ARGUMENTS, NOT USED) Document the various override arguments.
6. Implement conjugation for -éyer.
7. (MAYBE? MAYBE NOT NECESSARY, {{fr-conj-ir}} doesn't seem to use it,
   MAYBE ALREADY DONE IN THE HEADWORD CODE?) Implement sort= for sort key,
   and handle most cases automatically (e.g. chérir with sort=cherir).
8. (DONE) Copy notes from {{fr-conj-ir}} to our conj["ir"].
9. (DONE) Lots of other conjugations needed. Consider generalizing existing code
   so a minimal number of principal parts can be given and all the conjugation
   and pronunciation derived.
10. (DONE) Convert remaining use of old templates to use {{fr-conj-auto}}.
11. (DONE) Figure out what the COMBINING flag in [[Module:fr-pron]] does and
	remove it, including all calls from this module.
12. (ALREADY DONE) Support sevrer, two-stem e/è verb.
13. (DONE) Autodetect e-er verbs including eCer as well as eCler and eCrer verbs
	like sevrer, and eguer/equer (if they exist). Make sure there aren't
	verbs of this form that aren't e-er by looking for them in the list of
	fr-conj-auto verbs that have an empty typ arg (possibly enough to look
	at all fr-conj-auto verbs).
14. Check pronunciation of 'pleuvoir'. TLFi says /pløvwaʁ/, frwikt says /plœvwaʁ/.
15. (DONE) Check if -er-type conjugations of -aillir, -cueillir, braire are
	correct.
16. (DONE) Fix notes for prefixed croitre/croître verbs, based on the old-style
	templates.
17. (DONE) Implement impersonal and only-third verbs, including impers=
	and onlythird=.
18. (DONE) Fix schwa in -ayer, -eyer pronunciation and check other uses of
	ind_f() to see if they need a fut_stem_i.
19. Implement sort key in {{fr-verb}}. Should map accented letters to
	unaccented letters and rearrange "se regarded" to "regarded, se" and
	similarly for "s'infiltrer".
20. "se regarder" should have optional schwa in re-.

Remaining templates:

-- copier-coller: FIXME, eventually implement general support for verbs like this
--]=]

-- Table of exported functions.
local export = {}
-- Table of conjugation functions. The keys are verbal suffixes (e.g. "ir",
-- "iller") and the values are no-argument functions that apply to verbs whose
-- infinitive contains that suffix, unless the verb also matches a conjugation
-- corresponding to a longer suffix. The values take all info on the verb
-- from 'data' (see below) and set properties of 'data' to indicate the
-- verb forms and pronunciation.
local conj = {}

-- If not false, compare this module with new version of module to make
-- sure all conjugations and pronunciations are the same. If "error", issue
-- an error whenever they are different, with the contents of the error
-- indicating the different forms; otherwise, use the tracking category
-- [[Template:tracking/fr-verb/different-conj]] (see what links there to see
-- the differing verbs; there's also [[Template:tracking/fr-verb/same-conj]]
-- for the verbs that don't differ, which can be used to verify that all verbs
-- have been processed, as it takes awhile for this to happen).
local test_new_fr_verb_module = false

local m_core = require("Module:fr-verb/core")
local m_pron = require("Module:fr-verb/pron")
local m_links = require("Module:links")
local m_conj = require("Module:fr-conj")
local m_fr_pron = require("Module:fr-pron")
local lang = require("Module:languages").getByCode("fr")
local m_table = require("Module:table")
local m_utilities = require("Module:utilities")
local m_debug = require("Module:debug")

local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len

local written_vowel = "aàâeéèêiîoôuûäëïöüÿ"
local written_cons_c = "[^%-" .. written_vowel .. "]"
local written_cons_no_y_c = "[^%-y" .. written_vowel .. "]"
local written_cons_no_cgy_c = "[^%-cgy" .. written_vowel .. "]"
local written_cons_no_cgyx_c = "[^%-cgyx" .. written_vowel .. "]"
local written_cons_no_lryx_c = "[^%-lryx" .. written_vowel .. "]"

-- 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

-- Map a function over one of the following:
-- (1) a single string (return value will be FUN(STRING))
-- (2) a list SEQ of either strings or tables of the form {"STEM", respelling="RESPELLING"}; the return value is a
--     list of calls to FUN, with one element per element in SEQ, flattened if DO_FLATMAP is specified and the return
--     value of FUN is a list; if an element of SEQ is a string, the corresponding return value will be FUN(STRING);
--     if an element of SEQ is a table of the above form, the corresponding return value will be FUN("STEM"), unless
--     third arg USE_RESPELLING is given, in which case the corresponding return value will be FUN("RESPELLING").
local function map(seq, fun, use_respelling, do_flatmap)
	if type(seq) == "table" then
		local ret = {}
		for _, s in ipairs(seq) do
			local single_stem_or_respelling
			if type(s) == "table" then
				if use_respelling then
					assert(s.respelling)
					s = s.respelling
				else
					s = s[1]
				end
			end
			local retval = fun(s)
			if do_flatmap and type(retval) == "table" then
				for _, item in ipairs(retval) do
					m_table.insertIfNot(ret, item)
				end
			else
				m_table.insertIfNot(ret, retval)
			end
		end
		return ret
	else
		-- store in separate var in case fun() has multiple retvals
		local retval = fun(seq)
		return retval
	end
end

local function IPA(str)
	return require("Module:IPA").format_IPA(nil, str)
end

local function pron(str)
	return m_fr_pron.show(str, "v")
end

local function dopron(data, stem, suffix)
	suffix = suffix or ""
	return map(stem, function(s)
		return pron((data and data.pronstem or "") .. s .. suffix)
	end, "respelling", "flatmap")
end

local function setform(data, form, val, pron)
	data.forms[form] = val
	data.prons[form] = dopron(data, pron or val)
end

local function copyform(data, fromform, toform, newformval)
	-- Need to clone objects when copying forms, otherwise we get duplicated prefixes added in case of
	-- prefixed verbs.
	data.forms[toform] = newformval or mw.clone(data.forms[fromform])
	data.prons[toform] = mw.clone(data.prons[fromform])
end

local all_verb_props = {
	"inf", "pp", "ppr",
	"inf_nolink", "pp_nolink", "ppr_nolink",
	"ind_p_1s", "ind_p_2s", "ind_p_3s", "ind_p_1p", "ind_p_2p", "ind_p_3p",
	"ind_i_1s", "ind_i_2s", "ind_i_3s", "ind_i_1p", "ind_i_2p", "ind_i_3p",
	"ind_ps_1s", "ind_ps_2s", "ind_ps_3s", "ind_ps_1p", "ind_ps_2p", "ind_ps_3p",
	"ind_f_1s", "ind_f_2s", "ind_f_3s", "ind_f_1p", "ind_f_2p", "ind_f_3p",
	"cond_p_1s", "cond_p_2s", "cond_p_3s", "cond_p_1p", "cond_p_2p", "cond_p_3p",
	"sub_p_1s", "sub_p_2s", "sub_p_3s", "sub_p_1p", "sub_p_2p", "sub_p_3p",
	"sub_pa_1s", "sub_pa_2s", "sub_pa_3s", "sub_pa_1p", "sub_pa_2p", "sub_pa_3p",
	"imp_p_2s", "imp_p_1p", "imp_p_2p"
}

-- Table mapping verb suffixes to other verb suffixes that they are
-- conjugated the same as. Only required when there is a shorter-length
-- suffix of the verb that has a different conjugation (in this case,
-- 'naitre' and 'naître').
local alias = {
	["connaitre"] = "aitre",
	["connaître"] = "aître",
}

-- List of -ir verbs that do not take -iss- infix.
local ir_s = {
	"dormir", "endormir", "redormir", "rendormir",
	"partir", "départir", "repartir",
	"sortir", "ressortir",
	"sentir", "assentir", "consentir", "pressentir", "ressentir",
	"mentir", "démentir",
	"servir", "desservir", "resservir",
	"repentir"
}
for _,key in ipairs(ir_s) do
	ir_s[key] = true
end

local function link(term, alt)
	return m_links.full_link({lang = lang, term = term, alt = alt}, "term")
end

-- Clone parent's args while also assigning nil to empty strings.
local function clone_args(frame)
	local args = {}
	for pname, param in pairs(frame:getParent().args) do
		if param == "" then args[pname] = nil
		else args[pname] = param
		end
	end
	return args
end

local function track(page)
	m_debug.track("fr-verb/" .. page)
	return true
end

local function unsupported_pron(data)
	if data.pron then
		error("Pronunciation respelling (pron=) not supported for this verb")
	end
end

-- Remove the expected ending ENDING from IPA pronunciation PRON (possibly
-- nil); error if ending not present.
local function strip_pron_ending(pron, ending)
	if not pron then
		return nil
	end
	return map(pron, function(val)
		if not rfind(val, ending .. "$") then
			error('Internal error: expected pronunciation "' .. val .. '" to end with "' .. ending .. '"')
		end
		return rsub(val, ending .. "$", "")
	end)
end

-- Remove the expected ending ENDING from respelling pronunciation PRON
-- (possibly nil or a sequence); error if ending not present.
local function strip_respelling_ending(pron, ending)
	if not pron then
		return nil
	end
	return map(pron, function(val)
		if not rfind(val, ending .. "$") then
			error('Expected respelling "' .. val .. '" to end with "' .. ending .. '"')
		end
		return rsub(val, ending .. "$", "")
	end)
end

-- Remove the expected beginning BEGINNING from respelling pronunciation PRON
-- (possibly nil); error if beginning not present. If SPLIT, split the value
-- of PRON on comma, strip the beginning from each component, and paste
-- together.
local function strip_respelling_beginning(pron, beginning, split)
	if not pron then
		return nil
	end
	if split then
		local pronvals = rsplit(pron, ",")
		local stripped_pronvals = {}
		for _, pronval in ipairs(pronvals) do
			table.insert(stripped_pronvals, strip_respelling_beginning(pronval, beginning))
		end
		return table.concat(stripped_pronvals, ",")
	end
	if not rfind(pron, "^" .. beginning) then
		error('Expected respelling "' .. pron .. '" to begin with "' .. beginning .. '"')
	end
	return rsub(pron, "^" .. beginning, "")
end

-- Construct the pronunciation of all forms of an -er verb. PRONSTEM is the
-- pronunciation respelling of the stem (minus -er). If PRONSTEM_FINAL_FUT is
-- given, it is used in place of PRONSTEM for the forms without a pronounced
-- ending (i.e. 1s/2s/3s/3p present) and for the future and conditional; this
-- is used with two-stem verbs such as mener (with stems 'men' and 'mèn') and
-- céder (with stems 'céd' and 'cèd').
local function construct_er_pron(data, pronstem, pronstem_final_fut)
	pronstem_final_fut = pronstem_final_fut or pronstem
	pronstem = map(pronstem, function(stem) return data.pronstem .. stem end)
	pronstem_final_fut = map(pronstem_final_fut, function(stem)
		stem = data.pronstem .. stem
		-- In pronstem_final_fut, convert é+C in the last syllable to è even if
		-- the caller didn't do it. This is principally useful with pron=
		-- specifications, so that e.g. pron=blésser,blèsser works.
		stem = rsub(stem, "é(" .. written_cons_c .. "+)$", "è%1")
		return rsub(stem, "é([gq]u)$", "è%1")
	end)
	local stem_final = dopron(nil, pronstem_final_fut, "e")
	local stem_nonfinal = strip_pron_ending(dopron(nil, pronstem, "ez"), "e")
	local stem_nonfinal_i = strip_pron_ending(dopron(nil, pronstem, "iez"), "je")
	local stem_fut = strip_pron_ending(dopron(nil, pronstem_final_fut, "erez"), "e")
	local stem_fut_i = strip_pron_ending(dopron(nil, pronstem_final_fut, "eriez"), "je")
	return m_pron.er(data, stem_final, stem_nonfinal, stem_nonfinal_i,
		stem_fut, stem_fut_i)
end

local function make_passe_simple(data, past_stem, er_past)
	-- Passé simple
	if er_past then
		m_core.make_ind_ps_a(data, past_stem)
	else
		m_core.make_ind_ps(data, past_stem)
	end
	if past_stem ~= "—" then
		local past_stem_pron = dopron(data, past_stem)
		if er_past then
			m_pron.ind_ps_a(data, past_stem_pron)
		else
			m_pron.ind_ps(data, past_stem_pron)
		end
	end
end

local function make_future_conditional(data, fut_stem)
	-- Future/conditional
	if not fut_stem then
		fut_stem = rsub(data.forms.inf, "e$", "")
	end
	m_core.make_ind_f(data, fut_stem)
	if fut_stem ~= "—" then
		local fut_stem_pron = strip_pron_ending(dopron(data, fut_stem, "ez"), "e")
		-- If the future stem ends in -er, the schwa is optional in -erez but
		-- not in -eriez; examples are assaillir, cueillir, refaire, défaire,
		-- contrefaire, méfaire (the latter four have the future pronounced
		-- -fer-). Also, if the future stem ends in -Cr, there will be an
		-- extra syllable inserted before -ions, -iez.
		local fut_stem_pron_i = strip_pron_ending(dopron(data, fut_stem, "iez"), "je")
		m_pron.ind_f(data, fut_stem_pron, fut_stem_pron_i)
	end
end

-- Construct the conjugation and pronunciation of all forms of a non-er verb.
-- DATA holds the forms and pronunciations. The remaining args are stems:
--
-- * PRES_SG_STEM is used for pres indicative 1s/2s/3s and the imperative 2s;
-- * PRES_12P_STEM is used for pres indicative/imperative 1p/2p, the whole of the imperfect, the present participle,
--    and (unless PRES_SUBJ_STEM is given) the pres subjunctive 1p/2p;
-- * PRES_3P_STEM is used for pres indicative 3p and pres subjunctive 1s/2s/3s/3p;
-- * PAST_STEM is used for the past historic, the imperfect subjunctive and (unless PP is given) the past participle;
-- * FUT_STEM (which should end with 'r') is used for the future and conditional. If omitted, it is taken from the
--    infinitive minus any final -e.
-- * PP is the past participle. If omitted, if defaults to PAST_STEM.
-- * PRES_SUBJ_STEM if given overrides the present subjunctive stem.
-- * PRES_SUBJ_NONFINAL_STEM if given overrides the present subjunctive stem specifically for 1p/2p, defaulting to
--    PRES_SUBJ_STEM.
-- * ER_PRESENT, if true, specifies that the present singular follows an -er type of conjugation (endings -e, -es, -e
--    in place of -s, -s, -t). In this case, PRES_12P_STEM and PRES_3P_STEM are currently ignored. Normally, use
--    construct_non_er_conj_er_present() in place of this arg.
-- * ER_PAST, if true, specifies that the past historic and imperfect subjunctive follow an -er type of conjugation.
--
-- Any of the stem arguments may be "—" to indicate that it is missing, or a table, where each element can be a string
-- (a stem) or a table of the form {"STEM", RESPELLING="RESPELLING"}, specifying a stem to use for constructing the
-- verb forms and the corresponding respelling to use when constructing the pronunciation. This is used, for example,
-- in [[mourir]], [[courir]] and [[avoir]].
local function construct_non_er_conj(data, pres_sg_stem, pres_12p_stem, pres_3p_stem, past_stem, fut_stem, pp,
	pres_subj_stem, pres_subj_nonfinal_stem, er_present, er_past)
	if er_present then
		m_core.make_ind_p_e(data, pres_sg_stem)
	else
		m_core.make_ind_p(data, pres_sg_stem, pres_12p_stem, pres_3p_stem)
	end

	-- Most of the time it works to add 's' to produce the 1sg (it doesn't
	-- always work to use the stem directly, cf. apparais vs. apparai). But
	-- this fails with stems ending in -er, e.g 'resser-' from 'resservir',
	-- because the 'r' will be silent. In that case, we add 't' to produce
	-- the 3sg. We can't always add 't' because that will fail with e.g.
	-- 'ressen-' from 'ressentir', where the resulting '-ent' will be silent.
	if pres_sg_stem ~= "—" then
		if er_present then
			local stem_final_pron = dopron(data, pres_sg_stem, "e")
			local stem_nonfinal_pron = strip_pron_ending(dopron(data, pres_sg_stem, "ez"), "e")
			local stem_nonfinal_i_pron = strip_pron_ending(dopron(data, pres_sg_stem, "iez"), "je")
			m_pron.er(data, stem_final_pron, stem_nonfinal_pron,
				stem_nonfinal_i_pron)
		else
			local pres_sg_stem_pron = map(pres_sg_stem, function(stem)
				return rmatch(data.pronstem .. stem, "er$") and dopron(data, stem, "t") or dopron(data, stem, "s")
			end, "respelling", "flatmap")
			local pres_12p_stem_pron = strip_pron_ending(dopron(data, pres_12p_stem, "ez"), "e")
			local pres_3p_stem_pron = dopron(data, pres_3p_stem, "e")
			local pre_j_stem_pron = strip_pron_ending(dopron(data, pres_12p_stem, "iez"), "je")
			m_pron.ind_p(data, pres_sg_stem_pron, pres_12p_stem_pron, pres_3p_stem_pron, pre_j_stem_pron)
		end
	end

	make_passe_simple(data, past_stem, er_past)
	make_future_conditional(data, fut_stem)

	if pp then
		data.forms.pp = pp
		if pp ~= "—" then
			data.prons.pp = dopron(data, pp)
		end
	end

	if pres_subj_stem then
		m_core.make_sub_p(data, pres_subj_stem, pres_subj_nonfinal_stem)
		if pres_subj_stem ~= "—" then
			local pres_subj_pron1 = dopron(data, pres_subj_stem, "e")
			local pres_subj_pron2 = strip_pron_ending(dopron(data, pres_subj_nonfinal_stem or pres_subj_stem, "iez"), "je")
			m_pron.sub_p(data, pres_subj_pron1, pres_subj_pron2)
		end
	end
end

-- Construct the conjugation and pronunciation of all forms of a non-er verb
-- with an -er type of present (singular -e, -es, -e). DATA holds the forms
-- and pronunciations. The remaining args are stems:
--
-- * PRES_STEM is used for the whole of the present as well as the imperfect
--    indicative;
-- * PAST_STEM is used for the past historic and past participle;
-- * FUT_STEM (which should end with 'r') is used for the future and
--    conditional. If omitted, it is taken from the infinitive minus final -e.
-- * PP is the past participle. If omitted, if defaults to PAST_STEM.
-- * PRES_SUBJ_STEM if given overrides the present subjunctive stem.
-- * PRES_SUBJ_NONFINAL_STEM if given overrides the present subjunctive stem
--    specifically for 1p/2p, defaulting to PRES_SUBJ_STEM.
--
-- Any of the stem arguments may actually be a table of stems.
local function construct_non_er_conj_er_present(data, pres_stem, past_stem,
	fut_stem, pp, pres_subj_stem, pres_subj_nonfinal_stem)
	-- Specify the pp explicitly, explicitly defaulting to the past_stem,
	-- else it will end in -é.
	construct_non_er_conj(data, pres_stem, nil, nil, past_stem, fut_stem,
	pp or past_stem, pres_subj_stem, pres_subj_nonfinal_stem, "er-present")
end

local function impersonal_verb(data)
	for _, k in ipairs(all_verb_props) do
		if rmatch(k, "[12]") or rmatch(k, "3p") then
			data.forms[k] = "—"
		end
	end
end

local function only_third_verb(data)
	for _, k in ipairs(all_verb_props) do
		if rmatch(k, "[12]") then
			data.forms[k] = "—"
		end
	end
end

conj["er"] = function(data)
	if data.stem == "all" then
		data.stem = ""
		data.pronstem = strip_respelling_ending(data.pron, "aller") or data.stem
		conj["irreg-aller"](data)
		data.forms.inf = "aller"

		data.conjcat = "aller"
		data.cat = "suppletive"
	else
		m_core.make_ind_p_e(data, "")
		construct_er_pron(data, "")
		data.group = 1
		data.conjcat = "-er"
	end
end

conj["cer"] = function(data)
	m_core.make_ind_p_e(data, "c", "ç")

	construct_er_pron(data, "c")
	data.group = 1
	data.conjcat = "-cer"
end

conj["ger"] = function(data)
	m_core.make_ind_p_e(data, "g", "ge")

	construct_er_pron(data, "g")
	data.group = 1
	data.conjcat = "-ger"
end

conj["ayer"] = function(data)

	m_core.make_ind_p_e(data, {"ay", "ai"}, "ay", "ay")
	construct_er_pron(data, "ay", {"ay", "ai"})
	data.group = 1
	data.conjcat = "-ayer"
end

conj["eyer"] = function(data)
	m_core.make_ind_p_e(data, "ey")
	construct_er_pron(data, "ey", "ey")
	data.group = 1
	data.conjcat = "-eyer"
end

conj["yer"] = function(data)

	m_core.make_ind_p_e(data, "i", "y", "y")
	construct_er_pron(data, "y", "i")
	data.group = 1
	data.conjcat = "-yer"
end

conj["xxer"] = function(data)
	local newstem, consonant = rmatch(data.stem, "^(.*)e(" .. written_cons_c .. ")$")
	if not consonant then
		error("Stem '" .. data.stem .. "' should end with -e- + consonant")
	end
	data.forms.inf = "e" .. consonant .. "er" -- not xxer
	local origstem = data.stem
	data.stem = newstem
	data.pronstem = strip_respelling_ending(data.pron, data.forms.inf) or data.stem


	if rfind(origstem, "jet$") or rfind(origstem, "appel$") then
		m_core.make_ind_p_e(data, "e" .. consonant .. consonant,
			"e" .. consonant, "e" .. consonant)
	else
		m_core.make_ind_p_e(data, {"e" .. consonant .. consonant, "è" .. consonant},
			"e" .. consonant, "e" .. consonant)
	end
	construct_er_pron(data, "e" .. consonant, "e" .. consonant .. consonant)
	data.group = 1
	data.conjcat = "-xxer"
end

conj["e-er"] = function(data)
	local newstem, consonant = rmatch(data.stem, "^(.*)e(" .. written_cons_c .. "+)$")
	if not consonant then
		error("Stem '" .. data.stem .. "' should end with -e- + one or more consonants")
	end
	local stem = 'e' .. consonant
	local stem2 = 'è' .. consonant
	data.forms.inf = stem .. "er" -- not e-er
	local origstem = data.stem
	data.stem = newstem
	data.pronstem = strip_respelling_ending(data.pron, data.forms.inf) or data.stem


	m_core.make_ind_p_e(data, stem2, stem, stem)
	construct_er_pron(data, stem, stem2)
	data.group = 1
	data.conjcat = "-e-er"
end

conj["ecer"] = function(data)
	m_core.make_ind_p_e(data, "èc", "eç", "ec")
	construct_er_pron(data, "ec", "èc")
	data.group = 1
	data.conjcat = "-e-er"
end

conj["eger"] = function(data)
	m_core.make_ind_p_e(data, "èg", "ege", "eg")
	construct_er_pron(data, "eg", "èg")
	data.group = 1
	data.conjcat = "-e-er"
end

conj["é-er"] = function(data)
	local newstem, consonant = rmatch(data.stem, "^(.*)é(" .. written_cons_c .. "+)$")
	if not consonant then
		newstem, consonant = rmatch(data.stem, "^(.*)é([gq]u)$")
	end
	if not consonant then
		error("Stem '" .. data.stem .. "' should end with -e- + one or more consonants")
	end
	local stem = 'é' .. consonant
	local stem2 = 'è' .. consonant
	data.forms.inf = stem .. "er" -- not é-er
	local origstem = data.stem
	data.stem = newstem
	data.pronstem = strip_respelling_ending(data.pron, data.forms.inf) or data.stem

	m_core.make_ind_p_e(data, stem2, stem, stem)
	m_core.make_ind_f(data, {stem2 .. "er", stem .. "er"})
	construct_er_pron(data, stem, stem2)
	data.group = 1
	data.conjcat = "-é-er"
end

conj["écer"] = function(data)

	m_core.make_ind_p_e(data, "èc", "éç", "éc")
	m_core.make_ind_f(data, {"écer", "ècer"})
	construct_er_pron(data, "éc", "èc")
	data.group = 1
	data.conjcat = "-é-er"
end

conj["éger"] = function(data)

	m_core.make_ind_p_e(data, "èg", "ége", "ég")
	m_core.make_ind_f(data, {"éger", "èger"})
	construct_er_pron(data, "ég", "èg")
	data.group = 1
	data.conjcat = "-é-er"
end

conj["ir-s"] = function(data)
	local ending = usub(data.stem, -1, -1)
	data.stem = usub(data.stem, 1, -2)
	data.pronstem = strip_respelling_ending(data.pron, ending .. "ir") or data.stem

	data.forms.inf = ending .. "ir"
	construct_non_er_conj(data, "", ending, ending, ending .. "i")
	data.conjcat = "-ir"
end

conj["ir-reg"] = function(data)
	-- if ir-reg explicitly used in type argument (e.g. ressortir), inf will
	-- be ir-reg by default with messed-up future
	data.forms.inf = "ir"
	construct_non_er_conj(data, "i", "iss", "iss", "i")

	data.group = 2
	data.conjcat = "-ir"
end

conj["ir"] = function(data)
	if ir_s[data.stem.."ir"] then
		conj["ir-s"](data)
	else
		conj["ir-reg"](data)
	end
end

conj["ïr"] = function(data)
	construct_non_er_conj(data, "ï", "ïss", "ïss", "ï")
	data.group = 2
	data.conjcat = "-ïr"
end

conj["haïr"] = function(data)

	construct_non_er_conj(data, "hai", "haïss", "haïss", "haï")
	data.conjcat = "haïr"
end

conj["ouïr"] = function(data)

	construct_non_er_conj(data, {"ouï", "oi"}, {"ouïss", {"oy", respelling="oill"}},
		{"ouïss", "oi"}, "ouï", {"ouïr", "oir", "orr"})
	data.conjcat = "ouïr"
end

conj["asseoir"] = function(data)

	construct_non_er_conj(data, {"assoi", "assied"}, {"assoy", "assey"},
		{"assoi", "assey"}, "assi", {"assoir", "assiér"}, "assis")
	data.conjcat = "seoir"
end

conj["surseoir"] = function(data)
	-- Pronunciation in future/cond as if written sursoir- not surseoir-
	construct_non_er_conj(data, "sursoi", "sursoy", "sursoi", "sursi", {{"surseoir", respelling="sursoir"}},
		"sursis")
	data.conjcat = "seoir"
end

conj["seoir"] = function(data)

	construct_non_er_conj(data, "sied", "sey", "sié", "—", "siér")
	only_third_verb(data)
	setform(data, "ppr", {"séant", "seyant"})
	data.conjcat = "seoir"
	data.cat = "defective"
end

conj["bouillir"] = function(data)
	construct_non_er_conj(data, "bou", "bouill", "bouill", "bouilli")
	data.conjcat = "bouillir"
end

conj["enir"] = function(data)
	construct_non_er_conj(data, "ien", "en", "ienn", "in", {{"iendr", respelling="iaindr"}}, "enu")

	if usub(data.stem,-1) == "t" then
		data.conjcat = "tenir"
	else
		data.conjcat = "venir"
	end
end

local function ouvrir_ffrir(data, rir_prefix)
	data.stem = data.stem .. rir_prefix
	data.pronstem = data.pronstem .. rir_prefix
	data.forms.inf = "rir"

	construct_non_er_conj_er_present(data, "r", "ri", nil, "ert")
end

conj["ouvrir"] = function(data)
	ouvrir_ffrir(data, "ouv")
end

conj["ffrir"] = function(data)
	ouvrir_ffrir(data, "ff")
end

conj["quérir"] = function(data)
	construct_non_er_conj(data, "quier", "quér", "quièr", "qui", "querr", "quis")
end

conj["aillir"] = function(data)
	construct_non_er_conj_er_present(data, "aill", "ailli", {"aillir", "ailler"})
end

conj["chauvir"] = function(data)

	construct_non_er_conj(data, "chauvi", {"chauv", "chauviss"},
		{"chauv", "chauviss"}, "chauvi")
	data.group = {2, 3}
end

conj["choir"] = function(data)
	construct_non_er_conj(data, "choi", "choy", "choi", "chu",
		data.stem == "é" and "choir" or {"choir", "cherr"})
	m_core.clear_imp(data)
	data.forms.ppr = "—"

	if data.stem == "" then
		-- FIXME! frwikt says 1p 2p of pres indic is rare, and likewise
		-- all of the pres subj.
		m_core.make_ind_i(data, "—")
		-- FIXME! frwikt says future in cherr- is archaic, and archaic
		-- conditional forms in cherr- exist as well.
		m_core.make_cond_p(data, "choir")
		m_pron.cond_p(data, dopron(data, "choir"))
		m_core.make_sub_p(data, "—")
		-- FIXME! frwikt does not say subjunctive past is missing other than
		-- 3s.
		m_core.make_sub_pa(data, "—")
		data.forms.sub_pa_3s = "chût"
	elseif data.stem == "dé" then
		m_core.make_ind_i(data, "—")
		-- FIXME! frwikt does not indicate 'chet' as an alternative. Based on
		-- échoir, we'd expect 'chettent' as alternative as well.
		setform(data, "ind_p_3s", {"choit", "chet"})
		-- FIXME! frwikt lists rare ppr déchoyant.
	elseif data.stem == "é" then
		only_third_verb(data)
		setform(data, "ind_p_3s", {"choit", "chet"})
		-- FIXME! frwikt gives both échettent and échéent as alternatives,
		-- but gives the pronunciation only of the first.
		setform(data, "ind_p_3p", {"choient", "chettent"})
		setform(data, "ppr", "chéant")
	end
	data.cat = "defective"
end

conj["cueillir"] = function(data)
	construct_non_er_conj_er_present(data, "cueill", "cueilli", {"cueillir", "cueiller"})
end

conj["courir"] = function(data)

	construct_non_er_conj(data, "cour", "cour", "cour", "couru", {{"courr", respelling="cour_r"}})
end

conj["falloir"] = function(data)
	construct_non_er_conj(data, "fau", "fall", "fall", "fallu", "faudr", nil, "faill")
	impersonal_verb(data)
	data.cat = {"defective", "impersonal"}
end

conj["faillir"] = function(data)
	if data.stem == "" then
		construct_non_er_conj(data, "fau", "faill", "faill", "failli", {"faudr", "faillir"})
		data.forms.ind_p_1s = "faux"
		data.forms.ind_p_2s = "faux"
		m_core.clear_imp(data)
		data.cat = "defective"
	else
		-- frwikt doesn't include forms like -faillerai
		construct_non_er_conj_er_present(data, "faill", "failli", "faillir")
	end
end

conj["férir"] = function(data)

	construct_non_er_conj(data, "—", "—", "—", "—", "—", "féru")
	data.cat = "defective"
end

conj["fuir"] = function(data)
	construct_non_er_conj(data, "fui", "fuy", "fui", "fui")
end

conj["gésir"] = function(data)
	construct_non_er_conj(data, "gi", "gis", "gis", "—", "—", "—", "—")
	data.forms.ind_p_3s = "gît"
	m_core.clear_imp(data)
	data.cat = "defective"
end

conj["re"] = function(data)
	construct_non_er_conj(data, "", "", "", "i", nil, "u")
	data.forms.ind_p_3s = ""
	data.irregular = "no"
end

conj["cre"] = function(data)

	construct_non_er_conj(data, "c", "qu", "qu", "qui", nil, "cu")
end

conj["pre"] = function(data)

	construct_non_er_conj(data, "p", "p", "p", "pi", nil, "pu")
end

conj["crire"] = function(data)
	construct_non_er_conj(data, "cri", "criv", "criv", "crivi", nil, "crit")
end

conj["rire"] = function(data)
	construct_non_er_conj(data, "ri", "ri", "ri", "ri")
end

conj["uire"] = function(data)
	construct_non_er_conj(data, "ui", "uis", "uis", "uisi", nil, "uit")
end

conj["nuire"] = function(data)
	-- nuire has different pp from other -uire verbs
	construct_non_er_conj(data, "nui", "nuis", "nuis", "nuisi", nil, "nui")
end

conj["aitre"] = function(data)

	-- future stem must be nil here because we are called from conj["aître"]
	construct_non_er_conj(data, "ai", "aiss", "aiss", "u", nil, "u")
end

conj["aître"] = function(data)
	conj["aitre"](data)
	data.forms.ind_p_3s = "aît"
end

conj["oître"] = function(data)

	construct_non_er_conj(data, {{"oi", respelling="ai"}}, {{"oiss", respelling="aiss"}},
		{{"oiss", respelling="aiss"}}, "u", {{"oîtr", respelling="aitr"}})
end

conj["indre"] = function(data)
	construct_non_er_conj(data, "in", "ign", "ign", "igni", nil, "int")
end

conj["clure"] = function(data)
	local pp
	if data.stem == "in" or data.stem == "trans" or data.stem == "oc" then
		pp = "clus"
	end

	construct_non_er_conj(data, "clu", "clu", "clu", "clu", nil, pp)
end

conj["raire"] = function(data) --braire, traire
	construct_non_er_conj(data, "rai", "ray", "rai", "ray", nil, "rait", nil, nil, nil, "er past")
end

conj["clore"] = function(data)

	construct_non_er_conj(data, "clo", "clos", "clos", "—", nil, "clos")
	m_core.make_ind_i(data, "—") -- no imperfect
	data.forms.ind_p_3s = "clôt"
	data.cat = "defective"
end

conj["confire"] = function(data)
	construct_non_er_conj(data, "confi", "confis", "confis", "confi", nil, "confit")
end

conj["suffire"] = function(data)
	construct_non_er_conj(data, "suffi", "suffis", "suffis", "suffi")
end

conj["coudre"] = function(data)

	construct_non_er_conj(data, "coud", "cous", "cous", "cousi", nil, "cousu")
end

conj["croire"] = function(data)
	construct_non_er_conj(data, "croi", "croy", "croi", "cru")
end

conj["croitre"] = function(data)
	if data.stem == "" then
		construct_non_er_conj(data, "croî", "croiss", "croiss", "crû")
		data.forms.ind_ps_1p = "crûmes"
		data.forms.ind_ps_2p = "crûtes"
		data.forms.sub_pa_3s = "crût"
	else
		construct_non_er_conj(data, "croi", "croiss", "croiss", "cru")
	end
end

conj["croître"] = function(data)
	if data.stem == "" or data.stem == "re" then
		construct_non_er_conj(data, "croî", "croiss", "croiss", "crû")
	else
		construct_non_er_conj(data, "croi", "croiss", "croiss", "cru")
		data.forms.ind_p_3s = "croît"
	end
	data.forms.ind_ps_1p = "crûmes"
	data.forms.ind_ps_2p = "crûtes"
	data.forms.sub_pa_3s = "crût"
end

conj["foutre"] = function(data)
	construct_non_er_conj(data, "fou", "fout", "fout", "fouti", nil,
		"foutu")
end

conj["soudre"] = function(data)
	construct_non_er_conj(data, "sou", "solv", "solv", "solu", nil, "sous")
	m_core.make_sub_pa(data, "—")
	data.cat = "defective"
end

conj["résoudre"] = function(data)
	construct_non_er_conj(data, "résou", "résolv", "résolv", "résolu")
end

conj["voir"] = function(data)
	construct_non_er_conj(data, "voi", "voy", "voi", "vi", "verr", "vu")
end

conj["prévoir"] = function(data)
	construct_non_er_conj(data, "prévoi", "prévoy", "prévoi", "prévi", nil,
		"prévu")
end

conj["cevoir"] = function(data)
	construct_non_er_conj(data, "çoi", "cev", "çoiv", "çu", "cevr")
end

conj["battre"] = function(data)

	construct_non_er_conj(data, "bat", "batt", "batt", "batti", nil, "battu")
end

conj["circoncire"] = function(data)
	construct_non_er_conj(data, "circonci", "circoncis", "circoncis",
		"circonci", nil, "circoncis")
end

conj["lire"] = function(data)
	construct_non_er_conj(data, "li", "lis", "lis", "lu")
end

conj["luire"] = function(data)
	construct_non_er_conj(data, "lui", "luis", "luis", {"lui", "luisi"},
		nil, "lui")
	m_core.make_sub_pa(data, "luisi")
	m_pron.sub_pa(data, dopron(data, "luisi"))
	setform(data, "ind_ps_3s", "luit")
end

conj["maudire"] = function(data)

	construct_non_er_conj(data, "maudi", "maudiss", "maudiss", "maudi",
		nil, "maudit")
end

conj["mettre"] = function(data)

	construct_non_er_conj(data, "met", "mett", "mett", "mi", nil, "mis")
end

conj["moudre"] = function(data)
	construct_non_er_conj(data, "moud", "moul", "moul", "moulu")
end

conj["mouvoir"] = function(data)
	construct_non_er_conj(data, "meu", "mouv", "meuv", "mu", "mouvr")
	if data.stem == "" then
		data.forms.pp = "mû"
	end
end

conj["paitre"] = function(data)
	construct_non_er_conj(data, "pai", "paiss", "paiss", "pu")
	--data.cat = "defective" -- FIXME: Not true with pu as past participle?
end

conj["paître"] = function(data)
	conj["paitre"](data)
	data.forms.ind_p_3s = "paît"
end

conj["pleuvoir"] = function(data)

	construct_non_er_conj(data, "pleu", "pleuv", "pleuv", "plu", "pleuvr")
	if data.stem == "re" then
		impersonal_verb(data)
		data.cat = {"defective", "impersonal"}
	else
		only_third_verb(data)
		data.cat = "defective"
	end
end

conj["pourvoir"] = function(data)

	construct_non_er_conj(data, "pourvoi", "pourvoy", "pourvoi", "pourvu")
end

conj["prendre"] = function(data)

	construct_non_er_conj(data, "prend", "pren", "prenn", "pri", nil, "pris")
end

conj["faire"] = function(data)
	construct_non_er_conj(data, "fai", {{"fais", respelling="fes"}}, {{"fais", respelling="fes"}}, "fi", "fer", "fait",
		"fass")
	setform(data, "ind_p_2p", "faites")
	setform(data, "ind_p_3p", "font")
	copyform(data, "ind_p_2p", "imp_p_2p")
end

conj["boire"] = function(data)
	construct_non_er_conj(data, "boi", "buv", "boiv", "bu")
end

conj["devoir"] = function(data)
	construct_non_er_conj(data, "doi", "dev", "doiv", "du", "devr")
	if data.stem == "" then
		data.forms.pp = "dû"
	end
end

conj["avoir"] = function(data)
	-- Need to specify a value for singular present to get proper pronunciation for present tense,
	-- even though we override it.
	construct_non_er_conj(data, "a", "av", "—", {{"eu", respelling="u"}}, {"aur", {"aur", respelling="ôr"}}, nil,
		"ai", "ay")
	setform(data, "ind_p_1s", "ai")
	setform(data, "ind_p_2s", "as", "a")
	setform(data, "ind_p_3s", "a")
	setform(data, "ind_p_3p", "ont")
	setform(data, "ppr", "ayant")
	data.forms.sub_p_3s = "ait"
	setform(data, "sub_p_1p", "ayons")
	setform(data, "sub_p_2p", "ayez")
	copyform(data, "sub_p_1s", "imp_p_2s")
	copyform(data, "sub_p_1p", "imp_p_1p")
	copyform(data, "sub_p_2p", "imp_p_2p")
end

conj["être"] = function(data)
	construct_non_er_conj(data, "e", "ét", "—", "fu", "ser", "été", "soi", "soy")
	setform(data, "ind_p_1s", "suis")
	setform(data, "ind_p_2s", "es")
	setform(data, "ind_p_3s", "est")
	setform(data, "ind_p_1p", "sommes")
	setform(data, "ind_p_2p", "êtes")
	setform(data, "ind_p_3p", "sont")

	setform(data, "sub_p_1s", "sois")
	copyform(data, "sub_p_1s", "sub_p_2s")
	copyform(data, "sub_p_1s", "sub_p_3s", "soit")
	setform(data, "sub_p_1p", "soyons")
	setform(data, "sub_p_2p", "soyez")

	copyform(data, "sub_p_2s", "imp_p_2s")
	copyform(data, "sub_p_1p", "imp_p_1p")
	copyform(data, "sub_p_2p", "imp_p_2p")
end

conj["estre"] = function(data)
	conj["être"](data)

	for key,val in pairs(data.forms) do
		data.forms[key] = map(val, function(form)
			form = rsub(form, "[éê]", "es")
			form = rsub(form, "û", "us")
			form = rsub(form, "ai", "oi")
			return form
		end)
	end

	data.forms.ind_ps_1p = "fumes"
	data.forms.sub_pa_3s = "fust"
	data.forms.pp = "esté"
end

conj["naitre"] = function(data)
	-- future stem must be nil here because we are called from conj["naître"]
	construct_non_er_conj(data, "nai", "naiss", "naiss", "naqui", nil, "né")
end

conj["naître"] = function(data)
	conj["naitre"](data)
	data.forms.ind_p_3s = "naît"
end

conj["envoyer"] = function(data)
	data.notes = "This verb is one of a few verbs that conjugate like " .. link("noyer") .. ", except in the future and conditional, where they conjugate like " .. link("voir") .. "."

	m_core.make_ind_p_e(data, "envoi", "envoy", "envoy")
	construct_er_pron(data, "envoy", "envoi")
	make_future_conditional(data, "enverr")
	data.group = 1
	data.irregular = "yes"
end

conj["irreg-aller"] = function(data)
	data.notes = "The verb ''{stem}aller'' has a unique and highly irregular conjugation. The second-person singular imperative ''[[va]]'' additionally combines with ''[[y]]'' to form ''[[vas-y]]'' instead of the expected ''va-y''."

	-- Need to specify a value for singular present to get proper pronunciation for present tense,
	-- even though we override it.
	construct_non_er_conj(data, "va", "all", "—", "all", "ir", "allé", "aill", "all", nil, "er past")
	setform(data, "ind_p_1s", "vais")
	-- TLFi says /va/ even for 2s, not /vɑ/.
	setform(data, "ind_p_2s", "vas", "va")
	setform(data, "ind_p_3s", "va")
	setform(data, "ind_p_3p", "vont")
	copyform(data, "ind_p_3s", "imp_p_2s")
end

conj["dire"] = function(data)
	construct_non_er_conj(data, "di", "dis", "dis", "di", nil, "dit")

	if data.stem == "" or data.stem == "re" then
		setform(data, "ind_p_2p", "dites")
		copyform(data, "ind_p_2p", "imp_p_2p")
	else
		data.notes = "This verb is one of a group of " .. link("-re") .. " verbs all ending in ''-dire''. "
		data.notes = data.notes .. "They are conjugated exactly like " .. link("dire") .. ", "
		data.notes = data.notes .. "but with a different second-person plural indicative present (that is, like " .. link("confire") .. "). "
		data.notes = data.notes .. "Members of this group include " .. link(data.stem == "contre" and "dédire" or "contredire") .. " and "
		data.notes = data.notes .. link(data.stem == "inter" and "dédire" or "interdire") .. "."
	end
end

conj["vivre"] = function(data)
	construct_non_er_conj(data, "vi", "viv", "viv", "vécu")
end

conj["mourir"] = function(data)
	construct_non_er_conj(data, "meur", "mour", "meur", "mouru", {{"mourr", respelling="mour_r"}}, "mort")
end

conj["savoir"] = function(data)
	construct_non_er_conj(data, "sai", "sav", "sav", "su", {"saur", {"saur", respelling="sôr"}}, nil, "sach")
	copyform(data, "sub_p_1s", "imp_p_2s") -- sache
	setform(data, "imp_p_1p", "sachons")
	setform(data, "imp_p_2p", "sachez")
	setform(data, "ppr", "sachant")
end

conj["pouvoir"] = function(data)
	construct_non_er_conj(data, "peu", "pouv", "peuv", "pu", "pourr", nil, "puiss")
	data.forms.ind_p_1s = "peux"
	data.forms.ind_p_2s = "peux"
	m_core.clear_imp(data)
	data.cat = "defective"
end

conj["ouloir"] = function(data) -- vouloir, revouloir, douloir
	construct_non_er_conj(data, "eu", "oul", "eul", "oulu", "oudr", nil, "euill", "oul")
	data.forms.ind_p_1s = "eux"
	data.forms.ind_p_2s = "eux"
	if data.stem == "v" then -- irregular imperative for vouloir
		setform(data, "imp_p_2s", {"eux", "euille"})
		setform(data, "imp_p_1p", {"oulons", "euillons"})
		setform(data, "imp_p_2p", {"oulez", "euillez"})
	else
		data.forms.imp_p_2s = "eux"
	end
end

conj["bruire"] = function(data)
	construct_non_er_conj(data, "bruis", "bruiss", "bruiss", "brui")
end

conj["ensuivre"] = function(data)
	data.notes = "This verb is [[defective]], and is only used in the "
		.. "infinitive and the third-person singular and plural forms."
	construct_non_er_conj(data, "ensui", "ensuiv", "ensuiv", "ensuivi")
	only_third_verb(data)
	data.cat = "defective"
end

conj["frire"] = function(data)
	data.notes = "This verb is defective and it is not conjugated in certain"
		.. " tenses and plural persons. Using " .. link("faire") ..
		" '''frire''' is recommended."
	construct_non_er_conj(data, "fri", "fris", "fris", "fri", nil, "frit")
	-- clear subjunctive present and past
	m_core.make_sub_pa(data, "—")
	m_core.make_sub_p(data, "—")
	-- clear plural forms
	for _, k in ipairs(all_verb_props) do
		if rmatch(k, "[123]p") then
			data.forms[k] = "—"
		end
	end
	data.cat = "defective"
end

conj["plaire"] = function(data)
	data.notes = link("plaire") .. " and its derived verbs conjugate like "
		.. link("taire") .. ", except that the third person singular of the "
		.. "present indicative may take a circumflex on the 'i'."
	construct_non_er_conj(data, "plai", "plais", "plais", "plu")
	data.forms.ind_p_3s = {"plaît", "plait"}
end

conj["suivre"] = function(data)
	construct_non_er_conj(data, "sui", "suiv", "suiv", "suivi")
end

conj["taire"] = function(data)
	construct_non_er_conj(data, "tai", "tais", "tais", "tu")
end

conj["valoir"] = function(data)
	construct_non_er_conj(data, "vau", "val", "val", "valu", "vaudr", nil,
		data.stem == "pré" and "val" or "vaill", "val")
	data.forms.ind_p_1s = "vaux"
	data.forms.ind_p_2s = "vaux"
	m_core.clear_imp(data)
	data.cat = "defective"
end

conj["vêtir"] = function(data)
	data.notes = "This is an irregular verb of the third conjugation. "
		.. "Unlike regular -ir verbs, this conjugation does not include "
		.. "the infix " .. link("-iss-") .. "."
	construct_non_er_conj(data, "vêt", "vêt", "vêt", "vêti", nil, "vêtu")
end

local function call_conj(data, conjtyp, pronstem)
	data.pronstem = pronstem or strip_respelling_ending(data.pron, data.forms.inf) or data.stem
	conj[conjtyp](data)
end

-- Conjugate the verb according to the TYPE, which is either explicitly
-- specified by the caller of {{fr-conj-auto}} or derived automatically.
local function conjugate(data, typ)
	data.forms.inf = typ
	local future_stem = rsub(data.forms.inf, "e$", "")
	m_core.make_ind_f(data, future_stem)

	if typ == "xxer" or typ == "e-er" or typ == "é-er" then
		call_conj(data, typ, strip_respelling_ending(data.pron, "er"))
		return
	end

	if alias[typ] then
		data.stem = data.stem .. rsub(typ, alias[typ] .. "$", "")
		data.forms.inf = alias[typ]
		call_conj(data, alias[typ])
	elseif conj[typ] then
		call_conj(data, typ)
	elseif typ ~= "" then
		error('The type "' .. typ .. '" is not recognized')
	end
end

-- Autodetect the conjugation type and extract the preceding stem. We have
-- special handling for verbs in -éCer and -eCer for C = consonant. Otherwise,
-- the conjugation type is the longest suffix of the infinitive for which
-- there's an entry in conj[], and stem is the preceding text. (As an
-- exception, certain longer suffixes are mapped to the conjugation type of
-- shorter suffixes using alias[]. An example is 'connaitre', which conjugates
-- like '-aitre' verbs rather than like 'naitre' and its derivatives.) Note
-- that for many irregular verbs, the "stem" is actually the prefix, or empty
-- if the verb has no prefix.
local function auto(pagename, argstype, argsstem)
	local stem

	if argstype then
		local tostrip
		if argstype == "xxer" or argstype == "é-er" or argstype == "e-er" then
			tostrip = "er"
		else
			tostrip = argstype
		end
		stem = argsstem or strip_respelling_ending(pagename, tostrip)
		return stem, argstype
	end

	-- check for espérer, céder, etc.; exclude y so as not to be confused by [[acétyler]], [[déméthyler]];
	-- exclude -écer, -éger, -éyer
	stem = rmatch(pagename, "^(.*é" .. written_cons_no_y_c .. "*" .. written_cons_no_cgy_c .. ")er$")
	if stem then
		return stem, "é-er"
	end
	-- check for alléguer, disséquer, etc.
	stem = rmatch(pagename, "^(.*é[gq]u)er$")
	if stem then
		return stem, "é-er"
	end
	-- check for acheter, etc.; exclude -exer, -ecer, -eger, -eyer
	stem = rmatch(pagename, "^(.*e" .. written_cons_no_cgyx_c .. ")er$")
	if stem then
		return stem, "e-er"
	end
	-- check for sevrer, etc.; exclude -ller, -rrer, -rler (perler)
	stem = rmatch(pagename, "^(.*e" .. written_cons_no_lryx_c .. "[lr])er$")
	if stem then
		return stem, "e-er"
	end
	stem = ""
	local typ = pagename
	while typ ~= "" do
		if conj[typ] then break end
		if alias[typ] then
			stem = stem .. rsub(typ,alias[typ].."$","")
			typ = alias[typ]
			break
		end
		stem = stem .. rsub(typ,"^(.).*$","%1")
		typ = rsub(typ,"^.","")
	end
	if typ == "" then
		return "",""
	end
	return stem,typ
end

-- Append elements of TAB2 to the elements of TAB1, converting them to lists
-- as necessary.
local function append_tables(tab1, tab2)
	for k, values in pairs(tab2) do
		local t1 = tab1[k]
		if type(t1) ~= "table" then
			t1 = {t1}
		end
		if type(values) ~= "table" then
			values = {values}
		end
		for _, val in ipairs(values) do
			m_table.insertIfNot(t1, val)
		end
		tab1[k] = t1
	end
end

local verb_prefix_to_type = {
	{"les y en ", "lesyen"},
	{"les en ", "lesen"},
	{"s[’']en ", "reflen"},
	{"se le ", "reflle"},
	{"se la ", "reflla"},
	{"se l[’']", "refll"},
	{"se les y ", "refllesy"},
	{"les y ", "lesy"},
	{"se les ", "reflles"},
	{"les ", "les"},
	{"se l[’']y ", "reflly"},
	{"l[’']y ", "l_y"},
	{"l[’']en ", "l_en"},
	{"l[’']", "l"},
	{"le ", "le"},
	{"la ", "la"},
	{"s[’']y en ", "reflyen"},
	{"y en ", "yen"},
	{"en ", "en"},
	{"s[’']y ", "refly"},
	{"y ", "y"},
	{"s[’']", "refl"},
	{"se ", "refl"},
}

-- This is meant to be invoked by the module itself, or possibly by a
-- different version of the module (for comparing changes to see whether
-- they have an effect on conjugations or pronunciations).
function export.do_generate_forms(args)
	local data
	local stem = args[1] or ""
	local typ = args[2] or ""
	local argspron = args.pron
	local prefix, preftype
	local pagename_from_args
	
	local PAGENAME = mw.title.getCurrentTitle().text

	if stem == "" and typ == "" then
		pagename_from_args = args.pagename or PAGENAME
	else
		error("Specifying 1= or 2= not supported any more; use type=, stem= and/or pagename=")
	end

	local argstype = args.type
	if argstype == "" then argstype = nil; end
	local argsstem = args.stem
	if argsstem == "" then argsstem = nil; end

	stem, typ = auto(pagename_from_args, argstype, argsstem)

	-- expand + and [...] notations
	if argspron then
		local pronvals = rsplit(argspron, ",")
		local expanded_pronvals = {}
		for _, pronval in ipairs(pronvals) do
			table.insert(expanded_pronvals, m_fr_pron.canonicalize_pron(pronval, pagename_from_args))
		end
		argspron = table.concat(expanded_pronvals, ",")
	end

	-- autodetect prefixed verbs
	for _, pref_and_type in ipairs(verb_prefix_to_type) do
		local pref, prefty = pref_and_type[1], pref_and_type[2]
		if rfind(stem, "^" .. pref) then
			stem = rsub(stem, "^" .. pref, "")
			argspron = strip_respelling_beginning(argspron, pref, "split")
			prefix = pref
			preftype = prefty
			break
		end
	end

	local pronargs = argspron and rsplit(argspron, ",") or {false}
	local all_forms, all_prons 
	for i = 1, #pronargs do
		local pronarg = pronargs[i]
		if pronarg == false then pronarg = nil end
		data = {
			prefix = prefix,
			preftype = preftype,
			stem = stem,
			aux = "avoir",
			pron = pronarg,
			forms = {},
			prons = {},
			cat = {},
			group = 3
		}
		conjugate(data, typ)
		if type(data.cat) ~= "table" then
			data.cat = {data.cat}
		end
		if i == 1 then
			all_forms = data.forms
			all_prons = data.prons
		else
			append_tables(all_forms, data.forms)
			append_tables(all_prons, data.prons)
		end
	end
	data.forms = all_forms
	data.prons = all_prons

	-- FIXME! From here on out we use the value of data.notes, data.stem
	-- and data.cat as set/modified in the conjugation functions of the last
	-- iteration of the loop above. As it happens, this doesn't matter
	-- because we iterate over pronunciations keeping the stem and conjugation
	-- type the same, but might matter one day if we break this assumption.

	-- FIXME, allow all overrides
	if args.inf then
		data.forms.inf = args.inf
	end

	if args.archaic then
		for k, v in pairs(data.forms) do
			data.forms[k] = map(v, function(val)
				val = rsub(val, "ai", "oi")
				val = rsub(val, "â", "as")
				return val end)
		end
	end

	if args.impers or args.onlythird then
		if data.notes then
			data.notes = data.notes .. "\n"
		else
			data.notes = ""
		end
		table.insert(data.cat, "defective")
	end
	if args.impers then
		data.notes = data.notes .. "This verb is impersonal and is conjugated only in the third-person singular."
		impersonal_verb(data)
		table.insert(data.cat, "impersonal")
	elseif args.onlythird then
		data.notes = data.notes .. "This verb is conjugated only in the third person."
		only_third_verb(data)
	end

	if args.note then
		if data.notes then
			data.notes = data.notes .. "\n"
		else
			data.notes = ""
		end
		data.notes = data.notes .. args.note
	end
	
	if data.notes then data.notes = rsub(data.notes, "{stem}", data.stem) end
	for key,val in pairs(data.forms) do
		if type(val) == "table" then
			for i,form in ipairs(val) do
				if form ~= "—" then
					if type(form) == "table" then
						error(("Internal error: Saw table value for key '%s': %s"):format(key, mw.dumpObject(form)))
					end
					data.forms[key][i] = data.stem .. form
				end
			end
		else
			if val ~= "—" then
				data.forms[key] = data.stem .. val
			end
		end
	end

	for _, pref_and_type in ipairs(verb_prefix_to_type) do
		local pref, prefty = pref_and_type[1], pref_and_type[2]
		if args[prefty] == "n" or args[prefty] == "no" then
			if data.preftype == prefty then
				data.preftype = nil
			end
		elseif args[prefty] then
			data.preftype = prefty
		end
	end

	if data.preftype then
		for key, val in pairs(data.forms) do
			m_core.pref_sufs[data.preftype](data, key, val)
		end
	end

	local aux_prefix = data.prefix or ""
	aux_prefix = rsub(aux_prefix, "l[ae] $", "l'")
	if args.aux == "a" or args.aux == "avoir" then
		data.aux = aux_prefix .. "avoir"
	elseif args.aux == "e" or args.aux == "être" then
		data.aux = aux_prefix .. "être"
	elseif args.aux == "ae" or args.aux == "avoir,être" or args.aux == "avoir or être" then
		data.aux = aux_prefix .. "avoir]] or [[être"
	elseif args.aux then
		error("Unrecognized value for aux=, should be 'a', 'e', 'ae', 'avoir', 'être', or 'avoir,être'")
	end

	data.forms.inf_nolink = data.forms.inf_nolink or data.forms.inf
	data.forms.ppr_nolink = data.forms.ppr_nolink or data.forms.ppr
	data.forms.pp_nolink = data.forms.pp_nolink or data.forms.pp

	if not data.irregular then
		if data.group == 1 or data.group == 2 then
			data.irregular = "no"
		else
			data.irregular = "yes"
		end
	end

	return data
end

function export.generate_forms(frame)
	local args = clone_args(frame)
	local data = export.do_generate_forms(args)
	local retval = {}
	for arraytype = 1, 2 do
		local arrayname = arraytype == 1 and "forms" or "prons"
		local array = data[arrayname]
		for _, prop in ipairs(all_verb_props) do
			local val = array[prop]
			if type(val) ~= "table" then val = {val} end
			local newval = {}
			for _, form in ipairs(val) do
				if not rmatch(form, "—") then
					table.insert(newval, form)
				end
			end
			-- Ignore pronunciation if dash present in form.
			-- FIXME, we shouldn't generate the pronunciation at all in that
			-- case, so we can support both dash and another form.
			if arrayname == "prons" then
				local val = data.forms[prop]
				if type(val) == "string" then val = {val} end
				local found_dash = false
				for _, form in ipairs(val) do
					if rmatch(form, "—") then
						found_dash = true
						break
					end
				end
				if found_dash then
					newval = {}
				end
			end
			if #newval > 0 then
				table.insert(retval, arrayname .. "." .. prop .. "=" .. table.concat(newval, ","))
			end
		end
	end
	return table.concat(retval, "|")
end

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
	local args = clone_args(frame)
	local args_clone
	if test_new_fr_verb_module then
		-- clone in case export.do_generate_forms() modifies args
		-- (I don't think it does currently)
		args_clone = mw.clone(args)
	end

	local data = export.do_generate_forms(args)

	-- Test code to compare existing module to new one.
	if test_new_fr_verb_module then
		local m_new_fr_verb = require("Module:User:Benwing2/fr-verb")
		local newdata = m_new_fr_verb.do_generate_forms(args_clone)
		local difconj = false
		local difforms = {}
		for arraytype = 1, 2 do
			local arrayname = arraytype == 1 and "forms" or "prons"
			local array = data[arrayname]
			local newarray = newdata[arrayname]
			for _, prop in ipairs(all_verb_props) do
				local val = array[prop]
				local newval = newarray[prop]
				-- deal with possible impedance mismatch between plain string
				-- and list
				if type(val) == "string" then val = {val} end
				if type(newval) == "string" then newval = {newval} end
				if not m_table.deepEquals(val, newval) then
					if test_new_fr_verb_module == "error" then
						table.insert(difforms, arrayname .. "." .. prop .. " " .. (val and table.concat(val, ",") or "nil") .. " || " .. (newval and table.concat(newval, ",") or "nil"))
					end
					difconj = true
				end
			end
		end
		if #difforms > 0 then
			error(table.concat(difforms, "; "))
		end
		track(difconj and "different-conj" or "same-conj")
	end

	m_core.link(data)

	local categories = {}
	if data.aux == "être" then
		table.insert(categories, "French verbs taking être as auxiliary")
	elseif data.aux == "avoir]] or [[être" then
		table.insert(categories, "French verbs taking avoir or être as auxiliary")
	end
	if data.conjcat then
		table.insert(categories, "Động từ tiếng Pháp với chia động từ " .. data.conjcat)
	end
	for _, cat in ipairs(data.cat) do
		table.insert(categories, "French " .. cat .. " verbs")
	end
	for _, group in ipairs(type(data.group) == "table" and data.group or {data.group}) do
		if group == 1 then
			table.insert(categories, "Động từ tiếng Pháp nhóm 1")
		elseif group == 2 then
			table.insert(categories, "Động từ tiếng Pháp nhóm 2")
		else
			table.insert(categories, "Động từ tiếng Pháp nhóm 3")
		end
	end
	if data.irregular == "yes" then
			table.insert(categories, "Động từ tiếng Pháp bất quy tắc")
	end

	return m_conj.make_table(data) .. m_utilities.format_categories(categories, lang)
end

return export