A modult a Modul:he-verb-ng/doc lapon tudod dokumentálni

--[=[

TODO:
    - Implement support for hollow roots in nif`al.
    - Implement support for geminate roots in pa`al, nif`al, hif`il, and huf`al.
    - Implement smarter ktiv male spellings.
    - Implement support for mixed binyanim (such as for ניגש and יכול).
    - Implement support for alternative forms.

Reference:

    Input parameters:
        1: binyan abbreviation
        p: first root letter
        a: second root letter
        l: third root letter

    Root letters may be prefixed with ':' to include them literally, in which case they will not be
    parsed as hollow or weak and no dageshes will be added to or removed from them. Any of the
    final form codes or intermediate data below can also be given explicitly in the input.

    Binyan abbreviations:
        pa: pa`al
        pi: pi`el
        po: po`el/polel
        pu: pu`al
        poal: po`al/polal
        hif: hif`il
        huf: huf`al
        nif: nif`al
        hit: hitpa`el
        hitpo: hitpo`el/hitpolel
        hitpu: hitpu`al *rare modern coinage

    Tense abbreviations:
        inf: to-infinitive
        imp: imperative
        fut: future (a.k.a. imperfect or prefix conjugation)
        pres: present (a.k.a. active participle)
        past: past (a.k.a. perfect or suffix conjugation)
        noun: action noun
        pp: passive participle

    Gender abbreviations:
        s: singular *first person only
        p: plural *first person only
        ms: masculine singular
        fs: feminine singular
        mp: masculine plural
        fp: feminine plural

    Person abbreviations:
        1: first person
        2: second person
        3: third person

    Exhaustive list of final form codes:

        inf
        noun *optional
        pp *optional

        imp_ms
        imp_fs
        imp_mp
        imp_fp *obsolete

        fut_1s
        fut_2ms *always equal to fut_3fs
        fut_2fs
        fut_3ms
        fut_3fs *always equal to fut_2ms
        fut_1p
        fut_2mp
        fut_2fp *obsolete and always equal to fut_3fp
        fut_3mp
        fut_3fp *obsolete and always equal to fut_2fp

        past_1s
        past_2ms
        past_2fs
        past_3ms
        past_3fs
        past_1p
        past_2mp
        past_2fp
        past_3mp
        past_3fp *obsolete before Biblical Hebrew, therefore equal to past_3mp

        pres_ms
        pres_fs
        pres_mp
        pres_fp

    Intermediate data:

        root_p
        root_a
        root_l

        gem *whether to treat as geminate

        imp_pre_stem
        imp_prs_stem
        imp_mid_stem
        imp_fin_stem
        imp_suf_stem
        imp_ffp_stem

        fut_gem
        fut_char *characteristic vowel [a,i,u], pa`al only (default: u)
        fut_pre_stem
        fut_prs_stem
        fut_1sp_stem
        fut_mid_stem
        fut_fin_stem
        fut_suf_stem
        fut_ffp_stem

        pres_gem
        pres_short *if not empty, use the pa`el-present, pa`al only
        pres_stem

        past_gem
        past_char *characteristic vowel [a,i,u], pa`al only (default: a)
        past_pre_stem
        past_2p_pre_stem
        past_stem
        past_3_stem
        past_3ms_stem
        past_3fs_stem
        past_2p_stem

]=]

local m_utilities = require("Module:utilities")
local m_links = require("Module:links")
local m_strutils = require("Module:string utilities")

local export = {}
local get_stems_for = {}

local lang = require("Module:languages").getByCode("he")

local convert_root_char = {
    ["א"] = "א",
    ["ב"] = "ב",
    ["בּ"] = "בּ", -- always hard
    ["בֿ"] = "בֿ", -- always soft
    ["ג"] = "ג",
    ["גּ"] = "גּ", -- always hard
    ["גֿ"] = "גֿ", -- always soft
    ["ד"] = "ד",
    ["דּ"] = "דּ", -- always hard
    ["דֿ"] = "דֿ", -- always soft
    ["ה"] = "ה",
    ["ו"] = "ו",
    ["ז"] = "ז",
    ["ח"] = "ח",
    ["ט"] = "ט",
    ["י"] = "י",
    ["כ"] = "כ",
    ["כּ"] = "כּ", -- always hard
    ["כֿ"] = "כֿ", -- always soft
    ["ל"] = "ל",
    ["מ"] = "מ",
    ["נ"] = "נ",
    ["ס"] = "ס",
    ["ע"] = "ע",
    ["פ"] = "פ",
    ["פּ"] = "פּ", -- always hard
    ["פֿ"] = "פֿ", -- always soft
    ["צ"] = "צ",
    ["ק"] = "ק",
    ["ר"] = "ר",
    ["ש"] = "שׁ", -- assume shin
    ["שׁ"] = "שׁ", -- shin
    ["שׂ"] = "שׂ", -- sin
    ["ת"] = "ת",
    ["תּ"] = "תּ", -- always hard
    ["תֿ"] = "תֿ", -- always soft
}

local convert_final_root_char = {
    ["ה"] = "י", -- make defective
    ["הּ"] = "ה", -- remove mappiq
    ["ו"] = "י", -- make defective
    ["ך"] = "כ", -- normalize final
    ["ךּ"] = "כּ", -- always hard, normalize final
    ["ךֿ"] = "כֿ", -- always soft, normalize final
    ["ם"] = "מ", -- normalize final
    ["ן"] = "נ", -- normalize final
    ["ף"] = "פ", -- normalize final
    ["ףּ"] = "פּ", -- always hard, normalize final
    ["ףֿ"] = "פֿ", -- always soft, normalize final
    ["ץ"] = "צ", -- normalize final
}

local forms_itp = {
    ["שׁ"] = "שְׁתּ",
    ["שׂ"] = "שְׂתּ",
    ["ס"] = "סְתּ",
    ["ז"] = "זְדּ",
    ["צ"] = "צְט",
    ["ת"] = {"ית", "תּ"},
    ["ד"] = {"יד", "דּ"},
    ["ט"] = {"יט", "טּ"},
}

local forms_etp = {
    ["ת"] = "תּ",
    ["ד"] = "דּ",
    ["ט"] = "טּ",
}

local forms = {
    ["בֿ"] = "ב",
    ["גֿ"] = "ג",
    ["דֿ"] = "ד",
    ["כֿ"] = "כ",
    ["פֿ"] = "פ",
    ["תֿ"] = "ת",
}

local forms_initial = {
    ["ב"] = "בּ",
    ["ג"] = "גּ",
    ["ד"] = "דּ",
    ["כ"] = "כּ",
    ["פ"] = "פּ",
    ["ת"] = "תּ",
}

local non_doubling_letters = {
    ["א"] = 1,
    ["ה"] = 2,
    ["ח"] = 3,
    ["ע"] = 2,
    ["ר"] = 1,
}

local forms_final_furtive = {
    ["ה"] = "הַּ",
    ["ח"] = "חַ",
    ["ע"] = "עַ",
}

local forms_final = {
    ["ה"] = "הּ",
    ["כ"] = "ךְ",
    ["כּ"] = "ךְּ",
    ["כֿ"] = "ךְ",
    ["מ"] = "ם",
    ["נ"] = "ן",
    ["פ"] = "ף",
    ["פֿ"] = "ף",
    ["צ"] = "ץ",
}

local short_gem_vowel = {
    ["a"] = "ַ",
    ["i"] = {"י", "ִ"},
    ["u"] = {"ו", "ֻ"},
    ["e"] = "ֶ",
    ["o"] = {"ו", "ָ"},
}

local lengthened_gem_vowel = {
    ["a"] = "ָ",
    ["i"] = {"י", "ֵ"},
    ["u"] = {"ו", "ֹ"},
    ["e"] = {"י", "ֵ"},
    ["o"] = {"ו", "ֹ"},
}

local mixed_gem_vowel = {
    ["a"] = "ַ",
    ["i"] = {"י", "ִ"},
    ["u"] = {"ו", "ֹ"},
    ["e"] = "ֶ",
    ["o"] = {"ו", "ֹ"},
}

local stressed_gem_vowel = {
    ["a"] = "ַ",
    ["i"] = "ֵ",
    ["u"] = {"ו", "ֹ"},
    ["e"] = "ֵ",
    ["o"] = {"ו", "ֹ"},
}

local doubling_type = {
    [1] = lengthened_gem_vowel,
    [2] = mixed_gem_vowel,
    [3] = short_gem_vowel,
}

local gutturals = {
    ["א"] = true,
    ["ה"] = true,
    ["ח"] = true,
    ["ע"] = true,
}

local char_vowel = {
    ["a"] = "ַ",
    ["i"] = "ֵ",
    ["u"] = {"ו", "ֹ"},
    ["A"] = "ָ",
    ["I"] = "ִי",
    ["U"] = "וּ",
    ["E"] = "ֵ",
    ["O"] = "וֹ",
}

local char_vowel_open = {
    ["a"] = "ָ",
    ["i"] = "ֵ",
    ["u"] = {"ו", "ֹ"},
    ["A"] = "ָ",
    ["I"] = "ִי",
    ["U"] = "וּ",
    ["E"] = "ֵ",
    ["O"] = "וֹ",
}

local char_vowel_short = {
    ["a"] = "ַ",
    ["i"] = "ֵ",
    ["u"] = {"ו", "ֹ"},
    ["A"] = "ַ",
    ["I"] = "ֵ",
    ["U"] = {"ו", "ֹ"},
    ["E"] = "ֵ",
    ["O"] = {"ו", "ֹ"},
}

local char_vowel_open_short = {
    ["a"] = "ָ",
    ["i"] = "ֵ",
    ["u"] = {"ו", "ֹ"},
    ["A"] = "ָ",
    ["I"] = "ֵ",
    ["U"] = {"ו", "ֹ"},
    ["E"] = "ֵ",
    ["O"] = {"ו", "ֹ"},
}

local char_vowel_closed = {
    ["a"] = "ַ",
    ["i"] = "ַ",
    ["u"] = {"ו", "ֹ"},
    ["A"] = "ַ",
    ["I"] = "ַ",
    ["U"] = {"ו", "ֹ"},
    ["E"] = "ַ",
    ["O"] = {"ו", "ֹ"},
}

local char_vowel_unstressed = {
    ["a"] = "ַ",
    ["i"] = "ַ",
    ["u"] = {"ו", "ָ"},
    ["A"] = "ַ",
    ["I"] = "ַ",
    ["U"] = {"ו", "ָ"},
    ["E"] = "ַ",
    ["O"] = {"ו", "ָ"},
}

local char_vowel_open_fp = {
    ["a"] = "ֶ",
    ["i"] = "ֶ",
    ["u"] = {"ו", "ֹ"},
    ["A"] = "ֶ",
    ["I"] = "ֶ",
    ["U"] = {"ו", "ֹ"},
    ["E"] = "ֶ",
    ["O"] = {"ו", "ֹ"},
}

local char_vowel_reduce = {
    ["a"] = nil,
    ["i"] = nil,
    ["u"] = nil,
    ["A"] = "ָ",
    ["I"] = "ִי",
    ["U"] = "וּ",
    ["E"] = "ֵ",
    ["O"] = "וֹ",
}

local chataf_vowel = {
    ["a"] = "ֲ",
    ["i"] = "ֱ",
    ["u"] = {"ו", "ֳ"},
}

local monophthongize = {
    ["י"] = "ֵי",
    ["ו"] = "וֹ",
}

local function gen_link(x)
    if type(x) == "table" then
        local pg = lang:makeEntryName(x[1])
        if pg == lang:makeEntryName(x[2]) then
            return m_links.full_link({lang = lang, term = pg, alt = x[2]})
        else
            return m_links.full_link({lang = lang, term = pg, alt = pg .. " \\ " .. x[2]})
        end
    else
        if x == "-" then
            return "—" -- m-dash
        else
            return m_links.full_link({lang = lang, term = x})
        end
    end
end

local function process_args(args)
    for key, value in pairs(args) do
        local i = mw.ustring.find(value, "\\")
        if i then
            args[key] = {mw.ustring.sub(value, 1, i - 1), mw.ustring.sub(value, i + 1)}
        end
    end
end

local function append_parts_2(a, b)
    if type(a) == "table" then
        if type(b) == "table" then
            return {a[1] .. b[1], a[2] .. b[2]}
        else
            return {a[1] .. lang:makeEntryName(b), a[2] .. b}
        end
    else
        if type(b) == "table" then
            return {lang:makeEntryName(a) .. b[1], a .. b[2]}
        else
            return a .. b
        end
    end
end

local function append_parts(a, ...)
    for _, b in ipairs({...}) do
        a = append_parts_2(a, b)
    end
    return a
end

local function equal(a, b)
    if type(a) == "table" then
        return type(b) == "table" and a[1] == b[1] and a[2] == b[2]
    else
        return a == b
    end
end

local function attach_t(x, novowel, assim_t)
    local is_table = (type(x) == "table")
    local wv = is_table and x[2] or x
    if mw.ustring.sub(wv, -2) == "תְ" then
        if is_table then
            return {x[1] .. "ת", mw.ustring.sub(wv, 1, -2) .. (novowel and "ְּ" or "ּ")}
        else
            return append_parts(mw.ustring.sub(wv, 1, -2), {"ת", (novowel and "ְּ" or "ּ")})
        end
    elseif assim_t and mw.ustring.sub(wv, -2) == "נְ" and (not is_table or mw.ustring.sub(x[1], -1) == "נ") then
        if is_table then
            return {mw.ustring.sub(x[1], 1, -2) .. "ת", mw.ustring.sub(wv, 1, -3) .. (novowel and "תְּ" or "תּ")}
        else
            return mw.ustring.sub(wv, 1, -3) .. (novowel and "תְּ" or "תּ")
        end
    else
        local dagesh = (mw.ustring.sub(wv, -1) == "ְ")
        return append_parts(x, dagesh and (novowel and "תְּ" or "תּ") or "ת")
    end
end

local function attach_n(x)
    local is_table = (type(x) == "table")
    local wv = is_table and x[2] or x
    if mw.ustring.sub(wv, -2) == "נְ" then
        if is_table then
            return {x[1], mw.ustring.sub(wv, 1, -2) .. "ּ"}
        else
            return mw.ustring.sub(wv, 1, -2) .. "ּ"
        end
    else
        return append_parts(x, "נ")
    end
end

local function get_form_initial(letter)
    if type(letter) == "table" then
        return letter
    elseif mw.ustring.sub(letter, 1, 1) == ":" then
        return mw.ustring.sub(letter, 2)
    else
        return forms_initial[letter] or forms[letter] or letter
    end
end

local function get_form_itp(letter)
    return forms_itp[letter] or append_parts("תְ", get_form_initial(letter))
end

local function get_form_etp(letter)
    return forms_etp[letter] or forms_itp[letter] or append_parts("תְ", get_form_initial(letter))
end

local function get_form_medial(letter)
    if type(letter) == "table" then
        return letter
    elseif mw.ustring.sub(letter, 1, 1) == ":" then
        return mw.ustring.sub(letter, 2)
    else
        return forms[letter] or letter
    end
end

local dagesh_or_rafe = {["ּ"] = true, ["ֿ"] = true}
local function add_dagesh(letter)
    if type(letter) == "table" then
        return letter
    elseif mw.ustring.sub(letter, 1, 1) == ":" then
        return get_form_medial(letter)
    elseif non_doubling_letters[letter] or dagesh_or_rafe[mw.ustring.sub(letter, -1)] then
        return get_form_medial(letter)
    else -- TODO: Handle geresh
        return get_form_medial(letter) .. "ּ"
    end
end

local function get_form_double(letter, vowel, force_lengthening, stressed)
    local lengthen = non_doubling_letters[letter]
    if force_lengthening then lengthen = lengthen and 1 end
    if stressed then
        vowel = vowel and (lengthen and vowel == "a" and "ָ" or stressed_gem_vowel[vowel]) or ""
    else
        vowel = vowel and (lengthen and doubling_type[lengthen][vowel] or short_gem_vowel[vowel]) or ""
    end
    return append_parts(vowel, add_dagesh(letter))
end

local function get_form_final(letter, char)
    if type(letter) == "table" then
        return letter
    elseif mw.ustring.sub(letter, 1, 1) == ":" then
        return mw.ustring.sub(letter, 2)
    else
        return (char ~= "a" and char ~= "A" and forms_final_furtive[letter]) or forms_final[letter] or forms[letter] or letter
    end
end

local function get_elet_ending(letter)
    if letter == "א" then
        return "ֵאת"
    else
        local vowel = gutturals[letter] and "ַ" or "ֶ"
        return append_parts(vowel, get_form_medial(letter), vowel, "ת")
    end
end

local function ends_in_guttural(letter)
    if type(letter) == "table" then
        letter = letter[2]
    end
    return gutturals[mw.ustring.sub(letter, -1)]
end

local function shva_na(letter, vowel)
    return ends_in_guttural(letter) and chataf_vowel[vowel or "a"] or "ְ"
end

local function reduce(char, chataf)
    return char_vowel_reduce[char] or chataf and "ֲ" or "ְ"
end

local letters     = "[א-ת]"
local not_letters = "[^א-ת]"
local modifiers   = "[ּֿׁׂׄ׳]?"
local separators  = "[-־ %.,!|]?"
local root_regex  = "(" .. letters .. modifiers .. ")" .. separators

local function parse_root_part(part, last)
    if mw.ustring.sub(part, 1, 1) == ":" then
        return part
    end
    local letters = {}
    local len = 0
    local subber = function(letter)
        table.insert(letters, letter)
        len = len + 1
        return ""
    end
    local scraps = mw.ustring.gsub(part, root_regex, subber)
    if scraps ~= "" then
        return ":" .. part
    end
    if len < 1 then
        return nil
    elseif len == 1 then
        return part
    else
        local ret = ""
        for i, letter in ipairs(letters) do
            local letterx = last and i == len and convert_final_root_char[letter] or convert_root_char[letter]
            if not letterx then
                error("Unrecognized root letter '" .. letter .. "'.")
            end
            ret = append_parts(ret, get_form_medial(letterx), i == len and "" or "ְ")
        end
        if type(ret) == "table" then
            return ret
        else
            return ":" .. ret
        end
    end
end

local function parse_root(args)
    if args[2] == "" then args[2] = nil end
    if args[3] == "" then args[3] = nil end
    if args[4] == "" then args[4] = nil end
    if args[3] then
        args["p"] = args["p"] or parse_root_part(args[2])
        args["a"] = args["a"] or parse_root_part(args[3])
        args["l"] = args["l"] or parse_root_part(args[4], true)
    elseif args[2] then
        local radicals = {}
        local len = 0
        local subber = function(radical)
            table.insert(radicals, radical)
            len = len + 1
            return ""
        end
        local scraps = mw.ustring.gsub(args[2], root_regex, subber)
        if scraps ~= "" then
            error("Unrecognized characters in root.")
        end
        if len < 3 then
            error("Root must have at least three letters.")
        elseif len == 3 then
            args["p"] = args["p"] or radicals[1]
            args["a"] = args["a"] or radicals[2]
            args["l"] = args["l"] or radicals[3]
        elseif len == 4 then
            args["p"] = args["p"] or radicals[1]
            args["a"] = args["a"] or radicals[2]
            args["a2"] = args["a2"] or radicals[3]
            args["l"] = args["l"] or radicals[4]
        elseif len > 4 then
            error("Roots with more than four letters must be explicitly delimited.")
        end
    end
end

local function convert_root(args)
    for _, x in ipairs({"p", "a", "a2", "l"}) do
        if args[x] then
            local root_x = "root_" .. x
            if not args[root_x] then
                if mw.ustring.sub(args[x], 1, 1) == ":" then
                    args[root_x] = args[x]
                else
                    args[root_x] = x == "l" and convert_final_root_char[args[x]] or convert_root_char[args[x]]
                    if not args[root_x] then
                        error("Unrecognized root letter '" .. args[x] .. "'.")
                    end
                end
            end
        end
    end
    if args["root_a2"] then
        local a = append_parts(get_form_medial(args["root_a"]), "ְ", get_form_initial(args["root_a2"]))
        if type(a) ~= "table" then
            a = ":" .. a
        end
        args["root_a"] = a
        args["root_a2"] = nil
    end
end

local function conjugate(args)
    args["imp_pre_stem"] = args["imp_pre_stem"] or args["fut_pre_stem"]
    args["imp_mid_stem"] = args["imp_mid_stem"] or args["fut_mid_stem"]
    args["imp_fin_stem"] = args["imp_fin_stem"] or args["fut_fin_stem"]
    args["imp_suf_stem"] = args["imp_suf_stem"] or args["fut_suf_stem"]
    args["imp_prs_stem"] = args["imp_prs_stem"] or args["fut_prs_stem"]
    args["imp_ffp_stem"] = args["imp_ffp_stem"] or args["fut_ffp_stem"]
    local fp_long = args["fp_long"] and args["fp_long"] ~= "-"
    if fp_long then
        args["fut_long_stem"] = append_parts(args["fut_long_stem"] or args["fut_suf_stem"], "ֶי")
        args["imp_long_stem"] = append_parts(args["imp_long_stem"] or args["imp_suf_stem"], "ֶי")
    end
    if args["imp"] ~= "-" then
        if not args["imp_ms"] then
            args["imp_ms"] = append_parts(args["imp_pre_stem"], args["imp_mid_stem"], args["imp_fin_stem"])
        end
        if not args["imp_fs"] then
            args["imp_fs"] = append_parts(args["imp_prs_stem"] or args["imp_pre_stem"], args["imp_mid_stem"], args["imp_suf_stem"], "ִי")
        end
        if not args["imp_mp"] then
            args["imp_mp"] = append_parts(args["imp_prs_stem"] or args["imp_pre_stem"], args["imp_mid_stem"], args["imp_suf_stem"], "וּ")
        end
        if not args["imp_fp"] then
            args["imp_fp"] = append_parts(fp_long and (args["imp_long_pre_stem"] or args["imp_prs_stem"]) or args["imp_pre_stem"], args["imp_mid_stem"], attach_n(fp_long and args["imp_long_stem"] or args["imp_ffp_stem"]), "ָה")
        end
    end

    if args["fut"] ~= "-" then
        if not args["fut_1s"] then
            args["fut_1s"] = append_parts("א", args["fut_1sp_stem"] or args["fut_pre_stem"], args["fut_mid_stem"], args["fut_fin_stem"])
        end
        if not args["fut_2ms"] then
            args["fut_2ms"] = append_parts("תּ", args["fut_pre_stem"], args["fut_mid_stem"], args["fut_fin_stem"])
        end
        if not args["fut_2fs"] then
            args["fut_2fs"] = append_parts("תּ", args["fut_prs_stem"] or args["fut_pre_stem"], args["fut_mid_stem"], args["fut_suf_stem"], "ִי")
        end
        if not args["fut_3ms"] then
            args["fut_3ms"] = append_parts("י", args["fut_pre_stem"], args["fut_mid_stem"], args["fut_fin_stem"])
        end
        if not args["fut_3fs"] then
            args["fut_3fs"] = args["fut_2ms"]
        end
        if not args["fut_1p"] then
            args["fut_1p"] = append_parts("נ", args["fut_pre_stem"], args["fut_mid_stem"], args["fut_fin_stem"])
        end
        if not args["fut_2mp"] then
            args["fut_2mp"] = append_parts("תּ", args["fut_prs_stem"] or args["fut_pre_stem"], args["fut_mid_stem"], args["fut_suf_stem"], "וּ")
        end
        if not args["fut_2fp"] then
            args["fut_2fp"] = append_parts("תּ", fp_long and (args["fut_long_pre_stem"] or args["fut_prs_stem"]) or args["fut_pre_stem"], args["fut_mid_stem"], attach_n(fp_long and args["fut_long_stem"] or args["fut_ffp_stem"]), "ָה")
        end
        if not args["fut_3mp"] then
            args["fut_3mp"] = append_parts("י", args["fut_prs_stem"] or args["fut_pre_stem"], args["fut_mid_stem"], args["fut_suf_stem"], "וּ")
        end
        if not args["fut_3fp"] then
            args["fut_3fp"] = append_parts("תּ", fp_long and (args["fut_long_pre_stem"] or args["fut_prs_stem"]) or args["fut_pre_stem"], args["fut_mid_stem"], attach_n(fp_long and args["fut_long_stem"] or args["fut_ffp_stem"]), "ָה")
        end
    end

    if args["pres"] ~= "-" then
        args["pres_pre_stem"] = args["pres_pre_stem"] or ""
        args["pres_prs_stem"] = args["pres_prs_stem"] or args["pres_pre_stem"]
        if not args["pres_ms"] then
            args["pres_ms"] = append_parts(args["pres_pre_stem"], args["pres_ms_stem"])
        end
        if not args["pres_fs"] then
            args["pres_fs_stem"] = args["pres_fs_stem"] or append_parts(args["pres_suf_stem"], "ָה")
            args["pres_fs"] = append_parts(args["pres_prs_stem"], args["pres_fs_stem"])
        end
        if not args["pres_mp"] then
            args["pres_mp"] = append_parts(args["pres_prs_stem"], args["pres_suf_stem"], "ִים")
        end
        if not args["pres_fp"] then
            args["pres_fp"] = append_parts(args["pres_prs_stem"], args["pres_suf_stem"], "וֹת")
        end
    end

    if args["past"] ~= "-" then
        args["past_pre_stem"] = args["past_pre_stem"] or ""
        args["past_2p_pre_stem"] = args["past_2p_pre_stem"] or args["past_pre_stem"]
        local past_long = args["past_long"] and args["past_long"] ~= "-"
        if past_long then
            args["past_stem"] = append_parts(args["past_long_stem"] or args["past_3_stem"] or args["past_stem"], "וֹ")
            args["past_2p_stem"] = args["past_stem"]
            args["past_long_pre_stem"] = args["past_long_pre_stem"] or args["root_l"] == "י" and args["past_pre_stem"] or args["past_2p_pre_stem"]
        else
            args["past_2p_stem"] = args["past_2p_stem"] or args["past_stem"]
        end
        local assim_t = args["assim_t"] and args["assim_t"] ~= "-"
        if not args["past_1s"] then
            args["past_1s"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_pre_stem"], attach_t(args["past_stem"], false, assim_t), "ִי")
        end
        if not args["past_2ms"] then
            args["past_2ms"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_pre_stem"], attach_t(args["past_stem"], false, assim_t), "ָ")
        end
        if not args["past_2fs"] then
            args["past_2fs"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_pre_stem"], attach_t(args["past_stem"], true, assim_t))
        end
        if not args["past_3ms"] then
            args["past_3ms"] = append_parts(args["past_pre_stem"], args["past_3ms_stem"])
        end
        if not args["past_3fs"] then
            args["past_3fs"] = append_parts(args["past_3fs_pre_stem"] or args["past_3_pre_stem"] or args["past_pre_stem"], args["past_3fs_stem"] or args["past_3_stem"], "ָה")
        end
        if not args["past_1p"] then
            args["past_1p"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_pre_stem"], attach_n(args["past_stem"]), "וּ")
        end
        if not args["past_2mp"] then
            args["past_2mp"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_2p_pre_stem"], attach_t(args["past_2p_stem"], false, assim_t), "ֶם")
        end
        if not args["past_2fp"] then
            args["past_2fp"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_2p_pre_stem"], attach_t(args["past_2p_stem"], false, assim_t), "ֶן")
        end
        if not args["past_2mp2"] then
            args["past_2mp2"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_pre_stem"], attach_t(args["past_stem"], false, assim_t), "ֶם")
        end
        if not args["past_2fp2"] then
            args["past_2fp2"] = append_parts(past_long and args["past_long_pre_stem"] or args["past_pre_stem"], attach_t(args["past_stem"], false, assim_t), "ֶן")
        end
        if not args["past_3mp"] then
            args["past_3mp"] = append_parts(args["past_3_pre_stem"] or args["past_pre_stem"], args["past_3_stem"], "וּ")
        end
        if not args["past_3fp"] then
            args["past_3fp"] = args["past_3mp"]
        end
    end

    args["inf"] = args["inf"] or "-"

    args["heading"] = "Conjugation of " .. gen_link(args["past_3ms"]) 
end

local function get_imp_stem_endings(args, root_l, char, chataf, keep_long_vowel, guttural_force_patach)
    if root_l == "א" then
        args["imp_fin_stem"] = args["imp_fin_stem"] or append_parts(keep_long_vowel and char_vowel_open[char] or char_vowel_open_short[char], "א")
        args["imp_suf_stem"] = args["imp_suf_stem"] or append_parts(reduce(char, chataf), "א")
        args["imp_ffp_stem"] = args["imp_ffp_stem"] or append_parts(char_vowel_open_fp[char], "א")
        args["imp_long_stem"] = args["imp_long_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), "א")
    elseif root_l == "י" then
        args["imp_fin_stem"] = args["imp_fin_stem"] or "ֵה"
        args["imp_suf_stem"] = args["imp_suf_stem"] or ""
        args["imp_ffp_stem"] = args["imp_ffp_stem"] or "ֶי"
    else
        args["imp_fin_stem"] = args["imp_fin_stem"] or append_parts(keep_long_vowel and char_vowel[char] or guttural_force_patach and gutturals[root_l] and "ַ" or char_vowel_short[char], get_form_final(root_l, guttural_force_patach and (not keep_long_vowel) and gutturals[root_l] and "a" or char))
        args["imp_suf_stem"] = args["imp_suf_stem"] or append_parts(reduce(char, chataf), get_form_medial(root_l))
        args["imp_ffp_stem"] = args["imp_ffp_stem"] or append_parts(gutturals[root_l] and "ַ" or char_vowel_short[char], get_form_medial(root_l), "ְ")
        args["imp_long_stem"] = args["imp_long_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), get_form_medial(root_l))
    end
end

local function get_fut_stem_endings(args, root_l, char, chataf)
    if root_l == "א" then
        args["fut_fin_stem"] = args["fut_fin_stem"] or append_parts(char_vowel_open[char], "א")
        args["fut_suf_stem"] = args["fut_suf_stem"] or append_parts(reduce(char, chataf), "א")
        args["fut_ffp_stem"] = args["fut_ffp_stem"] or append_parts(char_vowel_open_fp[char], "א")
        args["fut_long_stem"] = args["fut_long_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), "א")
    elseif root_l == "י" then
        args["fut_fin_stem"] = args["fut_fin_stem"] or "ֶה"
        args["fut_suf_stem"] = args["fut_suf_stem"] or ""
        args["fut_ffp_stem"] = args["fut_ffp_stem"] or "ֶי"
    else
        args["fut_fin_stem"] = args["fut_fin_stem"] or append_parts(char_vowel[char], get_form_final(root_l, char))
        args["fut_suf_stem"] = args["fut_suf_stem"] or append_parts(reduce(char, chataf), get_form_medial(root_l))
        args["fut_ffp_stem"] = args["fut_ffp_stem"] or append_parts(gutturals[root_l] and "ַ" or char_vowel_short[char], get_form_medial(root_l), "ְ")
        args["fut_long_stem"] = args["fut_long_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), get_form_medial(root_l))
    end
end

local function get_pres_stem_endings(args, root_l, char, chataf, use_elet, use_et)
    if root_l == "י" then
        args["pres_ms_stem"] = args["pres_ms_stem"] or "ֶה"
        args["pres_suf_stem"] = args["pres_suf_stem"] or ""
        if use_et then
            args["pres_fs_stem"] = args["pres_fs_stem"] or "ֵית"
        end
    else
        args["pres_ms_stem"] = args["pres_ms_stem"] or append_parts(char_vowel_open[char], get_form_final(root_l, char))
        args["pres_suf_stem"] = args["pres_suf_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), get_form_medial(root_l))
        if use_elet then
            args["pres_fs_stem"] = args["pres_fs_stem"] or get_elet_ending(root_l)
        end
    end
end

local function get_past_stem_endings(args, root_l, char, chataf, kca, use_e)
    if root_l == "א" then
        args["past_stem"] = args["past_stem"] or append_parts(kca and char_vowel_open[char] or "ֵ", "א")
        args["past_3ms_stem"] = args["past_3ms_stem"] or append_parts(char_vowel_open[char], "א")
        args["past_3_stem"] = args["past_3_stem"] or append_parts(reduce(char, chataf), "א")
        args["past_long_stem"] = args["past_long_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), "א")
    elseif root_l == "י" then
        args["past_stem"] = args["past_stem"] or use_e and "ֵי" or "ִי"
        args["past_3ms_stem"] = args["past_3ms_stem"] or "ָה"
        args["past_3fs_stem"] = args["past_3fs_stem"] or append_parts(reduce("a", chataf), "ת")
        args["past_3_stem"] = args["past_3_stem"] or ""
    else
        args["past_stem"] = args["past_stem"] or append_parts(char_vowel_closed[char], get_form_medial(root_l), "ְ")
        args["past_2p_stem"] = args["past_2p_stem"] or append_parts(char_vowel_unstressed[char], get_form_medial(root_l), "ְ")
        args["past_3ms_stem"] = args["past_3ms_stem"] or append_parts(char_vowel[char], get_form_final(root_l, char))
        args["past_3_stem"] = args["past_3_stem"] or append_parts(reduce(char, chataf), get_form_medial(root_l))
        args["past_long_stem"] = args["past_long_stem"] or append_parts(char == "a" and char_vowel_open[char] or reduce(char, chataf), get_form_medial(root_l))
    end
end

local function get_imp_stem_endings_gem(args, root_l, char)
    if root_l == "א" then
        args["imp_fin_stem"] = args["imp_fin_stem"] or append_parts(char == "a" and "ָ" or stressed_gem_vowel[char], "א")
        args["imp_suf_stem"] = args["imp_suf_stem"] or append_parts(char == "a" and "ָ" or stressed_gem_vowel[char], "א")
        args["imp_ffp_stem"] = args["imp_ffp_stem"] or append_parts(char ~= "u" and char ~= "o" and "ֶ" or stressed_gem_vowel[char], "א")
    else
        args["imp_fin_stem"] = args["imp_fin_stem"] or append_parts(stressed_gem_vowel[char], get_form_final(root_l, char))
        args["imp_suf_stem"] = args["imp_suf_stem"] or get_form_double(root_l, char, true, true)
        args["imp_ffp_stem"] = args["imp_ffp_stem"] or append_parts(gutturals[root_l] and "ַ" or stressed_gem_vowel[char], get_form_medial(root_l), "ְ")
        args["imp_long_stem"] = args["imp_long_stem"] or get_form_double(root_l, char, true)
    end
end

local function get_fut_stem_endings_gem(args, root_l, char)
    if root_l == "א" then
        args["fut_fin_stem"] = args["fut_fin_stem"] or append_parts(char == "a" and "ָ" or stressed_gem_vowel[char], "א")
        args["fut_suf_stem"] = args["fut_suf_stem"] or get_form_double("א", char)
        args["fut_ffp_stem"] = args["fut_ffp_stem"] or append_parts(char ~= "u" and char ~= "o" and "ֶ" or stressed_gem_vowel[char], "א")
    else
        args["fut_fin_stem"] = args["fut_fin_stem"] or append_parts(stressed_gem_vowel[char], get_form_final(root_l, char))
        args["fut_suf_stem"] = args["fut_suf_stem"] or get_form_double(root_l, char, true, true)
        args["fut_ffp_stem"] = args["fut_ffp_stem"] or append_parts(gutturals[root_l] and "ַ" or stressed_gem_vowel[char], get_form_medial(root_l), "ְ")
        args["fut_long_stem"] = args["fut_long_stem"] or get_form_double(root_l, char, true)
    end
end

local function get_pres_stem_endings_gem(args, root_l, char, short_a)
    if root_l == "א" then
        args["pres_ms_stem"] = args["pres_ms_stem"] or append_parts(char == "a" and "ָ" or stressed_gem_vowel[char], "א")
        args["pres_suf_stem"] = args["pres_suf_stem"] or get_form_double("א", char)
    else
        args["pres_ms_stem"] = args["pres_ms_stem"] or append_parts(char == "a" and (not short_a) and "ָ" or stressed_gem_vowel[char], get_form_final(root_l, char))
        args["pres_suf_stem"] = args["pres_suf_stem"] or get_form_double(root_l, char, true)
    end
end

local function get_past_stem_endings_gem(args, root_l, char)
    if root_l == "א" then
        args["past_stem"] = args["past_stem"] or append_parts(char == "a" and "ָ" or stressed_gem_vowel[char], "א")
        args["past_3ms_stem"] = args["past_3ms_stem"] or append_parts(char == "a" and "ָ" or stressed_gem_vowel[char], "א")
        args["past_3_stem"] = args["past_3_stem"] or get_form_double("א", char)
    else
        args["past_stem"] = args["past_stem"] or append_parts((char == "u" or char == "o") and {"ו", "ֹ"} or "ַ", get_form_medial(root_l), "ְ")
        args["past_2p_stem"] = args["past_2p_stem"] or append_parts((char == "u" or char == "o") and {"ו", "ָ"} or "ַ", get_form_medial(root_l), "ְ")
        args["past_3ms_stem"] = args["past_3ms_stem"] or append_parts(stressed_gem_vowel[char], get_form_final(root_l, char))
        args["past_3_stem"] = args["past_3_stem"] or get_form_double(root_l, char, true, true)
        args["past_long_stem"] = args["past_long_stem"] or get_form_double(root_l, char, true)
    end
end

get_stems_for["pa"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]

    if args["hollow"] ~= "-" then
        local past_char = args["past_char"] or "A"
        local fut_char = args["fut_char"] or (root_a == "י" and "I") or "U"
        local imp_char = args["imp_char"] or fut_char
        local inf_char = args["inf_char"] or fut_char
        local pres_char = args["pres_char"] or past_char

        args["inf"] = args["inf"] or append_parts("לָ", get_form_medial(root_p), char_vowel[inf_char], get_form_final(root_l, inf_char))
        args["noun"] = args["noun"] or append_parts(get_form_initial(root_p), "ִי", get_form_medial(root_l), "ָה")

        args["imp_pre_stem"] = args["imp_pre_stem"] or get_form_initial(root_p)
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ָ", get_form_medial(root_p))
        args["fut_long_pre_stem"] = args["fut_long_pre_stem"] or append_parts("ְ", get_form_medial(root_p))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, gutturals[root_p], true)
        get_fut_stem_endings(args, root_l, fut_char, gutturals[root_p])

        args["pres_pre_stem"] = args["pres_pre_stem"] or get_form_initial(root_p)
        get_pres_stem_endings(args, root_l, pres_char, gutturals[root_p], false, false)

        args["past_pre_stem"] = args["past_pre_stem"] or get_form_initial(root_p)
        get_past_stem_endings(args, root_l, past_char, gutturals[root_p], true, false)
    elseif false then
        -- reserved for special cases
    else
        local past_char = args["past_char"] or "a"
        local fut_char = args["fut_char"]
        local imp_char = args["imp_char"]
        local inf_char = args["inf_char"]
        local pres_char = args["pres_char"]

        local if_ = nil
        local ef_ = nil
        local suf_if_ = nil
        if args["assim"] ~= "-" then
            fut_char = fut_char or ((args["elide"] ~= "-" or gutturals[root_l]) and "a") or "u"
            if_ = get_form_double(root_a, "i", true)
            ef_ = get_form_double(root_a, "e", true)
        elseif args["elide"] ~= "-" then
            if root_p == "א" then
                fut_char = fut_char or "a"
                if_ = append_parts("ֹא", get_form_medial(root_a))
                ef_ = append_parts({"ו", "ֹ"}, get_form_medial(root_a))
            else
                fut_char = fut_char or (gutturals[root_l] and root_l ~= "א" and "a") or "i"
                if_ = append_parts("ֵ", get_form_medial(root_a))
            end
        elseif monophthongize[root_p] then
            fut_char = fut_char or "a"
            if_ = append_parts("ִי", get_form_medial(root_a))
        elseif gutturals[root_p] then
            fut_char = fut_char or ((gutturals[root_l] or gutturals[root_a]) and "a") or "u"
            local v1 = (root_p == "א" or (root_l == "י" and root_p ~= "ע") or (root_l ~= "י" and fut_char == "a")) and "ֶ" or "ַ"
            if args["nochataf"] then
                if root_a == "י" then v1 = "ִ" end
                if_ = append_parts(v1, get_form_medial(root_p), "ְ", get_form_initial(root_a))
                ef_ = append_parts("ֶ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
            else
                local v2 = v1 == "ֶ" and "ֱ" or "ֲ"
                if_ = append_parts(v1, get_form_medial(root_p), v2, get_form_medial(root_a))
                ef_ = append_parts("ֶ", get_form_medial(root_p), "ֱ", get_form_medial(root_a))
                suf_if_ = append_parts(v1, get_form_medial(root_p), v1, get_form_medial(root_a))
            end
        else
            fut_char = fut_char or ((gutturals[root_l] or gutturals[root_a]) and "a") or "u"
            if_ = append_parts("ִ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
            ef_ = append_parts("ֶ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
        end

        local inf_if_ = nil
        local p_ = nil
        if args["elide"] ~= "-" and root_p ~= "א" then
            inf_char = inf_char or "i"
            imp_char = imp_char or fut_char
            args["inf"] = args["inf"] or append_parts("לָ", get_form_medial(root_a), get_elet_ending(root_l))
            p_ = get_form_initial(root_a)
        elseif gutturals[root_p] then
            inf_char = inf_char or "u"
            imp_char = imp_char or (root_p == "א" and args["elide"] ~= "-" and (((gutturals[root_l] or gutturals[root_a]) and "a") or "u")) or fut_char
            local v1 = (root_p == "א" or (root_l ~= "י" and inf_char == "a")) and "ֶ" or "ַ"
            if args["nochataf"] then
                if root_a == "י" then v1 = "ִ" end
                inf_if_ = append_parts(v1, get_form_medial(root_p), "ְ", get_form_initial(root_a))
            else
                local v2 = v1 == "ֶ" and "ֱ" or "ֲ"
                inf_if_ = append_parts(v1, get_form_medial(root_p), v2, get_form_medial(root_a))
            end
            p_ = append_parts(get_form_initial(root_p), root_p == "א" and "ֱ" or "ֲ", get_form_medial(root_a))
            if root_l ~= "י" and root_p == "א" and gutturals[root_a] then
                args["imp_prs_stem"] = args["imp_prs_stem"] or append_parts(get_form_initial(root_p), "ֶ", get_form_medial(root_a))
                args["imp_suf_stem"] = args["imp_suf_stem"] or append_parts("ֱ", get_form_medial(root_l))
            end
        else
            inf_char = inf_char or "u"
            imp_char = imp_char or fut_char
            inf_if_ = if_
            p_ = append_parts(get_form_initial(root_p), shva_na(root_p), get_form_medial(root_a))
        end

        args["inf"] = args["inf"] or append_parts("ל", inf_if_, root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        args["noun"] = args["noun"] or append_parts(get_form_initial(root_p), shva_na(root_p), get_form_medial(root_a), "ִי", root_l == "י" and "ּ" or get_form_medial(root_l), "ָה")
        args["pp"] = args["pp"] or append_parts(get_form_initial(root_p), "ָ", get_form_medial(root_a), "וּ", get_form_final(root_l, "U"))

        args["imp_pre_stem"] = args["imp_pre_stem"] or p_
        if root_l ~= "י" and (args["elide"] == "-" or root_p == "א") then
            if gutturals[root_a] then
                args["imp_prs_stem"] = args["imp_prs_stem"] or append_parts(get_form_initial(root_p), "ַ", get_form_medial(root_a))
                if imp_char ~= "u" and imp_char ~= "i" then
                    args["imp_long_pre_stem"] = args["imp_long_pre_stem"] or args["imp_pre_stem"]
                end
                args["imp_suf_stem"] = args["imp_suf_stem"] or append_parts("ֲ", get_form_medial(root_l))
            else
                args["imp_prs_stem"] = args["imp_prs_stem"] or append_parts(get_form_initial(root_p), "ִ", get_form_medial(root_a))
            end
        end
        args["fut_pre_stem"] = args["fut_pre_stem"] or if_
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or ef_
        if root_l ~= "י" then
            args["fut_prs_stem"] = args["fut_prs_stem"] or suf_if_
        end
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a))
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        if args["pres_short"] and args["pres_short"] ~= "" then
            -- pa`el
            args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts(get_form_initial(root_p), "ָ", get_form_medial(root_a))
            if root_l ~= "י" then
                args["pres_prs_stem"] = args["pres_prs_stem"] or append_parts(get_form_initial(root_p), shva_na(root_p), get_form_medial(root_a))
            end
            get_pres_stem_endings(args, root_l, pres_char or "E", ends_in_guttural(root_a), false, false)
        else
            -- po`el
            args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts(get_form_initial(root_p), "וֹ", get_form_medial(root_a))
            get_pres_stem_endings(args, root_l, pres_char or "i", ends_in_guttural(root_a), true, false)
        end

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts(get_form_initial(root_p), "ָ", get_form_medial(root_a))
        args["past_2p_pre_stem"] = args["past_2p_pre_stem"] or append_parts(get_form_initial(root_p), shva_na(root_p), get_form_medial(root_a))
        if past_char == "i" or past_char == "u" then
            args["past_long_pre_stem"] = args["past_long_pre_stem"] or append_parts(get_form_initial(root_p), gutturals[root_a] and "ַ" or "ִ", get_form_medial(root_a))
        end
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), true, false)
    end
end

get_stems_for["pi"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "i"
    local fut_char = args["fut_char"] or "i"
    local imp_char = args["imp_char"] or fut_char
    local inf_char = args["inf_char"] or fut_char
    local pres_char = args["pres_char"] or "i"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        args["inf"] = args["inf"] or append_parts("לְ", get_form_medial(root_p), get_form_double(root_a, "a"), root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        args["noun"] = args["noun"] or append_parts(get_form_initial(root_p), get_form_double(root_a, "i"), "וּ", get_form_final(root_l, "U"))

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts(get_form_initial(root_p), get_form_double(root_a, "a"))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ְ", get_form_medial(root_p), get_form_double(root_a, "a"))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts("ֲ", get_form_medial(root_p), get_form_double(root_a, "a"))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a))
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מְ", get_form_medial(root_p), get_form_double(root_a, "a"))
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts(get_form_initial(root_p), get_form_double(root_a, "i"))
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, false)
    end
end

get_stems_for["po"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["hollow"] == "-" and args["root_a"] or args["root_l"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "i"
    local fut_char = args["fut_char"] or "i"
    local imp_char = args["imp_char"] or fut_char
    local inf_char = args["inf_char"] or fut_char
    local pres_char = args["pres_char"] or "i"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        args["inf"] = args["inf"] or append_parts("לְ", get_form_medial(root_p), "וֹ", get_form_medial(root_a), root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        -- args["noun"] = args["noun"] or append_parts(get_form_initial(root_p), "וֹ", get_form_medial(root_a), shva_na(root_a), get_form_medial(root_l), "וּת")
        args["noun"] = args["noun"] or append_parts(get_form_initial(root_p), "ִי", get_form_medial(root_a), "וּ", get_form_final(root_l, "U"))

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts(get_form_initial(root_p), "וֹ", get_form_medial(root_a))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ְ", get_form_medial(root_p), "וֹ", get_form_medial(root_a))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts("ֲ", get_form_medial(root_p), "וֹ", get_form_medial(root_a))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a))
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מְ", get_form_medial(root_p), "וֹ", get_form_medial(root_a))
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts(get_form_initial(root_p), "וֹ", get_form_medial(root_a))
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, false)
    end
end

get_stems_for["pu"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "a"
    local fut_char = args["fut_char"] or "a"
    local pres_char = args["pres_char"] or "a"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        args["imp"] = args["imp"] or "-"
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ְ", get_form_medial(root_p), get_form_double(root_a, "u"))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts("ֲ", get_form_medial(root_p), get_form_double(root_a, "u"))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מְ", get_form_medial(root_p), get_form_double(root_a, "u"))
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts(get_form_initial(root_p), get_form_double(root_a, "u"))
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["poal"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["hollow"] == "-" and args["root_a"] or args["root_l"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "a"
    local fut_char = args["fut_char"] or "a"
    local pres_char = args["pres_char"] or "a"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        args["imp"] = args["imp"] or "-"
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ְ", get_form_medial(root_p), "וֹ", get_form_medial(root_a))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts("ֲ", get_form_medial(root_p), "וֹ", get_form_medial(root_a))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מְ", get_form_medial(root_p), "וֹ", get_form_medial(root_a))
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts(get_form_initial(root_p), "וֹ", get_form_medial(root_a))
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["hif"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or args["geminate"] ~= "-" and "i" or "I"
    local fut_char = args["fut_char"] or args["geminate"] ~= "-" and "i" or "I"
    local imp_char = args["imp_char"] or fut_char
    local inf_char = args["inf_char"] or fut_char
    local pres_char = args["pres_char"] or args["geminate"] ~= "-" and "i" or "I"

    if args["hollow"] ~= "-" then
        args["inf"] = args["inf"] or append_parts("לְהָ", get_form_medial(root_p), char_vowel[inf_char], get_form_final(root_l, inf_char))
        args["noun"] = args["noun"] or append_parts(gutturals[root_p] and "הֶ" or "הֲ", get_form_medial(root_p), "ָ", get_form_medial(root_l), "ָה")

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts("הָ", get_form_medial(root_p))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ָ", get_form_medial(root_p))
        args["imp_long_pre_stem"] = args["imp_long_pre_stem"] or append_parts(gutturals[root_p] and "הַ" or "הֲ", get_form_medial(root_p))
        args["fut_long_pre_stem"] = args["fut_long_pre_stem"] or append_parts("ְ", get_form_medial(root_p))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, gutturals[root_p], false, true)
        get_fut_stem_endings(args, root_l, fut_char, gutturals[root_p])

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מֵ", get_form_medial(root_p))
        args["pres_prs_stem"] = args["pres_prs_stem"] or append_parts("מְ", get_form_medial(root_p))
        get_pres_stem_endings(args, root_l, pres_char, gutturals[root_p], false, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("הֵ", get_form_medial(root_p))
        args["past_2p_pre_stem"] = args["past_2p_pre_stem"] or append_parts(gutturals[root_p] and "הַ" or "הֲ", get_form_medial(root_p))
        get_past_stem_endings(args, root_l, past_char, gutturals[root_p], false, true)
    elseif args["geminate"] ~= "-" then
        args["inf"] = args["inf"] or append_parts("לְהָ", get_form_medial(root_p), char_vowel[inf_char], get_form_final(root_l, inf_char))
        --- args["noun"] = args["noun"] or append_parts(gutturals[root_p] and (non_doubling_letters[root_l] and "הֶ" or "הַ") or "הֲ", get_form_medial(root_p), get_form_double(root_l, "a", true), "ָה")
        args["noun"] = args["noun"] or append_parts(gutturals[root_p] and "הֶ" or "הֲ", get_form_medial(root_p), "ָ", get_form_medial(root_l), "ָה")

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts("הָ", get_form_medial(root_p))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ָ", get_form_medial(root_p))
        args["imp_long_pre_stem"] = args["imp_long_pre_stem"] or append_parts(gutturals[root_p] and "הַ" or "הֲ", get_form_medial(root_p))
        args["fut_long_pre_stem"] = args["fut_long_pre_stem"] or append_parts("ְ", get_form_medial(root_p))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings_gem(args, root_l, imp_char)
        get_fut_stem_endings_gem(args, root_l, fut_char)

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מֵ", get_form_medial(root_p))
        args["pres_prs_stem"] = args["pres_prs_stem"] or append_parts("מְ", get_form_medial(root_p))
        get_pres_stem_endings_gem(args, root_l, pres_char)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("הֵ", get_form_medial(root_p))
        args["past_2p_pre_stem"] = args["past_2p_pre_stem"] or append_parts(gutturals[root_p] and "הַ" or "הֲ", get_form_medial(root_p))
        get_past_stem_endings_gem(args, root_l, past_char)
    elseif false then
        -- reserved for special cases
    else
        local af_ = nil
        local if_ = nil
        if args["assim"] ~= "-" then
            af_ = append_parts(gutturals[root_a] and "ָ" or "ַ", add_dagesh(root_a))
            if_ = append_parts(gutturals[root_a] and "ֵ" or "ִ", add_dagesh(root_a))
        elseif monophthongize[root_p] then
            af_ = append_parts(monophthongize[root_p], get_form_medial(root_a))
            if_ = af_
        elseif gutturals[root_p] then
            if args["nochataf"] then
                af_ = append_parts("ַ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
                if_ = append_parts("ֶ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
            else
                af_ = append_parts("ַ", get_form_medial(root_p), "ֲ", get_form_medial(root_a))
                if_ = append_parts("ֶ", get_form_medial(root_p), "ֱ", get_form_medial(root_a))
            end
        else
            af_ = append_parts("ַ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
            if_ = append_parts(root_a == "א" and root_l == "י" and "ֶ" or "ִ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
        end

        args["inf"] = args["inf"] or append_parts("לְה", af_, root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        args["noun"] = args["noun"] or append_parts("ה", af_, "ָ", get_form_medial(root_l), "ָה")

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts("ה", af_)
        args["fut_pre_stem"] = args["fut_pre_stem"] or af_
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a), false, args["assim"] ~= "-")
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מ", af_)
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), false, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("ה", if_)
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["huf"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "a"
    local fut_char = args["fut_char"] or "a"
    local pres_char = args["pres_char"] or "a"

    if args["hollow"] ~= "-" then
        args["imp"] = args["imp"] or "-"
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("וּ", get_form_medial(root_p))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_fut_stem_endings(args, root_l, fut_char, gutturals[root_p])

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מוּ", get_form_medial(root_p))
        get_pres_stem_endings(args, root_l, pres_char, gutturals[root_p], true, true)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("הוּ", get_form_medial(root_p))
        get_past_stem_endings(args, root_l, past_char, gutturals[root_p], false, true)
    elseif args["geminate"] ~= "-" then
        args["imp"] = args["imp"] or "-"
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("וּ", get_form_medial(root_p))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_fut_stem_endings_gem(args, root_l, fut_char)

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מוּ", get_form_medial(root_p))
        get_pres_stem_endings_gem(args, root_l, pres_char)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("הוּ", get_form_medial(root_p))
        get_past_stem_endings_gem(args, root_l, past_char)
    elseif false then
        -- reserved for special cases
    else
        local uf_ = nil
        if args["assim"] ~= "-" then
            uf_ = append_parts({"ו", "ֻ"}, add_dagesh(root_a))
        elseif monophthongize[root_p] then
            uf_ = append_parts("וּ", get_form_medial(root_a))
        elseif gutturals[root_p] then
            if args["nochataf"] then
                uf_ = append_parts({"ו", "ֻ"}, get_form_medial(root_p), "ְ", get_form_initial(root_a))
            else
                uf_ = append_parts({"ו", "ָ"}, get_form_medial(root_p), "ֳ", get_form_medial(root_a))
            end
        else
            uf_ = append_parts({"ו", "ֻ"}, get_form_medial(root_p), "ְ", get_form_initial(root_a))
        end

        args["imp"] = args["imp"] or "-"
        args["fut_pre_stem"] = args["fut_pre_stem"] or uf_
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מ", uf_)
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, true)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("ה", uf_)
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["nif"] = function(args, categories)
    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "a"
    local fut_char = args["fut_char"] or gutturals[root_l] and root_l ~= "א" and "a" or "i"
    local imp_char = args["imp_char"] or fut_char
    local inf_char = args["inf_char"] or fut_char
    local pres_char = args["pres_char"] or "a"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        local if_ = nil
        local suf_if_ = nil
        if args["assim"] ~= "-" then
            if_ = get_form_double(root_a, "i", true)
        elseif monophthongize[root_p] then
            if_ = append_parts(monophthongize[root_p], get_form_medial(root_a))
        elseif gutturals[root_p] then
            local v1 = root_p == "ע" and root_l == "י" and "ַ" or "ֶ"
            if args["nochataf"] then
                if_ = append_parts(v1, get_form_medial(root_p), "ְ", get_form_initial(root_a))
            else
                local v2 = v1 == "ַ" and "ֲ" or "ֱ"
                if_ = append_parts(v1, get_form_medial(root_p), v2, get_form_medial(root_a))
                suf_if_ = append_parts(v1, get_form_medial(root_p), v1, get_form_medial(root_a))
            end
        else
            if_ = append_parts("ִ", get_form_medial(root_p), "ְ", get_form_initial(root_a))
        end

        args["inf"] = args["inf"] or append_parts("לְה", get_form_double(root_p, "i", true), "ָ", get_form_medial(root_a), root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        if root_l == "י" then
            args["noun"] = args["noun"] or append_parts("ה", get_form_double(root_p, "i", true), "ָ", get_form_medial(root_a), "וּת")
        else
            args["noun"] = args["noun"] or append_parts("ה", get_form_double(root_p, "i", true), "ָ", get_form_medial(root_a), shva_na(root_a), get_form_medial(root_l), "וּת")
        end

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts("ה", get_form_double(root_p, "i", true), "ָ", get_form_medial(root_a))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts(get_form_double(root_p, "i", true), "ָ", get_form_medial(root_a))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts(get_form_double(root_p, "e", true), "ָ", get_form_medial(root_a))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a))
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("נ", if_)
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, true)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("נ", if_)
        if suf_if_ then
            local pres_stem_name = root_l == "י" and "past_3fs_pre_stem" or "past_3_pre_stem"
            args[pres_stem_name] = args[pres_stem_name] or append_parts("נ", suf_if_)
        end
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["hit"] = function(args, categories)
    local itp = get_form_itp(args["root_p"])
    local etp = get_form_etp(args["root_p"])
    local root_a = args["root_a"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "i"
    local fut_char = args["fut_char"] or "i"
    local imp_char = args["imp_char"] or fut_char
    local inf_char = args["inf_char"] or fut_char
    local pres_char = args["pres_char"] or "i"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        args["inf"] = args["inf"] or append_parts("לְהִ", itp, get_form_double(root_a, "a"), root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        if root_l == "י" then
            args["noun"] = args["noun"] or append_parts("הִ", itp, get_form_double(root_a, "a"), "וּת")
        else
            args["noun"] = args["noun"] or append_parts("הִ", itp, get_form_double(root_a, "a"), shva_na(root_a), get_form_medial(root_l), "וּת")
        end

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts("הִ", itp, get_form_double(root_a, "a"))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ִ", itp, get_form_double(root_a, "a"))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts("ֶ", etp, get_form_double(root_a, "a"))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a))
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מִ", itp, get_form_double(root_a, "a"))
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("הִ", itp, get_form_double(root_a, "a"))
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["hitpo"] = function(args, categories)
    local itp = get_form_itp(args["root_p"])
    local etp = get_form_etp(args["root_p"])
    local root_a = args["hollow"] == "-" and args["root_a"] or args["root_l"]
    local root_l = args["root_l"]
    local past_char = args["past_char"] or "i"
    local fut_char = args["fut_char"] or "i"
    local imp_char = args["imp_char"] or fut_char
    local inf_char = args["inf_char"] or fut_char
    local pres_char = args["pres_char"] or "i"

    if false then
        -- reserved for special cases
    elseif false then
        -- reserved for special cases
    else
        args["inf"] = args["inf"] or append_parts("לְהִ", itp, "וֹ", get_form_medial(root_a), root_l == "י" and "וֹת" or append_parts(char_vowel[inf_char], get_form_final(root_l, inf_char)))
        if root_l == "י" then
            args["noun"] = args["noun"] or append_parts("הִ", itp, "וֹ", get_form_medial(root_a), "וּת")
        else
            args["noun"] = args["noun"] or append_parts("הִ", itp, "וֹ", get_form_medial(root_a), shva_na(root_a), get_form_medial(root_l), "וּת")
        end

        args["imp_pre_stem"] = args["imp_pre_stem"] or append_parts("הִ", itp, "וֹ", get_form_medial(root_a))
        args["fut_pre_stem"] = args["fut_pre_stem"] or append_parts("ִ", itp, "וֹ", get_form_medial(root_a))
        args["fut_1sp_stem"] = args["fut_1sp_stem"] or append_parts("ֶ", etp, "וֹ", get_form_medial(root_a))
        args["fut_mid_stem"] = args["fut_mid_stem"] or ""
        get_imp_stem_endings(args, root_l, imp_char, ends_in_guttural(root_a))
        get_fut_stem_endings(args, root_l, fut_char, ends_in_guttural(root_a))

        args["pres_pre_stem"] = args["pres_pre_stem"] or append_parts("מִ", itp, "וֹ", get_form_medial(root_a))
        get_pres_stem_endings(args, root_l, pres_char, ends_in_guttural(root_a), true, false)

        args["past_pre_stem"] = args["past_pre_stem"] or append_parts("הִ", itp, "וֹ", get_form_medial(root_a))
        get_past_stem_endings(args, root_l, past_char, ends_in_guttural(root_a), false, true)
    end
end

get_stems_for["hitpu"] = function(args, categories)
    error("Binyan hitpu`al is not yet implemented.")
end

local form_names = {
        ["inf"] = true,
        ["noun"] = true,
        ["pp"] = true,

        ["imp_ms"] = true,
        ["imp_fs"] = true,
        ["imp_mp"] = true,
        ["imp_fp"] = true,

        ["fut_1s"] = true,
        ["fut_2ms"] = true,
        ["fut_2fs"] = true,
        ["fut_3ms"] = true,
        ["fut_3fs"] = true,
        ["fut_1p"] = true,
        ["fut_2mp"] = true,
        ["fut_2fp"] = true,
        ["fut_3mp"] = true,
        ["fut_3fp"] = true,

        ["past_1s"] = true,
        ["past_2ms"] = true,
        ["past_2fs"] = true,
        ["past_3ms"] = true,
        ["past_3fs"] = true,
        ["past_1p"] = true,
        ["past_2mp"] = true,
        ["past_2fp"] = true,
        ["past_3mp"] = true,
        ["past_3fp"] = true,

        ["pres_ms"] = true,
        ["pres_fs"] = true,
        ["pres_mp"] = true,
        ["pres_fp"] = true,
}

local optional_forms = {
        ["inf"] = true,
        ["noun"] = true,
        ["pp"] = true,
}

local table_template = [===[<div>

<div class="NavHead" align="left">{heading}&emsp;</div>
<div class="NavContent" align="center">
{\op}| border="1" color="#cdcdcd" style="width:100%;border-collapse:collapse;line-height:2em;border:2px solid #000000;background:#fdfdfd;text-align:center" class="inflection-table"
! scope='row' colspan="2" style="background:#E4C0CF;border-right:2px solid" | non-finite forms
| colspan="4" style="text-align:left" |
{non_finite}
|- style="border-top:2px solid"
! colspan="2" rowspan="2" style="background:#E4C0CF;border-right:2px solid" | finite forms
! scope='col' colspan="2" style="background:#C0CFE4" | singular
! scope='col' colspan="2" style="background:#C0CFE4" | plural
|-
! scope='col' style="background:#C0CFE4" | m.
! scope='col' style="background:#C0CFE4" | f.
! scope='col' style="background:#C0CFE4" | m.
! scope='col' style="background:#C0CFE4" | f.
|- style="border-top:2px solid"
! scope='row' rowspan="3" style="background:#E2E4C0;width:1px" | past
! scope='row' style="background:#C0CFE4;border-right:2px solid;width:1px" | first
| colspan="2" | {past_1s} || colspan="2" | {past_1p}
|-
! scope='row' style="background:#C0CFE4;border-right:2px solid" | second
| {past_2ms} || {past_2fs} || {past_2mp}{2p_tag} || {past_2fp}{2p_tag}
|-
! scope='row' style="background:#C0CFE4;border-right:2px solid" | third
| {past_3ms} || {past_3fs} || colspan="2" | {past_3mp}
|- style="border-top:2px solid"
! scope='row' colspan="2" style="background:#E2E4C0;border-right:2px solid" | present
| {pres_ms} || {pres_fs} || {pres_mp} || {pres_fp}
|- style="border-top:2px solid"
! scope='row' rowspan="3" style="background:#E2E4C0" | future
! scope='row' style="background:#C0CFE4;border-right:2px solid" | first
| colspan="2" | {fut_1s} || colspan="2" | {fut_1p}
|-
! scope='row' style="background:#C0CFE4;border-right:2px solid" | second
| {fut_2ms} || {fut_2fs} || {fut_2mp} || {fut_2fp}{fp_tag}
|-
! scope='row' style="background:#C0CFE4;border-right:2px solid" | third
| {fut_3ms} || {fut_3fs} || {fut_3mp} || {fut_3fp}{fp_tag}
|- style="border-top:2px solid"
! scope='row' colspan="2" style="background:#E2E4C0;border-right:2px solid" | imperative
| {imp_ms} || {imp_fs} || {imp_mp} || {imp_fp}{fp_tag}
|{\cl}
</div></div>]===]

local non_finite = {
    ["inf"] = "* '''to-infinitive:''' {inf}\n",
    ["noun"] = "* '''action noun:''' {noun}\n",
    ["pp"] = "* '''passive participle:''' {pp}\n",
}

local function make_table(args)
    for key, _ in pairs(form_names) do
        args[key] = args[key] and gen_link(args[key]) or (not optional_forms[key]) and "—"
    end
    args["non_finite"] = m_strutils.format(non_finite["inf"], args)
    if args["noun"] then
        args["non_finite"] = args["non_finite"] .. m_strutils.format(non_finite["noun"], args)
    end
    if args["pp"] then
        args["non_finite"] = args["non_finite"] .. m_strutils.format(non_finite["pp"], args)
    end
    return m_strutils.format(table_template, args)
end

local function track(binyan, args)
    local sub = nil
    local hollow = args["hollow"] ~= "-"
    local assim = args["assim"] ~= "-"
    local elide = args["elide"] ~= "-" and binyan == "pa" -- elide is only relevant to pa`al I think
    local geminate = args["geminate"] ~= "-"
    if binyan == "pa" or binyan == "nif" or binyan == "hif" or binyan == "huf" then
        if hollow then
            if assim or elide or geminate then
                sub = "?"
            else
                sub = "hollow"
            end
        elseif elide then
            if geminate then
                sub = "?"
            elseif assim then
                sub = "assim+elide"
            else
                sub = "elide"
            end
        elseif geminate then
            if assim then
                sub = "geminate+assim"
            else
                sub = "geminate"
            end
        elseif assim then
            sub = "assim"
        end
    end
    local m_debug = require('Module:debug')
    m_debug.track("he-conj/" .. binyan .. (sub and ("/" .. sub) or ""))
    if sub == "?" then
        m_debug.track("he-conj/incompatible-combination")
    end
    if args["past_long"] and args["past_long"] ~= "-" then
        m_debug.track("he-conj/past_long")
    end
    if args["past_long"] and args["fp_long"] ~= "-" then
        m_debug.track("he-conj/fp_long")
    end
end

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
    local args = frame:getParent().args
    local binyan = args[1] or frame.args[1] or error("Binyan has not been specified. Please pass parameter 1 to the module invocation or parent template.")
    -- PAGENAME = mw.title.getCurrentTitle().text
    -- NAMESPACE = mw.title.getCurrentTitle().nsText

    local categories = {}

    parse_root(args)
    convert_root(args)

    local root_p = args["root_p"]
    local root_a = args["root_a"]
    local root_l = args["root_l"]

    if not (root_p and root_a and root_l) then
        error("Missing root letters.")
    end

    args["hollow"] = args["hollow"] or ((root_a == "י" or root_a == "ו") and root_l ~= "י" and "+") or "-"
    args["geminate"] = args["geminate"] or ((root_a == root_l) and root_l ~= "י" and "+") or "-"
    args["assim"] = args["assim"] or (args["hollow"] == "-" and args["geminate"] == "-" and root_p == "נ" and not gutturals[root_a] and "+") or "-"
    args["elide"] = args["elide"] or (args["geminate"] and (args["assim"] ~= "-" == "-" or root_p == "י" or root_p == "ו") and root_l ~= "י" and "+") or "-"

    track(binyan, args)

    if binyan == "-" then
        -- do nothing, stems are given explicitly
    elseif get_stems_for[binyan] then
        get_stems_for[binyan](args, categories)
    else
        error("Unknown binyan code '" .. binyan .. "'")
    end

    conjugate(args) -- finishes conjugation based on stems from get_stems_for[binyan]

    local pre_notes = ""
    if args["past_2mp2"] and not equal(args["past_2mp"], args["past_2mp2"]) then
        args["2p_tag"] = ""
        args["fp_tag"] = ""
        pre_notes = pre_notes .. ""
    else
        args["2p_tag"] = ""
        args["fp_tag"] = ""
        pre_notes = pre_notes .. ""
    end
    args["notes"] = pre_notes .. (args["notes"] or "")

    if args["bot"] or "" ~= "" then
        return make_bot_list(forms, sep ~= "")
    else
        return make_table(args, title) .. m_utilities.format_categories(categories, lang)
    end
end

return export