ويكاموس arwiktionary https://ar.wiktionary.org/wiki/%D9%88%D9%8A%D9%83%D8%A7%D9%85%D9%88%D8%B3:%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A9_%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9 MediaWiki 1.47.0-wmf.12 case-sensitive ميديا خاص نقاش مستخدم نقاش المستخدم ويكاموس نقاش ويكاموس ملف نقاش الملف ميدياويكي نقاش ميدياويكي قالب نقاش القالب مساعدة نقاش المساعدة تصنيف نقاش التصنيف ملحق نقاش الملحق مكنز نقاش المكنز نص زمني نقاش النص الزمني وحدة نقاش الوحدة فعالية نقاش فعالية وحدة:utilities 828 161487 1097589 1003454 2026-07-26T19:30:43Z ForzaGreen 28665 تحديث الاسم بعد نقل [[وحدة:pages/ملعب]] إلى [[وحدة:pages/2]] 1097589 Scribunto text/plain local export = {} local headword_data_module = "Module:headword/data" local languages_module = "Module:languages" local links_module = "Module:links" local load_module = "Module:load" local pages_module = "وحدة:pages/2" local script_utilities_module = "Module:script utilities" local scripts_module = "Module:scripts" local string_utilities_module = "Module:string utilities" local utilities_data_module = "Module:utilities/data" local mw = mw local anchor_encode = mw.uri.anchorEncode local concat = table.concat local format_categories -- Defined below. local ipairs = ipairs local require = require local type = type local unstrip = mw.text.unstrip --[==[ Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==] local function decode_entities(...) decode_entities = require(string_utilities_module).decode_entities return decode_entities(...) end local function get_script(...) get_script = require(scripts_module).getByCode return get_script(...) end local function is_content_page(...) is_content_page = require(pages_module).is_content_page return is_content_page(...) end local function load_data(...) load_data = require(load_module).load_data return load_data(...) end local function remove_links(...) remove_links = require(links_module).remove_links return remove_links(...) end local function tag_text(...) tag_text = require(script_utilities_module).tag_text return tag_text(...) end local function trim(...) trim = require(string_utilities_module).trim return trim(...) end local function uupper(...) uupper = require(string_utilities_module).upper return uupper(...) end --[==[ Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==] local catfix_scripts local function get_catfix_scripts() catfix_scripts, get_catfix_scripts = load_data(utilities_data_module).catfix_scripts, nil return catfix_scripts end local current_title local function get_current_title() current_title, get_current_title = mw.title.getCurrentTitle(), nil return current_title end local defaultsort local function get_defaultsort() defaultsort, get_defaultsort = load_data(headword_data_module).page.pagename_defaultsort, nil return defaultsort end local pagename local function get_pagename() pagename, get_pagename = load_data(headword_data_module).page.encoded_pagename, nil return pagename end local und local function get_und() und, get_und = require(languages_module).getByCode("und"), nil return und end do local function handle_url(capture) return capture:match("https?://[^%s%]]+%s([^%]]+)") or "" end --[==[ A helper function to strip wiki markup, giving the plaintext of what is displayed on the page.]==] function export.get_plaintext(text) text = text :gsub("%[%[", "\1") :gsub("%]%]", "\2") -- Remove strip markers and HTML tags. text = unstrip(text):gsub("<[^<>\1\2]+>", "") -- Parse internal links for the display text, and remove categories. text = remove_links(text) -- Remove files. text = text:gsub("\1[Ff][Ii][Ll][Ee]:[^\1\2]+\2", "") :gsub("\1[Ii][Mm][Aa][Gg][Ee]:[^\1\2]+\2", "") -- Parse external links for the display text. text = text:gsub("%[(https?://[^%[%]]+)%]", handle_url) -- Any remaining square brackets aren't involved in links, but must be escaped to avoid creating new links. :gsub("\1", "&#91;&#91;") :gsub("\2", "&#93;&#93;") :gsub("%[", "&#91;") :gsub("]", "&#93;") -- Strip bold, italics and soft hyphens. :gsub("('*)'''(.-'*)'''", "%1%2") :gsub("('*)''(.-'*)''", "%1%2") :gsub("­", "") -- Get any HTML entities and trim. -- Note: don't decode URL percent encoding, as it shouldn't be used in display text and may cause problems if % is used. return trim(decode_entities(text)) end end do local function generate_sortkey(lang, sc, sort_key, sort_base) -- If the sort key is "-", treat the language as undetermined (the default). This is desirable when categorising (e.g.) translation requests, as the pages to be categorised are always in English/Translingual. if sort_key == "-" then lang, sort_key = und or get_und(), nil elseif not lang then lang = und or get_und() end -- Generate the automatic sort key. local auto = lang:makeSortKey(sort_base or pagename or get_pagename(), sc) -- Use the page defaultsort if necessary. if not auto or auto == "" then auto = defaultsort or get_defaultsort() end -- If not sort key specified, use the automatic one. if not sort_key or sort_key == "" then return auto -- Otherwise, if the language is not "und", categorize the manual sort key as either redundant or non-redundant. -- FIXME: we should do this for "und" as well, but "Undetermined terms..." does not make sense for translations etc. elseif lang:getCode() ~= "und" then return sort_key, "[[Category:" .. lang:getFullName() .. " terms with " .. ( uupper(sort_key) == auto and "redundant" or "non-redundant non-automated" ) .. " sortkeys|" .. sort_key .. "]]" end return sort_key end --[==[ Format the categories with the appropriate sort key. * `cat` can take two forms: ** A string (the full category, minus the {"Category:"} prefix); ** A list of categories. Each category in the list can be either: *** A string in the same format as above; *** An object with the fields: **** `cat`: a string in the same format as above (required); **** `lang`: an optional language object to override the overall `lang`; **** `sc`: an optional script object to override the overall `sc`. **** `sort_key`: an optional sort key to override the overall `sort_key`; **** `sort_base`: an optional sort base to override the overall `sort_base`; * `lang` is an object encapsulating a language; if {nil}, the object for language code {"und"} (undetermined) will be used. `lang` is used when computing the sort key (either from the subpage name or sort base). * `sort_key` is placed in the category invocation, and indicates how the page will sort in the respective category. Normally '''do not use this'''. Instead, leave it {nil}, and if you need to a control the sort order, use {sort_base}, so that language-specific normalization is applied on top of the specified sort base. If neither {sort_key} nor {sort_base} is specified, the default is to apply language-specific normalization to the subpage name; see below. * `sort_base` lets you override the default sort key while still maintaining appropriate language-specific normalization. If {nil} is specified, this defaults to the subpage name, which is the portion of the full pagename after subtracting the namespace prefix (and, in certain namespaces such as {User:}, but notably not in the mainspace, after subtracting anything up through the final slash). The actual sort key is derived from the sort base approximately by lowercasing, applying language-specific normalization and then uppercasing; note that the same process is applied in deriving the sort key when no sort base is specified. For example, for French, Spanish, etc. the normalization process maps accented letters to their unaccented equivalents, so that e.g. in French, {{m|fr|ça}} sorts after {{m|fr|ca}} (instead of after the default Wikimedia sort order, which is approximately based on Unicode sort order and places ç after z) and {{m|fr|côté}} sorts after {{m|fr|coté}} (instead of between c and d). Similarly, in Russian the normalization process converts Cyrillic ё to a string consisting of Cyrillic е followed by U+10FFFF, so that effectively ё sorts after е instead of the default Wikimedia sort, which (I think) puts ё after я, the last letter of the Cyrillic alphabet. * `force_output` forces normal output in all namespaces. Normally, nothing is output if the page isn't in the main, Appendix:, Thesaurus:, Reconstruction: or Citations: namespaces. * `sc` is a script object; if nil, the default will be derived from the sort base (or its default value, the subpage name) by calling {lang:findBestScript()}. The value of `sc` is used during the sort base normalization process; for example, languages with multiple scripts will often have script-specific normalization processes.]==] function export.format_categories(cat, lang, sort_key, sort_base, force_output, sc) if not ( force_output or is_content_page(current_title or get_current_title()) or current_title.prefixedText == "Wiktionary:Sandbox" ) then return "" elseif type(cat) == "string" then local this_sort_key, extra = generate_sortkey(lang, sc, sort_key, sort_base) return "[[Category:" .. cat .. "|" .. this_sort_key .. "]]" .. (extra or "") end local ret, i, n, default = {}, 0, 0 -- Iterate over all categories in `cat`. while true do i = i + 1 local category = cat[i] if category == nil then return concat(ret) end local this_sort_key, extra -- If the category type is a table, use any custom options in it. if type(category) == "table" then category, this_sort_key, extra = category.cat, generate_sortkey( category.lang or lang, category.sc or sc, category.sort_key or sort_key, category.sort_base or sort_base ) -- If `default` has already been determined, use it. elseif default then this_sort_key = default -- Otherwise, calculate `default` and use it. else this_sort_key, extra = generate_sortkey(lang, sc, sort_key, sort_base) default = this_sort_key end n = n + 1 ret[n] = "[[Category:" .. category .. "|" .. this_sort_key .. "]]" .. (extra or "") end end format_categories = export.format_categories end --[==[ Add a "catfix", which is used on language-specific category pages to add language attributes and often script classes to all entry names. The addition of language attributes and script classes makes the entry names display better (using the language- or script-specific styles specified in [[MediaWiki:Common.css]]), which is particularly important for non-English languages that do not have consistent font support in browsers. Language attributes are added for all languages, but script classes are only added for languages with one script listed in their data file, or for languages that have a default script listed in the {catfix_script} list in [[Module:utilities/data]]. Some languages clearly have a default script, but still have other scripts listed in their data file. If those other scripts are not simply scripts like {{cd|Brai}} (Braille), their default script needs to be specified. Others do not have a default script. * Serbo-Croatian is regularly written in both the Latin and Cyrillic scripts. Because it uses two scripts, Serbo-Croatian cannot have a script class applied to entries in its category pages, as only one script class can be specified at a time. * German is usually written in the Latin script ({{cd|Latn}}), but Fraktur ({{cd|Latf}}) is also listed in its data file. So German needs an entry in the {catfix_script} list, so that the {{cd|Latn}} (Latin) script class will be applied to entries in its category pages. To find the scripts listed for a language, go to [[Module:languages]] and use the search box to find the data file for the language. To find out what a script code means, search the script code in [[Module:scripts/data]].]==] function export.catfix(lang, sc) if not lang or not lang.getCanonicalName then error('The first argument to the function "catfix" should be a language object from [[Module:languages]] or [[Module:etymology languages]].') end if sc and not sc.getCode then error('The second argument to the function "catfix" should be a script object from [[Module:scripts]].') end -- To add script classes to links on pages created by category boilerplate templates. if not sc then local code = (catfix_scripts or get_catfix_scripts())[lang:getCode()] or catfix_scripts[lang:getFullCode()] if code then sc = get_script(code) end end -- If the language only has a single valid candidate script, apply it as the default. if not sc then local scripts = lang:getScripts() if #scripts == 1 then sc = scripts[1] else -- Iterate over scripts. If there is only one when ignoring -- scripts like Brai, then pick that one. for _, script in ipairs(scripts) do if script:getCode() ~= "Brai" then if sc then -- Multiple candidates - fail. sc = nil break else sc = script end end end end end -- Hack: using a <ul> tag prevents the parser from automatically generating a <p> tag around the catfix element. return "<ul class=\"catfix\" data-anchor=\"" .. anchor_encode(lang:getFullName()) .. "\">" .. tag_text("", lang, sc) .. "</ul>" .. format_categories("Pages using catfix", nil, nil, nil, true) end --[==[ Given a type (as a string) and an arbitrary number of entities, checks whether all of those entities are language, family, script, writing system or Wikimedia language objects. Useful for error handling in functions that require one of these kinds of object. If `noErr` is set, the function returns false instead of throwing an error, which allows customised error handling to be done in the calling function.]==] function export.check_object(typ, noErr, ...) if ... == nil then if noErr then return false end error("Must provide at least one object to check.", 2) end for _, obj in ipairs{...} do if type(obj) ~= "table" or type(obj.hasType) ~= "function" then if noErr then return false end error("Function expected a " .. typ .. " object, but received a " .. type(obj) .. " instead.", 2) elseif not (typ == "object" or obj:hasType(typ)) then for _, wrong_type in ipairs{"family", "language", "script", "Wikimedia language", "writing system"} do if obj:hasType(wrong_type) then if noErr then return false end error("Function expected a " .. typ .. " object, but received a " .. wrong_type .. " object instead.", 2) end end if noErr then return false end error("Function expected a " .. typ .. " object, but received another type of object instead.", 2) end end return true end return export n5akv3gh94epsguca8auvwy5a7i10zw وحدة:ar-verb 828 188226 1097559 1096874 2026-07-26T16:08:25Z ForzaGreen 28665 تبسيط الوحدة 1097559 Scribunto text/plain local export = {} --[=[ This module implements {{ar-conj}} and provides the underlying conjugation functions for {{ar-verb}} (whose actual formatting is done in [[Module:ar-headword]]). Author: User:Benwing, from an early version (2013-2014) by User:Atitarev, User:ZxxZxxZ. ]=] --[=[ TERMINOLOGY: -- "slot" = A particular combination of tense/mood/person/number/etc. Example slot names for verbs are "past_1s" (past tense first-person singular), "juss_pass_3fp" (non-past jussive passive third-person feminine plural) "ap" (active participle). Each slot is filled with zero or more forms. -- "form" = The conjugated Arabic form representing the value of a given slot. -- "lemma" = The dictionary form of a given Arabic term. For Arabic, normally the third person masculine singular past, although other forms may be used if this form is missing (e.g. in passive-only verbs or verbs lacking the past). ]=] --[=[ FIXME: 1. Finish unimplemented conjugation types. Only IX-final-weak left (extremely rare, possibly only one verb اِعْمَايَ (according to Haywood and Nahmad p. 244, who are very specific about the irregular occurrence of alif + yā instead of expected اِعْمَيَّ with doubled yā). Not in Hans Wehr. NOTE: Not true about this, cf. form IX اِرْعَوَى "to desist, to repent, to see the light". Also note form XII اِخْضَوْضَرَ = form IX اِخْضَرَّ "to be or become green". [DONE except for اِعْمَايَ] 2. Implement irregular verbs as special cases and recognize them, e.g. -- laysa "to not be"; only exists in the past tense, no non-past, no imperative, no participles, no passive, no verbal noun. Irregular alternation las-/lays-. [IMPLEMENTABLE USING OVERRIDES] -- istaḥā yastaḥī "be ashamed of" -- this is complex according to Hans Wehr because there are two verbs, regular istaḥyā yastaḥyī "to spare (someone)'s life" and irregular istaḥyā yastaḥyī "to be ashamed to face (someone)", which is irregular because it has the alternate irregular form istaḥā yastaḥī which only applies to this meaning. Currently we follow Haywood and Nahmad in saying that both varieties can be spelled istaḥyā/istaḥā/istaḥḥā, but we should instead use a variant= param similar to حَيَّ to distinguish the two possibilities, and maybe not include istaḥḥā. -- ʿayya/ʿayiya yaʿayyu/yaʿyā "to not find the right way, be incapable of, stammer, falter, fall ill". This appears to be a mixture of a geminate and final-weak verb. Unclear what the whole paradigm looks like. Do the consonant-ending parts in the past follow the final-weak paradigm? Is it the same in the non-past? Or can you conjugate the non-past fully as either geminate or final-weak? -- اِنْمَحَى inmaḥā or يمَّحَى immaḥā "to be effaced, obliterated; to disappear, vanish" has irregular assimilation of inm- to imm- as an alternative. inmalasa "to become smooth; to glide; to slip away; to escape" also has immalasa as an alternative. The only other form VII verbs in Hans Wehr beginning with -m- are inmalaḵa "to be pulled out, torn out, wrenched" and inmāʿa "to be melted, to melt, to dissolve", which are not listed with imm- alternatives, but might have them; if so, we should handle this generally. [DONE] -- يَرَعَ yaraʕa yariʕu "to be a coward, to be chickenhearted" as an alternative form of يَرِعَ yariʕa yayraʕu (as given in Wehr). [IMPLEMENTABLE USING OVERRIDES] 3. Implement individual override parameters for each paradigm part. See Module:fro-verb for an example of how to do this generally. Note that {{temp|ar-conj-I}} and other of the older templates already had such individual override params. [DONE] Irregular verbs already implemented: -- [ḥayya/ḥayiya yaḥyā "live" -- behaves like a normal final-weak verb (e.g. past first singular ḥayītu) except in the past-tense parts with vowel-initial endings (all the third person except for the third feminine plural). The normal singular and dual endings have -yiya- in them, which compresses to -yya-, with the normal endings the less preferred ones. In masculine third plural, expected ḥayū is replaced by ḥayyū by analogy to the -yy- parts, and the regular form is not given as an alternant in John Mace. Barron's 201 verbs appears to have the regular ḥayū as the part, however. Note also that final -yā appears with tall alif. This appears to be a spelling convention of Arabic, also applying in ḥayyā (form II, "to keep (someone) alive") and 'aḥyā (form IV, "to animate, revive, give birth to, give new life to").] -- implemented -- [ittaxadha yattaxidhu "take"] -- implemented -- [sa'ala yas'alu "ask" with alternative jussive/imperative yasal/sal] -- implemented -- [ra'ā yarā "see"] -- implemented -- ['arā yurī "show"] -- implemented -- ['akala ya'kulu "eat" with imperative kul] -- implemented -- ['axadha ya'xudhu "take" with imperative xudh] -- implemented -- ['amara ya'muru "order" with imperative mur] -- implemented --]=] local force_cat = false -- set to true for debugging -- if true, always maintain manual translit during processing, and compare against full translit at the end local debug_translit = false local lang = require("Module:languages").getByCode("ar") local m_links = require("Module:links") local m_string_utilities = require("Module:string utilities") local m_table = require("Module:table") local ar_utilities = require("Module:ar-utilities") local iut = require("Module:inflection utilities") local put = require("Module:parse utilities") local list_to_text = mw.text.listToText local rfind = m_string_utilities.find local rsubn = m_string_utilities.gsub local rmatch = m_string_utilities.match local rsplit = m_string_utilities.split local usub = m_string_utilities.sub local ulen = m_string_utilities.len local u = m_string_utilities.char local unpack = unpack or table.unpack -- Lua 5.2 compatibility local dump = mw.dumpObject -- Within this module, conjugations are the functions that do the actual -- conjugating by creating the parts of a basic verb. -- They are defined further down. local conjugations = {} -- hamza variants local HAMZA = u(0x0621) -- hamza on the line (stand-alone hamza) = ء local HAMZA_ON_ALIF = u(0x0623) local HAMZA_ON_W = u(0x0624) local HAMZA_UNDER_ALIF = u(0x0625) local HAMZA_ON_Y = u(0x0626) local HAMZA_ANY = "[" .. HAMZA .. HAMZA_ON_ALIF .. HAMZA_UNDER_ALIF .. HAMZA_ON_W .. HAMZA_ON_Y .. "]" local HAMZA_PH = u(0xFFF0) -- hamza placeholder local BAD = u(0xFFF1) local BORDER = u(0xFFF2) -- diacritics local A = u(0x064E) -- fatḥa local AN = u(0x064B) -- fatḥatān (fatḥa tanwīn) local U = u(0x064F) -- ḍamma local UN = u(0x064C) -- ḍammatān (ḍamma tanwīn) local I = u(0x0650) -- kasra local IN = u(0x064D) -- kasratān (kasra tanwīn) local SK = u(0x0652) -- sukūn = no vowel local SH = u(0x0651) -- šadda = gemination of consonants local DAGGER_ALIF = u(0x0670) local DIACRITIC_ANY_BUT_SH = "[" .. A .. I .. U .. AN .. IN .. UN .. SK .. DAGGER_ALIF .. "]" -- Pattern matching short vowels local AIU = "[" .. A .. I .. U .. "]" -- Pattern matching short vowels or sukūn local AIUSK = "[" .. A .. I .. U .. SK .. "]" -- Pattern matching any diacritics that may be on a consonant local DIACRITIC = SH .. "?" .. DIACRITIC_ANY_BUT_SH -- translit_patterns local vowels = "aeiouāēīōū" local NV = "[^" .. vowels .. "]" local dia = {a = A, i = I, u = U} local undia = {[A] = "a", [I] = "i", [U] = "u", ["-"] = "-"} -- various letters and signs local ALIF = u(0x0627) -- ʾalif = ا local AMAQ = u(0x0649) -- ʾalif maqṣūra = ى local AMAD = u(0x0622) -- ʾalif madda = آ local TAM = u(0x0629) -- tāʾ marbūṭa = ة local T = u(0x062A) -- tāʾ = ت local HYPHEN = u(0x0640) local N = u(0x0646) -- nūn = ن local W = u(0x0648) -- wāw = و local Y = u(0x064A) -- yāʾ = ي local S = "س" local M = "م" local LRM = u(0x200e) -- left-to-right mark -- common combinations local AH = A .. TAM local AT = A .. T local AA = A .. ALIF local AAMAQ = A .. AMAQ local AAH = AA .. TAM local AAT = AA .. T local II = I .. Y local UU = U .. W local AY = A .. Y local AW = A .. W local AYSK = AY .. SK local AWSK = AW .. SK local NA = N .. A local NI = N .. I local AAN = AA .. N local AANI = AA .. NI local AYNI = AYSK .. NI local AWNA = AWSK .. NA local AYNA = AYSK .. NA local AYAAT = AY .. AAT local UNU = "[" .. UN .. U .. "]" local MA = M .. A local MU = M .. U local TA = T .. A local TU = T .. U local _I = ALIF .. I local _U = ALIF .. U --W: Morphological pattern (الوزن الصرفي) to verb form mapping local MORPHOLOGICAL_PATTERN_TO_FORM = { ["فعَل يفعُل"] = "I/a~u", ["فعَل يفعِل"] = "I/a~i", ["فعَل يفعَل"] = "I/a~a", ["فعُل يفعُل"] = "I/u~u", ["فعِل يفعَل"] = "I/i~a", ["فعِل يفعِل"] = "I/i~i", ["فعّل"] = "II", ["فاعل"] = "III", ["أفعل"] = "IV", ["تفعّل"] = "V", ["تفاعل"] = "VI", ["انفعل"] = "VII", ["افتعل"] = "VIII", ["افعلّ"] = "IX", ["استفعل"] = "X", ["افعالّ"] = "XI", ["افعوعل"] = "XII", ["افعوّل"] = "XIII", ["فعلل"] = "Iq", ["تفعلل"] = "IIq", ["افعنلل"] = "IIIq", -- "XIV" should not be used ["افعللّ"] = "IVq", } --W: Create reverse mapping from form to morphological pattern local FORM_TO_MORPHOLOGICAL_PATTERN = {} for pattern, form in pairs(MORPHOLOGICAL_PATTERN_TO_FORM) do FORM_TO_MORPHOLOGICAL_PATTERN[form] = pattern end --W: local MORPHOLOGICAL_PATTERN_TO_BASIC_DERIV = { ["فعَل يفعُل"] = "مُجرَّد", ["فعَل يفعِل"] = "مُجرَّد", ["فعَل يفعَل"] = "مُجرَّد", ["فعُل يفعُل"] = "مُجرَّد", ["فعِل يفعَل"] = "مُجرَّد", ["فعِل يفعِل"] = "مُجرَّد", ["فعّل"] = "مزيد بحرف", ["فاعل"] = "مزيد بحرف", ["أفعل"] = "مزيد بحرف", ["تفعّل"] = "مزيد بحرفين", ["تفاعل"] = "مزيد بحرفين", ["انفعل"] = "مزيد بحرفين", ["افتعل"] = "مزيد بحرفين", ["افعلّ"] = "مزيد بحرفين", ["استفعل"] = "مزيد بثلاثة أحرف", ["افعالّ"] = "مزيد بثلاثة أحرف", ["افعوعل"] = "مزيد بثلاثة أحرف", ["افعوّل"] = "مزيد بثلاثة أحرف", ["فعلل"] = "مُجرَّد", ["تفعلل"] = "مزيد بحرف", ["افعنلل"] = "مزيد بحرفين", ["افعللّ"] = "مزيد بحرفين", } local translit_cache = { -- hamza variants [HAMZA] = "ʔ", [HAMZA_ON_ALIF] = "ʔ", [HAMZA_ON_W] = "ʔ", [HAMZA_UNDER_ALIF] = "ʔ", [HAMZA_ON_Y] = "ʔ", [HAMZA_PH] = "ʔ", -- diacritics [A] = "a", [AN] = "an", [U] = "u", [UN] = "un", [I] = "i", [IN] = "in", [SK] = "", [SH] = "*", -- handled specially [DAGGER_ALIF] = "ā", -- various letters and signs [""] = "", [ALIF] = BAD, -- we should never be transliterating ALIF by itself, as its translit in isolation is ambiguous [AMAQ] = BAD, [AMAD] = "ʔā", [TAM] = "", [T] = "t", [N] = "n", [W] = "w", [Y] = "y", [S] = "s", [M] = "m", [LRM] = "", -- common combinations [AH] = "a", [AT] = "at", [AA] = "ā", [AAMAQ] = "ā", [AAH] = "āh", [AAT] = "āt", [II] = "ī", [UU] = "ū", [AY] = "ay", [AW] = "aw", [AYSK] = "ay", [AWSK] = "aw", [NA] = "na", [NI] = "ni", [AAN] = "ān", [AANI] = "āni", [AYNI] = "ayni", [AWNA] = "awna", [AYNA] = "ayna", [AYAAT] = "ayāt", [MA] = "ma", [MU] = "mu", [TA] = "ta", [TU] = "tu", [_I] = "i", [_U] = "u", } local function transliterate(text) local cached = translit_cache[text] if cached then if cached == BAD then error(("Internal error: Unable to transliterate %s because explicitly marked as BAD"):format(text)) end return cached end local tr = (lang:transliterate(text)) if not tr then error(("Internal error: Unable to transliterate: %s"):format(text)) end translit_cache[text] = tr return tr end local all_person_number_list = { "1s", "2ms", "2fs", "3ms", "3fs", "2d", "3md", "3fd", "1p", "2mp", "2fp", "3mp", "3fp" } local function make_person_number_slot_accel_list(list) local slot_accel_list = {} return slot_accel_list end local imp_person_number_list = {} for _, pn in ipairs(all_person_number_list) do if pn:find("^2") then table.insert(imp_person_number_list, pn) end end local passive_types = m_table.listToSet { "pass", -- verb has both active and passive "ipass", -- verb is active with impersonal passive "nopass", -- verb is active-only "onlypass", -- verb is passive-only "onlypass-impers", -- verb itself is impersonal, meaning passive-only with impersonal passive } local indicator_flags = m_table.listToSet { "nopast", "no_nonpast", "noimp", "nocat", -- don't categorize or include annotations about this; useful in suppletive parts of verbs "reduced", -- verb has assimilation/reduction of initial coronals "altgem", -- form X with alternative past geminate forms with final-weak endings } export.potential_lemma_slots = {"past_3ms", "past_pass_3ms", "ind_3ms", "ind_pass_3ms", "imp_2ms"} export.unsettable_slots = {} for _, potential_lemma_slot in ipairs(export.potential_lemma_slots) do table.insert(export.unsettable_slots, potential_lemma_slot .. "_linked") end -- We don't set the active participle directly for form I because we don't want stative verbs (with past vowel i or u) -- to default to فَاعِل. Instead we set the special slot 'ap1' and later copy it to 'ap' for non-stative verbs. The user -- meanwhile can explicitly request the فَاعِل form for active participles for stative verbs using `ap:+`. table.insert(export.unsettable_slots, "ap1") -- primary default فَاعِل for form I active participles table.insert(export.unsettable_slots, "ap2") -- secondary default فَعِيل for form I active participles (stative I) table.insert(export.unsettable_slots, "ap3") -- secondary default فَعِل for form I active participles (stative II) table.insert(export.unsettable_slots, "apcd") -- secondary default أَفْعَل for form I active participles (color/defect) table.insert(export.unsettable_slots, "apan") -- secondary default فَعْلَان for form I active participles (in -ān) table.insert(export.unsettable_slots, "pp2") -- secondary default فَعِيل for form I passive participles (same as ap2) table.insert(export.unsettable_slots, "vn2") -- secondary default فِعَال for form III verbal nouns export.unsettable_slots_set = m_table.listToSet(export.unsettable_slots) local default_indicator_to_active_participle_slot = { ["+"] = "ap1", ["++"] = "ap2", ["+++"] = "ap3", ["+cd"] = "apcd", ["+an"] = "apan", } local slots_that_may_be_uncertain = { vn = "verbal noun", ap = "active participle", } -- Initialize all the slots for which we generate forms. local function add_slots(alternant_multiword_spec) alternant_multiword_spec.verb_slots = { {"ap", "act|part"}, {"pp", "pass|part"}, {"vn", "vnoun"}, } for _, unsettable_slot in ipairs(export.unsettable_slots) do table.insert(alternant_multiword_spec.verb_slots, {unsettable_slot, "-"}) end -- Add entries for a slot with person/number variants. -- `slot_prefix` is the prefix of the slot, typically specifying the tense/aspect. -- `tag_suffix` is a string listing the set of inflection tags to add after the person/number tags. -- `person_number_list` is a list of the person/number slot suffixes to add to `slot_prefix`. local function add_personal_slot(slot_prefix, tag_suffix, person_number_list) for _, persnum in ipairs(person_number_list) do local slot = slot_prefix .. "_" .. persnum local accel = persnum:gsub("(.)", "%1|") .. tag_suffix table.insert(alternant_multiword_spec.verb_slots, {slot, accel}) end end local tenses = { {"past", "past|%s"}, {"ind", "non-past|%s|ind"}, {"sub", "non-past|%s|sub"}, {"juss", "non-past|%s|juss"}, } for _, slot_accel in ipairs(tenses) do local slot, accel = unpack(slot_accel) for _, voice in ipairs {"act", "pass"} do add_personal_slot(voice == "act" and slot or slot .. "_pass", accel:format(voice), all_person_number_list) end end add_personal_slot("imp", "imp", imp_person_number_list) alternant_multiword_spec.verb_slots_map = {} for _, slot_accel in ipairs(alternant_multiword_spec.verb_slots) do local slot, accel = unpack(slot_accel) alternant_multiword_spec.verb_slots_map[slot] = accel end end local overridable_stems = {} local slot_override_param_mods = { footnote = { item_dest = "footnotes", store = "insert", }, alt = {}, t = { -- [[Module:links]] expects the gloss in "gloss". item_dest = "gloss", }, gloss = {}, g = { -- [[Module:links]] expects the genders in "g". `sublist = true` automatically splits on comma (optionally -- with surrounding whitespace). item_dest = "genders", sublist = true, }, pos = {}, lit = {}, id = {}, --W: The q/qq/l/ll (qualifier and label) modifiers were removed: rendering them -- needs [[Module:pron qualifier]], which does not exist on ar.wiktionary, so any -- use of them raised a script error. Accepting them here again requires importing -- that module first and restoring the block in generate_link() below. } local function generate_obj(formval, parse_err, prefix, is_slot_override) local val, uncertain = formval:match("^(.*)(%?)$") val = val or formval uncertain = not not uncertain local ar, translit = val:match("^(.*)//(.*)$") if not ar then ar = val end if ar == "" then if uncertain then ar = "?" else error(("Can't specify blank value for override for %s override '%s'"):format( is_slot_override and "slot" or "stem", prefix)) end end return {form = ar, translit = translit, uncertain = uncertain} end local function parse_inline_modifiers(comma_separated_group, parse_err, prefix, is_slot_override) local function this_generate_obj(formval, parse_err) return generate_obj(formval, parse_err, prefix, is_slot_override) end return put.parse_inline_modifiers_from_segments { group = comma_separated_group, props = { param_mods = slot_override_param_mods, parse_err = parse_err, generate_obj = this_generate_obj, pre_normalize_modifiers = function(data) local modtext = data.modtext modtext = modtext:match("^(%[.*%])$") if modtext then return ("<footnote:%s>"):format(modtext) end return data.modtext end, }, } end local function allow_multiple_values_for_override(comma_separated_groups, data, is_slot_override) local retvals = {} for _, comma_separated_group in ipairs(comma_separated_groups) do local retval if is_slot_override then retval = parse_inline_modifiers(comma_separated_group, data.parse_err) else retval = generate_obj(comma_separated_group[1], data.parse_err, data.prefix, is_slot_override) retval.footnotes = data.fetch_footnotes(comma_separated_group) end table.insert(retvals, retval) end for _, form in ipairs(retvals) do if form.form == "+" or default_indicator_to_active_participle_slot[form.form] then if form.form ~= "+" and default_indicator_to_active_participle_slot[form.form] and not is_slot_override then error(("Stem override '%s' cannot use %s to request a secondary default"):format( data.prefix, form.form)) end data.base.slot_override_uses_default[data.prefix] = true end end for _, form in ipairs(retvals) do if form.form == "-" then data.base.slot_explicitly_missing[data.prefix] = true break end end if data.base.slot_explicitly_missing[data.prefix] then for _, form in ipairs(retvals) do if form.form ~= "-" then data.parse_err(("For slot or stem '%s', saw both - and a value other than -, which isn't allowed"): format(data.prefix)) end end return nil end return retvals end local function simple_choice(choices) return function(separated_groups, data) if #separated_groups > 1 then data.parse_err("For spec '" .. data.prefix .. ":', only one value currently allowed") end if #separated_groups[1] > 1 then data.parse_err("For spec '" .. data.prefix .. ":', no footnotes currently allowed") end local choice = separated_groups[1][1] if not m_table.contains(choices, choice) then data.parse_err("For spec '" .. data.prefix .. ":', saw value '" .. choice .. "' but expected one of '" .. table.concat(choices, "،") .. "'") end return choice end end for _, overridable_stem in ipairs { "past", "past_v", "past_c", "past_pass", "past_pass_v", "past_pass_c", "nonpast", "nonpast_v", "nonpast_c", "nonpast_pass", "nonpast_pass_v", "nonpast_pass_c", "imp", "imp_v", "imp_c", } do overridable_stems[overridable_stem] = allow_multiple_values_for_override end overridable_stems.past_final_weak_vowel = simple_choice { "ay", "aw", "ī", "ū" } overridable_stems.past_pass_final_weak_vowel = simple_choice { "ay", "aw", "ī", "ū" } overridable_stems.nonpast_final_weak_vowel = simple_choice { "ā", "ī", "ū" } overridable_stems.nonpast_pass_final_weak_vowel = simple_choice { "ā", "ī", "ū" } ------------------------------------------------------------------------------- -- Utility functions -- ------------------------------------------------------------------------------- -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) return (rsubn(term, foo, bar)) end -- version of rsubn() that returns a 2nd argument boolean indicating whether a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end -- Concatenate one or more strings or form objects. local function q(...) local not_all_strings = debug_translit local has_manual_translit = debug_translit for i = 1, select("#", ...) do local argt = select(i, ...) if not argt then error(("Internal error: Saw nil at index %s: %s"):format(i, dump({...}))) end if type(argt) ~= "string" then not_all_strings = true if argt.translit then has_manual_translit = true break end end end if not not_all_strings then -- just strings, concatenate directly return table.concat({...}) end local formvals = {} local translit = has_manual_translit and {} or nil local footnotes for i = 1, select("#", ...) do local argt = select(i, ...) if type(argt) == "string" then formvals[i] = argt if has_manual_translit then translit[i] = transliterate(argt) end else formvals[i] = argt.form if has_manual_translit then translit[i] = argt.translit or transliterate(argt.form) end footnotes = iut.combine_footnotes(footnotes, argt.footnotes) end end -- FIXME: Do we want to support other properties? return { form = table.concat(formvals), translit = has_manual_translit and table.concat(translit) or nil, footnotes = footnotes, } end -- Return the formval associated with `rad` (a radical or past/non-past vowel, either a string or form object). local function rget(rad) if type(rad) == "string" then return rad elseif type(rad) == "table" then return rad.form else error(("Internal error: Unexpected type for radical or past/non-past vowel: %s"):format(dump(rad))) end end export.rget = rget -- for use in [[Module:ar-headword]] -- Return the footnotes associated with `rad` (a radical or past/non-past vowel, either a string or form object). local function rget_footnotes(rad) if type(rad) == "string" then return nil elseif type(rad) == "table" then return rad.footnotes else error(("Internal error: Unexpected type for radical or past/non-past vowel: %s"):format(dump(rad))) end end -- Return true if the formval associated with `rad` (a radical or past/non-past vowel, either a string or form object) -- is `val`. local function req(rad, val) return rget(rad) == val end -- Map `vow` (a past/non-past vowel, either a string or form object without translit) by passing the formval through -- `fn`. Don't call this on radicals because they may have manual translit and it isn't clear how to handle that. local function map_vowel(vow, fn) if type(vow) == "string" then return fn(vow) elseif type(vow) == "table" then return {form = fn(vow.form), footnotes = vow.footnotes} else error(("Internal error: Unexpected type for past/non-past vowel: %s"):format(dump(vow))) end end local function get_radicals_3(vowel_spec) return vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3, vowel_spec.past, vowel_spec.nonpast end local function get_radicals_4(vowel_spec) return vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3, vowel_spec.rad4 end local function is_final_weak(base, vowel_spec) return vowel_spec.weakness == "final-weak" or base.form == "XV" end --W: Create a simple link local function link_term_simple(text) return "[[" .. text .. "]]" end local function link_term(text, face, id) return m_links.full_link({lang = lang, term = text, tr = "-", id = id}, face) end local function tag_text(text, tag, class) return m_links.full_link({lang = lang, alt = text, tr = "-"}) end local function track(page) require("Module:debug/track")("ar-verb/" .. page) return true end local function track_if_ar_conj(base, page) if base.alternant_multiword_spec.source_template == "ar-conj" then require("Module:debug/track")("ar-verb/" .. page) end return true end local function reorder_shadda(word) -- shadda+short-vowel (including tanwīn vowels, i.e. -an -in -un) gets -- replaced with short-vowel+shadda during NFC normalisation, which -- MediaWiki does for all Unicode strings; however, it makes various -- processes inconvenient, so undo it. word = rsub(word, "(" .. DIACRITIC_ANY_BUT_SH .. ")" .. SH, SH .. "%1") return word end local function apply_nfc_shadda(word) -- Apply NFC-style normalization: reorder shadda+vowel to vowel+shadda -- to match MediaWiki's automatic NFC normalization. This reverses the -- internal shadda+vowel format used during processing. word = rsub(word, SH .. "(" .. DIACRITIC_ANY_BUT_SH .. ")", "%1" .. SH) return word end ------------------------------------------------------------------------------- -- Basic functions to inflect tenses -- ------------------------------------------------------------------------------- local function skip_slot(base, slot, allow_overrides) if base.slot_explicitly_missing[slot] then return true end if not allow_overrides and base.slot_overrides[slot] and not base.slot_override_uses_default[slot] then -- Skip any slots for which there are overrides, except those that request the default value using +, ++, etc. return true end if base.passive == "nopass" and (slot == "pp" or slot:find("_pass")) then return true elseif base.passive == "onlypass" and slot ~= "pp" and slot ~= "vn" and not slot:find("_pass") then return true elseif base.passive == "ipass" and slot:find("_pass") and not slot:find("3ms") then return true elseif base.passive == "onlypass-impers" and slot ~= "pp" and slot ~= "vn" and (not slot:find("_pass") or slot:find("_pass") and not slot:find("3ms")) then return true end if base.nopast and slot:find("^past_") then return true end if base.noimp and slot:find("^imp_") then return true end if base.no_nonpast and (slot:find("^ind_") or slot:find("^sub_") or slot:find("^juss")) then return true end return false end local function basic_combine_stem_ending(stem, ending) return stem .. ending end local function basic_combine_stem_ending_tr(stem, ending) return stem .. ending end -- Concatenate `prefixes`, `stems` and `endings` (any of which may be an abbreviate form list, i.e. strings, form -- objects or lists of strings or form objects) and store into `slot`. If a user-supplied override exists for the slot, -- nothing will happen unless `allow_overrides` is provided. local function add3(base, slot, prefixes, stems, endings, allow_overrides) if skip_slot(base, slot, allow_overrides) then return end -- Optimization since the prefixes are almost always single strings. if type(prefixes) == "string" then local function do_combine_stem_ending(stem, ending) return prefixes .. stem .. ending end local function do_combine_stem_ending_tr(stem, ending) return transliterate(prefixes) .. stem .. ending end iut.add_forms(base.forms, slot, stems, endings, do_combine_stem_ending, transliterate, do_combine_stem_ending_tr, base.form_footnotes) else iut.add_multiple_forms(base.forms, slot, {prefixes, stems, endings}, basic_combine_stem_ending, transliterate, basic_combine_stem_ending_tr, base.form_footnotes) end end -- Insert one or more forms in `form_or_forms` into `slot`. `form_or_forms` is an abbreviated form list (see comment at -- top of [[Module:inflection utilities]]). If a user-supplied override exists for the slot, nothing will happen unless -- `allow_overrides` is provided. BEWARE: One form object should never occur in two different slots, or twice in a given -- slot; if taking a form object from an existing slot, make sure to shallowCopy() it. local function insert_form_or_forms(base, slot, form_or_forms, allow_overrides, uncertain) if not skip_slot(base, slot, allow_overrides) then -- Some optimizations of the most common case of inserting a single string. if type(form_or_forms) == "string" and not base.form_footnotes then form_or_forms = {form = form_or_forms, uncertain = uncertain} iut.insert_form(base.forms, slot, form_or_forms) else local list = iut.convert_to_general_list_form(form_or_forms, base.form_footnotes) if uncertain then for _, formobj in ipairs(list) do formobj.uncertain = true end end iut.insert_forms(base.forms, slot, list) end end end -- Insert `string_or_form` into both the ap2 and pp2 slots, shallowCopying a form object to make sure no form objects -- occur in two slots. local function insert_ap2_pp2(base, string_or_form) insert_form_or_forms(base, "ap2", string_or_form) if type(string_or_form) == "table" then string_or_form = m_table.shallowCopy(string_or_form) end insert_form_or_forms(base, "pp2", string_or_form) end -- Convert `stemforms` (a string, a form object, or a list of strings and/or form objects) into "general form" (a list -- of form objects) and map `fn` over the list of objects. `fn` is passed two arguments (form value and translit) and -- should likewise return the new form value and translit. Footnotes will be preserved. FIXME: Preserve other metadata. local function map_general(stemforms, fn) return iut.map_forms(iut.convert_to_general_list_form(stemforms), fn) end -- Similar to map_general() except that `fn` should return a single value (one or more strings or form objects), instead -- of two values (form value and translit), and the resulting value(s) from all calls to `fn` will be flattened to -- construct the overall return value. Footnotes will be preserved. FIXME: Preserve other metadata. local function flatmap_general(stemforms, fn) return iut.flatmap_forms(iut.convert_to_general_list_form(stemforms), fn) end -- Given user-supplied stem overrides in `base`, construct any derived stem overrides (e.g. vowel-specific or -- consonant-specific variants), and truncate initial y-/ي- in any non-past overrides. local function construct_stems(base) local stems = base.stem_overrides stems.past_v = stems.past_v or stems.past stems.past_c = stems.past_c or stems.past stems.past_pass_v = stems.past_pass_v or stems.past_pass stems.past_pass_c = stems.past_pass_c or stems.past_pass stems.nonpast_v = stems.nonpast_v or stems.nonpast stems.nonpast_c = stems.nonpast_c or stems.nonpast stems.nonpast_pass_v = stems.nonpast_pass_v or stems.nonpast_pass stems.nonpast_pass_c = stems.nonpast_pass_c or stems.nonpast_pass stems.imp_v = stems.imp_v or stems.imp stems.imp_c = stems.imp_c or stems.imp local function truncate_nonpast_initial_cons(stem_type, form, translit) if form == "+" then return form, translit end if not form:find("^" .. Y) then error(("Form value %s for stem type '%s' should begin with ي"):format(form, stem_type)) end form = form:gsub("^" .. Y, "") if translit then if not translit:find("^y") then error(("Translit value %s for stem type '%s' should begin with y"):format(translit, stem_type)) end translit = translit:gsub("^y", "") end return form, translit end for _, nonpast_stem_type in ipairs { "nonpast_v", "nonpast_c", "nonpast_pass_v", "nonpast_pass_c" } do if stems[nonpast_stem_type] then stems[nonpast_stem_type] = map_general(stems[nonpast_stem_type], function(form, translit) return truncate_nonpast_initial_cons(nonpast_stem_type, form, translit) end) end end end -- Given user-specified overrides for stem `stemname`, return overrides with occurrences of + replaced by -- `default_stem`. If no overrides, return `default_stem`, or {} if no default. local function override_stem_if_needed(base, stemname, default_stem) local overrides = base.stem_overrides[stemname] if not overrides then return default_stem or {} end return map_general(overrides, function(form, translit) if form ~= "+" and default_indicator_to_active_participle_slot[form] then error(("Stem overrides cannot use secondary default indicators but saw %s in stem override '%s'"):format( form, stemname)) end if form == "+" then if translit then error(("Cannot supply manual translit along with + for stem override '%s'"):format(stemname)) end if not default_stem then error(("Cannot use + for stem override '%s' because no default is available"):format(stemname)) end if type(default_stem) ~= "string" then error(("Internal error: Default stem for '%s' is not a string: %s"):format(stemname, dump(default_stem))) end return default_stem end return form, translit end) end ------------------------------------------------------------------------------- -- Properties of different verbal forms -- ------------------------------------------------------------------------------- local allowed_vforms = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "Iq", "IIq", "IIIq", "IVq"} local allowed_vforms_set = m_table.listToSet(allowed_vforms) local allowed_vforms_with_weakness = m_table.shallowCopy(allowed_vforms) -- The user needs to be able to explicitly specify that a form-I verb (specifically one whose initial radical is و) is -- sound. Cf. wajiʕa yawjaʕu (not #yajaʕu) "to ache, to hurt". In general, i~a and u~u verbs whose initial radical is و -- seem to not assimilate the first radical; cf. وقح "to be shameless", variously waqaḥa~yaqiḥu, waquḥa~yawquḥu and -- waqiḥa~yawqaḥu, whereas a~i verbs (wafaḍa~yafiḍu "to rush"), i~i verbs (wafiqa~yafiqu "to be proper, to be suitable") -- and a~a verbs (waḍaʕa~yaḍaʕu "to set down, to place") do assimilate. But there are naturally exceptions, e.g. -- waṭiʔa~yaṭaʔu "to tread, to trample"; wasiʕa~yasaʕu "to be spacious; to be well-off"; waṯiʔa~yaṯaʔu "to get bruised, -- to be sprained". Also beware of waniya~yawnā "to be faint; to languish", which is sound in the first radical and -- final-weak in the last radical. Nonetheless, the regularity of the patterns mentioned above suggest we should provide -- them as defaults. -- Note that there are other cases of unexpectedly sound verbs, e.g. izdawaja~yazdawiju "to be in pairs", layisa~yalyasu -- "to be valiant, to be brave", ʔaḥwaja~yuḥwiju "to need", istahwana~yastahwinu "to consider easy", sawisa~yaswasu "to -- be or become moth-eaten or worm-eaten" (vs. sāsa~yasūsu "to govern, to rule" from the same radicals), ʕawira~yaʕwaru -- "to be one-eyed", istajwaba~yastajwibu "to interrogate", etc. But in these cases there is no need for explicit user -- specification as the lemma itself specifies the unexpected soundness. for _, form_with_weakness in ipairs { "I-sound", "I-assimilated", "none-sound", "none-hollow", "none-geminate", "none-final-weak" } do table.insert(allowed_vforms_with_weakness, form_with_weakness) end local allowed_vforms_with_weakness_set = m_table.listToSet(allowed_vforms_with_weakness) local function vform_supports_final_weak(vform) return vform ~= "XI" and vform ~= "XV" and vform ~= "IVq" end local function vform_supports_geminate(vform) return vform == "I" or vform == "III" or vform == "IV" or vform == "VI" or vform == "VII" or vform == "VIII" or vform == "X" end local function vform_supports_hollow(vform) return vform == "I" or vform == "IV" or vform == "VII" or vform == "VIII" or vform == "X" end local function vform_probably_impersonal_passive(vform, weakness, past_vowel, nonpast_vowel) return vform == "I" and req(past_vowel, I) or vform == "V" or vform == "VI" or vform == "X" or vform == "IIq" end local function vform_probably_full_passive(vform) return vform == "II" or vform == "III" or vform == "IV" or vform == "Iq" end local function vform_probably_no_passive(vform, weakness, past_vowel, nonpast_vowel) return vform == "I" and req(past_vowel, U) or vform == "VII" or vform == "IX" or vform == "XI" or vform == "XII" or vform == "XIII" or vform == "XIV" or vform == "XV" or vform == "IIIq" or vform == "IVq" end -- Active vforms II, III, IV, Iq use non-past prefixes in -u- instead of -a-. local function prefix_vowel_from_vform(vform) if vform == "II" or vform == "III" or vform == "IV" or vform == "Iq" then return "u" else return "a" end end -- True if the active non-past takes a-vocalization rather than i-vocalization in its last syllable. local function vform_nonpast_a_vowel(vform) return vform == "V" or vform == "VI" or vform == "XV" or vform == "IIq" end -- True if the `passive` spec indicates a passive-only verb. local function is_passive_only(passive) return passive == "onlypass" or passive == "onlypass-impers" end export.is_passive_only = is_passive_only -- for use in [[Module:ar-headword]] ------------------------------------------------------------------------------- -- Properties of specific sounds -- ------------------------------------------------------------------------------- -- Is radical wāw (و) or yāʾ (ي)? local function is_waw_ya(rad) return req(rad, W) or req(rad, Y) end -- Check that radical is wāw (و) or yāʾ (ي), error if not local function check_waw_ya(rad) if not is_waw_ya(rad) then error("Expecting weak radical: '" .. rget(rad) .. "' should be " .. W .. " or " .. Y) end end -- Form-I verb حيّ or حيي and form-X verb استحيا or استحى local function hayy_radicals(rad1, rad2, rad3) return req(rad1, "ح") and req(rad2, Y) and is_waw_ya(rad3) end -- FUCK ME HARD. "Lua error at line 1514: main function has more than 200 local variables". local function create_conjugations() ------------------------------------------------------------------------------- -- Radicals associated with various irregular verbs -- ------------------------------------------------------------------------------- -- Form-I verb أخذ or form-VIII verb اتخذ local function axadh_radicals(rad1, rad2, rad3) return req(rad1, HAMZA) and req(rad2, "خ") and req(rad3, "ذ") end -- Form-I verb whose imperative has a reduced form: أكل and أخذ and أمر. Return "shortonly" if only -- short-form imperatives exist (أكل and أخذ) or "shortlong" if long-form imperatives also exist (أمر); -- they are used after a clitic like فَ and وَ. local function reduced_imperative_verb(rad1, rad2, rad3) return axadh_radicals(rad1, rad2, rad3) and "shortonly" or req(rad1, HAMZA) and req(rad2, "ك") and req(rad3, "ل") and "shortonly" or req(rad1, HAMZA) and req(rad2, "م") and req(rad3, "ر") and "shortlong" end -- Form-I verb رأى and form-IV verb أرى local function raa_radicals(rad1, rad2, rad3) return req(rad1, "ر") and req(rad2, HAMZA) and is_waw_ya(rad3) end -- Form-I verb سأل local function saal_radicals(rad1, rad2, rad3) return req(rad1, "س") and req(rad2, HAMZA) and req(rad3, "ل") end -- Form-I verb كان local function kaan_radicals(rad1, rad2, rad3) return req(rad1, "ك") and req(rad2, W) and req(rad3, N) end ------------------------------------------------------------------------------- -- Sets of past endings -- ------------------------------------------------------------------------------- -- The 13 endings of the sound/hollow/geminate past tense. local past_endings = { -- singular SK .. TU, SK .. TA, SK .. "تِ", A, A .. "تْ", --dual SK .. "تُمَا", AA, A .. "تَا", -- plural SK .. "نَا", SK .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this SK .. "تُن" .. SH .. A, UU .. ALIF, SK .. "نَ" } -- Make endings for final-weak past in -aytu or -awtu. AYAW is AY or AW as appropriate. Note that AA and AW are -- global variables. local function make_past_endings_ay_aw(ayaw, third_sg_masc) return { -- singular ayaw .. SK .. TU, ayaw .. SK .. TA, ayaw .. SK .. "تِ", third_sg_masc, A .. "تْ", --dual ayaw .. SK .. "تُمَا", ayaw .. AA, A .. "تَا", -- plural ayaw .. SK .. "نَا", ayaw .. SK .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this ayaw .. SK .. "تُن" .. SH .. A, AW .. SK .. ALIF, ayaw .. SK .. "نَ" } end -- past final-weak -aytu endings local past_endings_ay = make_past_endings_ay_aw(AY, AAMAQ) -- past final-weak -awtu endings local past_endings_aw = make_past_endings_ay_aw(AW, AA) -- used for alternative endings for form-X geminate verbs like اِسْتَمَرَّ local past_endings_ay_12_person_only = { -- singular AY .. SK .. TU, AY .. SK .. TA, AY .. SK .. "تِ", {}, {}, --dual AY .. SK .. "تُمَا", {}, {}, -- plural AY .. SK .. "نَا", AY .. SK .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this AY .. SK .. "تُن" .. SH .. A, {}, {}, } -- Make endings for final-weak past in -ītu or -ūtu. IIUU is ī or ū as appropriate. Note that AA and UU are global -- variables. local function make_past_endings_ii_uu(iiuu) return { -- singular iiuu .. TU, iiuu .. TA, iiuu .. "تِ", iiuu .. A, iiuu .. A .. "تْ", --dual iiuu .. "تُمَا", iiuu .. AA, iiuu .. A .. "تَا", -- plural iiuu .. "نَا", iiuu .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this iiuu .. "تُن" .. SH .. A, UU .. ALIF, iiuu .. "نَ" } end -- past final-weak -ītu endings local past_endings_ii = make_past_endings_ii_uu(II) -- past final-weak -ūtu endings local past_endings_uu = make_past_endings_ii_uu(UU) ------------------------------------------------------------------------------- -- Sets of non-past prefixes and endings -- ------------------------------------------------------------------------------- local nonpast_prefix_consonants = { -- singular HAMZA, T, T, Y, T, -- dual T, Y, T, -- plural N, T, T, Y, Y } -- There are only five distinct endings in all non-past verbs. Make any set of non-past endings given these five -- distinct endings. local function make_nonpast_endings(null, fem, dual, pl, fempl) return { -- singular null, null, fem, null, null, -- dual dual, dual, dual, -- plural null, pl, fempl, pl, fempl } end -- endings for non-past indicative local ind_endings = make_nonpast_endings( U, II .. NA, AANI, UU .. NA, SK .. NA ) -- Make the endings for non-past subjunctive/jussive, given the vowel diacritic used in "null" endings -- (1s/2ms/3ms/3fs/1p). local function make_sub_juss_endings(dia_null) return make_nonpast_endings( dia_null, II, AA, UU .. ALIF, SK .. NA ) end -- endings for non-past subjunctive local sub_endings = make_sub_juss_endings(A) -- endings for non-past jussive local juss_endings = make_sub_juss_endings(SK) -- endings for alternative geminate non-past jussive in -a; same as subjunctive local juss_endings_alt_a = sub_endings -- endings for alternative geminate non-past jussive in -i local juss_endings_alt_i = make_sub_juss_endings(I) -- Endings for final-weak non-past indicative in -ā. Note that AY, AW and AAMAQ are global variables. local ind_endings_aa = make_nonpast_endings( AAMAQ, AYSK .. NA, AY .. AANI, AWSK .. NA, AYSK .. NA ) -- Make endings for final-weak non-past indicative in -ī or -ū; IIUU is ī or ū as appropriate. Note that II and UU -- are global variables. local function make_ind_endings_ii_uu(iiuu) return make_nonpast_endings( iiuu, II .. NA, iiuu .. AANI, UU .. NA, iiuu .. NA ) end -- endings for final-weak non-past indicative in -ī local ind_endings_ii = make_ind_endings_ii_uu(II) -- endings for final-weak non-past indicative in -ū local ind_endings_uu = make_ind_endings_ii_uu(UU) -- Endings for final-weak non-past subjunctive in -ā. Note that AY, AW, ALIF, AAMAQ are global variables. local sub_endings_aa = make_nonpast_endings( AAMAQ, AYSK, AY .. AA, AWSK .. ALIF, AYSK .. NA ) -- Make endings for final-weak non-past subjunctive in -ī or -ū. IIUU is ī or ū as appropriate. Note that AA, II, -- UU, ALIF are global variables. local function make_sub_endings_ii_uu(iiuu) return make_nonpast_endings( iiuu .. A, II, iiuu .. AA, UU .. ALIF, iiuu .. NA ) end -- endings for final-weak non-past subjunctive in -ī local sub_endings_ii = make_sub_endings_ii_uu(II) -- endings for final-weak non-past subjunctive in -ū local sub_endings_uu = make_sub_endings_ii_uu(UU) -- endings for final-weak non-past jussive in -ā local juss_endings_aa = make_nonpast_endings( A, AYSK, AY .. AA, AWSK .. ALIF, AYSK .. NA ) -- Make endings for final-weak non-past jussive in -ī or -ū. IU is short i or u, IIUU is long ī or ū as appropriate. -- Note that AA, II, UU, ALIF are global variables. local function make_juss_endings_ii_uu(iu, iiuu) return make_nonpast_endings( iu, II, iiuu .. AA, UU .. ALIF, iiuu .. NA ) end -- endings for final-weak non-past jussive in -ī local juss_endings_ii = make_juss_endings_ii_uu(I, II) -- endings for final-weak non-past jussive in -ū local juss_endings_uu = make_juss_endings_ii_uu(U, UU) ------------------------------------------------------------------------------- -- Sets of imperative endings -- ------------------------------------------------------------------------------- -- Extract the second person jussive endings to get corresponding imperative endings. local function imperative_endings_from_jussive(endings) return {endings[2], endings[3], endings[6], endings[10], endings[11]} end -- normal imperative endings local imp_endings = imperative_endings_from_jussive(juss_endings) -- alternative geminate imperative endings in -a local imp_endings_alt_a = imperative_endings_from_jussive(juss_endings_alt_a) -- alternative geminate imperative endings in -i local imp_endings_alt_i = imperative_endings_from_jussive(juss_endings_alt_i) -- final-weak imperative endings in -ā local imp_endings_aa = imperative_endings_from_jussive(juss_endings_aa) -- final-weak imperative endings in -ī local imp_endings_ii = imperative_endings_from_jussive(juss_endings_ii) -- final-weak imperative endings in -ū local imp_endings_uu = imperative_endings_from_jussive(juss_endings_uu) ------------------------------------------------------------------------------- -- Basic functions to inflect tenses -- ------------------------------------------------------------------------------- -- Add to `base` the inflections for the tense indicated by `tense` (the prefix in the slot names, e.g. 'past' -- or 'juss_pass'), formed by combining the `prefixes`, `stems` and `endings`. Each of `prefixes`, `stems` and -- `endings` is either a sequence of 5 (for the imperative) or 13 (for other tenses) abbreviated form lists (each of -- which is either a string, a form object, or a list of strings and/or form objects; see -- [[Module:inflection utilities]] for more info). Alternatively, any of `prefixes`, `stems` or `endings` can be a -- single-element list containing an abbreviated form list, with an additional key `all_same` set to true, or (as a -- special case) a single string; in the latter cases, the same value is used for all 5 or 13 slots. If existing -- inflections already exist, they will be added to, not overridden. `pnums` is the list of person/number slot name -- suffixes, which must match up with the elements in `prefixes`, `stems` and `endings` (i.e. 5 for imperative, 13 -- otherwise). local function inflect_tense_1(base, tense, prefixes, stems, endings, pnums) if not prefixes or not stems or not endings then return end local function verify_affixes(affixname, affixes) local function interr(msg) error(("Internal error: For tense '%s', '%s' %s: %s"):format(tense, affixname, msg, dump(affixes))) end if type(affixes) == "string" then -- do nothing elseif type(affixes) ~= "table" then interr("is not a table or string") elseif affixes.all_same then if #affixes ~= 1 then interr(("with all_same = true should have length 1 but has length %s"):format(#affixes)) end else if #affixes ~= #pnums then interr(("should have length %s but has length %s"):format(#pnums, #affixes)) end end end verify_affixes("prefixes", prefixes) verify_affixes("stems", stems) verify_affixes("endings", endings) local function get_affix(affixes, i) if type(affixes) == "string" then return affixes elseif affixes.all_same then return affixes[1] else return affixes[i] end end for i, pnum in ipairs(pnums) do local prefix = get_affix(prefixes, i) local stem = get_affix(stems, i) local ending = get_affix(endings, i) local slot = tense .. "_" .. pnum add3(base, slot, prefix, stem, ending) end end -- Add to `base` the inflections for the tense indicated by `tense` (the prefix in the slot names, e.g. 'past' -- or 'juss_pass'), formed by combining the `prefixes`, `stems` and `endings`. This is a simple wrapper around -- inflect_tense_1() that applies to all tenses other than the imperative; see inflect_tense_1() for more -- information about the parameters. local function inflect_tense(base, tense, prefixes, stems, endings) inflect_tense_1(base, tense, prefixes, stems, endings, all_person_number_list) end -- Like inflect_tense() but for the imperative, which has only five parts instead of 13 and no prefixes. local function inflect_tense_imp(base, stems, endings) inflect_tense_1(base, "imp", "", stems, endings, imp_person_number_list) end ------------------------------------------------------------------------------- -- Functions to inflect the past tense -- ------------------------------------------------------------------------------- -- Generate past verbs using specified vowel and consonant stems; works for sound, assimilated, hollow, and geminate -- verbs, active and passive. local function past_2stem_conj(base, tense, v_stem, c_stem, footnote_12) local passive = tense:find("_pass") and "_pass" or "" -- Override stems with user-specified stems if available. v_stem = override_stem_if_needed(base, "past" .. passive .. "_v", v_stem) local c_stem_12 = c_stem if footnote_12 then c_stem_12 = iut.combine_form_and_footnotes(c_stem_12, footnote_12) end c_stem_12 = override_stem_if_needed(base, "past" .. passive .. "_c", c_stem_12) local c_stem_3 = override_stem_if_needed(base, "past" .. passive .. "_c", c_stem) inflect_tense(base, tense, "", { -- singular c_stem_12, c_stem_12, c_stem_12, v_stem, v_stem, --dual c_stem_12, v_stem, v_stem, -- plural c_stem_12, c_stem_12, c_stem_12, v_stem, c_stem_3 }, past_endings) end -- Generate past verbs using single specified stem; works for sound and assimilated verbs, active and passive. local function past_1stem_conj(base, tense, stem) past_2stem_conj(base, tense, stem, stem) end ------------------------------------------------------------------------------- -- Functions to inflect non-past tenses -- ------------------------------------------------------------------------------- -- Generate non-past conjugation, with two stems, for vowel-initial and consonant-initial endings, respectively. -- Useful for active and passive; for all forms; for all weaknesses (sound, assimilated, hollow, final-weak and -- geminate) and for all types of non-past (indicative, subjunctive, jussive) except for the imperative. (There is a -- separate wrapper function below for geminate jussives because they have three alternants.) Both stems may be the -- same, e.g. for sound verbs. -- `prefix_vowel` will be either "a" or "u". `endings` should be an array of 13 items. If `endings` is nil or -- omitted, infer the endings from the tense. If `jussive` is true, or `endings` is nil and `tense` indicatives -- jussive, use the jussive pattern of vowel/consonant stems (different from the normal ones). local function nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, endings, jussive) local passive = tense:find("_pass") and "_pass" or "" -- Override stems with user-specified stems if available. v_stem = override_stem_if_needed(base, "nonpast" .. passive .. "_v", v_stem and q(dia[prefix_vowel], v_stem) or nil) c_stem = override_stem_if_needed(base, "nonpast" .. passive .. "_c", c_stem and q(dia[prefix_vowel], c_stem) or nil) if not endings then if tense:find("^ind") then endings = ind_endings elseif tense:find("^sub") then endings = sub_endings elseif tense:find("^juss") then jussive = true endings = juss_endings else error("Internal error: Unrecognized tense '" .. tense .."'") end end if not jussive then inflect_tense(base, tense, nonpast_prefix_consonants, { -- singular v_stem, v_stem, v_stem, v_stem, v_stem, --dual v_stem, v_stem, v_stem, -- plural v_stem, v_stem, c_stem, v_stem, c_stem }, endings) else inflect_tense(base, tense, nonpast_prefix_consonants, { -- singular -- 'adlul, tadlul, tadullī, yadlul, tadlul c_stem, c_stem, v_stem, c_stem, c_stem, --dual -- tadullā, yadullā, tadullā v_stem, v_stem, v_stem, -- plural -- nadlul, tadullū, tadlulna, yadullū, yadlulna c_stem, v_stem, c_stem, v_stem, c_stem }, endings) end end -- Generate non-past conjugation with one stem (no distinct stems for vowel-initial and consonant-initial endings). -- See nonpast_2stem_conj(). local function nonpast_1stem_conj(base, tense, prefix_vowel, stem, endings, jussive) nonpast_2stem_conj(base, tense, prefix_vowel, stem, stem, endings, jussive) end -- Generate active/passive jussive geminative. There are three alternants, two with terminations -a and -i and one -- in a null termination with a distinct pattern of vowel/consonant stem usage. See nonpast_2stem_conj() for a -- description of the arguments. local function jussive_gem_conj(base, tense, prefix_vowel, v_stem, c_stem) -- alternative in -a nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings_alt_a) -- alternative in -i nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings_alt_i) -- alternative in -null; requires different combination of v_stem and -- c_stem since the null endings require the c_stem (e.g. "tadlul" here) -- whereas the corresponding endings above in -a or -i require the v_stem -- (e.g. "tadulla, tadulli" above) nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings, "jussive") end ------------------------------------------------------------------------------- -- Functions to inflect the imperative -- ------------------------------------------------------------------------------- -- Generate imperative conjugation, with two stems, for vowel-initial and consonant-initial endings, respectively. -- Useful for all forms, and for all weaknesses other than final-weak. Note that the two stems may be the same -- (specifically for sound and assimilated verbs). If `endings` is nil or omitted, use `imp_endings`. If `alt_gem` -- is specified, use the pattern of vowel and consonant stems appropriate for the alternative geminate imperatives -- that use a null ending of -a or -i instead of an empty ending. local function make_2stem_imperative(base, v_stem, c_stem, endings, alt_gem) endings = endings or imp_endings -- Override stems with user-specified stems if available. v_stem = override_stem_if_needed(base, "imp_v", v_stem) c_stem = override_stem_if_needed(base, "imp_c", c_stem) if alt_gem then inflect_tense_imp(base, {v_stem, v_stem, v_stem, v_stem, c_stem}, endings) else inflect_tense_imp(base, {c_stem, v_stem, v_stem, v_stem, c_stem}, endings) end end -- Generate imperative parts for sound or assimilated verbs. local function make_1stem_imperative(base, stem) make_2stem_imperative(base, stem, stem) end -- Generate imperative parts for geminate verbs form I (also IV, VII, VIII, X). local function make_gem_imperative(base, v_stem, c_stem) make_2stem_imperative(base, v_stem, c_stem, imp_endings_alt_a, "alt gem") make_2stem_imperative(base, v_stem, c_stem, imp_endings_alt_i, "alt gem") make_2stem_imperative(base, v_stem, c_stem) end ------------------------------------------------------------------------------- -- Functions to inflect entire verbs -- ------------------------------------------------------------------------------- -- Generate finite parts of a sound verb (also works for assimilated verbs) from five stems (past and non-past, -- active and passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u"). local function make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel) past_1stem_conj(base, "past", past_stem) past_1stem_conj(base, "past_pass", past_pass_stem) nonpast_1stem_conj(base, "ind", prefix_vowel, nonpast_stem) nonpast_1stem_conj(base, "sub", prefix_vowel, nonpast_stem) nonpast_1stem_conj(base, "juss", prefix_vowel, nonpast_stem) nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem) nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem) nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem) make_1stem_imperative(base, imp_stem) end local function past_final_weak_endings_from_vowel(vowel) if vowel == "ay" then return past_endings_ay elseif vowel == "aw" then return past_endings_aw elseif vowel == "ī" then return past_endings_ii elseif vowel == "ū" then return past_endings_uu elseif not vowel then return nil else error(("Internal error: Unrecognized past final-weak vowel spec '%s'"):format(vowel)) end end local function nonpast_final_weak_endings_from_vowel(vowel) if vowel == "ā" then return ind_endings_aa, sub_endings_aa, juss_endings_aa, imp_endings_aa elseif vowel == "ī" then return ind_endings_ii, sub_endings_ii, juss_endings_ii, imp_endings_ii elseif vowel == "ū" then return ind_endings_uu, sub_endings_uu, juss_endings_uu, imp_endings_uu elseif not vowel then return nil else error(("Internal error: Unrecognized non-past final-weak vowel spec '%s'"):format(vowel)) end end -- Generate finite parts of a final-weak verb from five stems (past and non-past, active and passive, plus -- imperative), the past active ending vowel (ay, aw, ī or ū), the non-past active ending vowel (ā, ī or ū) and the -- prefix vowel in the active non-past (a or u). local function make_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, past_ending_vowel, nonpast_ending_vowel, prefix_vowel) past_stem = override_stem_if_needed(base, "past", past_stem) past_pass_stem = override_stem_if_needed(base, "past_pass", past_pass_stem) -- Don't call override_stem_if_needed() here for non-past stems; it's called in nonpast_2stem_conj(). imp_stem = override_stem_if_needed(base, "imp", imp_stem) -- + not supported for ending vowel overrides past_ending_vowel = base.stem_overrides.past_final_weak_vowel or past_ending_vowel local past_pass_ending_vowel = base.stem_overrides.past_pass_final_weak_vowel or "ī" nonpast_ending_vowel = base.stem_overrides.nonpast_final_weak_vowel or nonpast_ending_vowel local nonpast_pass_ending_vowel = base.stem_overrides.nonpast_pass_final_weak_vowel or "ā" local past_endings = past_final_weak_endings_from_vowel(past_ending_vowel) local past_pass_endings = past_final_weak_endings_from_vowel(past_pass_ending_vowel) local ind_endings, sub_endings, juss_endings, imp_endings = nonpast_final_weak_endings_from_vowel(nonpast_ending_vowel) local ind_pass_endings, sub_pass_endings, juss_pass_endings = nonpast_final_weak_endings_from_vowel(nonpast_pass_ending_vowel) inflect_tense(base, "past", "", {past_stem, all_same = 1}, past_endings) inflect_tense(base, "past_pass", "", {past_pass_stem, all_same = 1}, past_pass_endings) nonpast_1stem_conj(base, "ind", prefix_vowel, nonpast_stem, ind_endings) nonpast_1stem_conj(base, "sub", prefix_vowel, nonpast_stem, sub_endings) nonpast_1stem_conj(base, "juss", prefix_vowel, nonpast_stem, juss_endings) nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem, ind_pass_endings) nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem, sub_pass_endings) nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem, juss_pass_endings) inflect_tense_imp(base, {imp_stem, all_same = 1}, imp_endings) end -- Generate finite parts of an augmented (form II+) final-weak verb from five stems (past and non-past, active and -- passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u") and a flag indicating if it -- behaves like a form V/VI verb in taking non-past endings in -ā instead of -ī. local function make_augmented_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel, form56) make_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, "ay", form56 and "ā" or "ī", prefix_vowel) end -- Generate finite parts of an augmented (form II+) sound or final-weak verb, given: -- * `base` (conjugation data structure); -- * `vowel_spec` (radicals, weakness); -- * `past_stem_base` (active past stem minus last syllable (= -al or -ā)); -- * `nonpast_stem_base` (non-past stem minus last syllable (= -al/-il or -ā/-ī); -- * `past_pass_stem_base` (passive past stem minus last syllable (= -il or -ī)); -- * `vn` (verbal noun). local function make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) insert_form_or_forms(base, "vn", vn) local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3 local final_weak = is_final_weak(base, vowel_spec) local prefix_vowel = prefix_vowel_from_vform(base.verb_form) local form56 = vform_nonpast_a_vowel(base.verb_form) local a_base_suffix = final_weak and "" or q(A, lastrad) local i_base_suffix = final_weak and "" or q(I, lastrad) -- past and non-past stems, active and passive local past_stem = q(past_stem_base, a_base_suffix) -- In forms 5 and 6, non-past has /a/ as last stem vowel in the non-past -- in both active and passive, but /i/ in the active participle and /a/ -- in the passive participle. Elsewhere, consistent /i/ in active non-past -- and participle, consistent /a/ in passive non-past and participle. -- Hence, forms 5 and 6 differ only in the non-past active (but not -- active participle), so we have to split the finite non-past stem and -- active participle stem. local nonpast_stem = q(nonpast_stem_base, form56 and a_base_suffix or i_base_suffix) local ap_stem = q(nonpast_stem_base, i_base_suffix) local past_pass_stem = q(past_pass_stem_base, i_base_suffix) local nonpast_pass_stem = q(nonpast_stem_base, a_base_suffix) -- imperative stem local imp_stem = q(past_stem_base, form56 and a_base_suffix or i_base_suffix) -- make parts if final_weak then make_augmented_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel, form56) else make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel) end -- active and passive participle if final_weak then insert_form_or_forms(base, "ap", q(MU, ap_stem, IN)) insert_form_or_forms(base, "pp", q(MU, nonpast_pass_stem, AN, AMAQ)) else insert_form_or_forms(base, "ap", q(MU, ap_stem)) insert_form_or_forms(base, "pp", q(MU, nonpast_pass_stem)) end end -- Generate finite parts of a hollow or geminate verb from ten stems (vowel and consonant stems for each of past and -- non-past, active and passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u"), plus a -- flag indicating if we are a geminate verb. local function make_hollow_geminate_verb(base, geminate, past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel, altgem_note) past_2stem_conj(base, "past", past_v_stem, past_c_stem, altgem_note) past_2stem_conj(base, "past_pass", past_pass_v_stem, past_pass_c_stem) nonpast_2stem_conj(base, "ind", prefix_vowel, nonpast_v_stem, nonpast_c_stem) nonpast_2stem_conj(base, "sub", prefix_vowel, nonpast_v_stem, nonpast_c_stem) nonpast_2stem_conj(base, "ind_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) nonpast_2stem_conj(base, "sub_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) if geminate then jussive_gem_conj(base, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem) jussive_gem_conj(base, "juss_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) make_gem_imperative(base, imp_v_stem, imp_c_stem) else nonpast_2stem_conj(base, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem) nonpast_2stem_conj(base, "juss_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) make_2stem_imperative(base, imp_v_stem, imp_c_stem) end end -- Generate finite parts of an augmented (form II+) hollow verb, given: -- * `base` (conjugation data structure); -- * `vowel_spec` (radicals, weakness); -- * `past_stem_base` (invariable part of active past stem); -- * `nonpast_stem_base` (invariable part of nonpast stem); -- * `past_pass_stem_base` (invariable part of passive past stem); -- * `vn` (verbal noun). local function make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) insert_form_or_forms(base, "vn", vn) local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3 local form410 = base.verb_form == "IV" or base.verb_form == "X" local prefix_vowel = prefix_vowel_from_vform(base.verb_form) local a_base_suffix_v, a_base_suffix_c local i_base_suffix_v, i_base_suffix_c a_base_suffix_v = q(AA, lastrad) -- 'af-āl-a, inf-āl-a a_base_suffix_c = q(A, lastrad) -- 'af-al-tu, inf-al-tu i_base_suffix_v = q(II, lastrad) -- 'uf-īl-a, unf-īl-a i_base_suffix_c = q(I, lastrad) -- 'uf-il-tu, unf-il-tu -- past and non-past stems, active and passive, for vowel-initial and -- consonant-initial endings local past_v_stem = q(past_stem_base, a_base_suffix_v) local past_c_stem = q(past_stem_base, a_base_suffix_c) -- yu-f-īl-u, ya-staf-īl-u but yanf-āl-u, yaft-āl-u local nonpast_v_stem = q(nonpast_stem_base, form410 and i_base_suffix_v or a_base_suffix_v) local nonpast_c_stem = q(nonpast_stem_base, form410 and i_base_suffix_c or a_base_suffix_c) local past_pass_v_stem = q(past_pass_stem_base, i_base_suffix_v) local past_pass_c_stem = q(past_pass_stem_base, i_base_suffix_c) local nonpast_pass_v_stem = q(nonpast_stem_base, a_base_suffix_v) local nonpast_pass_c_stem = q(nonpast_stem_base, a_base_suffix_c) -- imperative stem local imp_v_stem = q(past_stem_base, form410 and i_base_suffix_v or a_base_suffix_v) local imp_c_stem = q(past_stem_base, form410 and i_base_suffix_c or a_base_suffix_c) -- make parts make_hollow_geminate_verb(base, false, past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel) -- active participle insert_form_or_forms(base, "ap", q(MU, nonpast_v_stem)) -- passive participle insert_form_or_forms(base, "pp", q(MU, nonpast_pass_v_stem)) end -- Generate finite parts of an augmented (form II+) geminate verb, given: -- * `base` (conjugation data structure); -- * `vowel_spec` (radicals, weakness); -- * `past_stem_base` (invariable part of active past stem; this and the stem bases below will end with a consonant -- for forms IV, X, IVq, and a short vowel for the others); -- * `nonpast_stem_base` (invariable part of nonpast stem); -- * `past_pass_stem_base` (invariable part of passive past stem); -- * `vn` (verbal noun); -- * `altgem_note` (footnote to add to active past 1/2-person forms, when alternative forms are supplied [form X]). local function make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn, altgem_note) insert_form_or_forms(base, "vn", vn) local vform = base.verb_form local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3 local prefix_vowel = prefix_vowel_from_vform(vform) local a_base_suffix_v, a_base_suffix_c local i_base_suffix_v, i_base_suffix_c if vform == "IV" or vform == "X" or vform == "IVq" then a_base_suffix_v = q(A, lastrad, SH) -- 'af-all a_base_suffix_c = q(SK, lastrad, A, lastrad) -- 'af-lal i_base_suffix_v = q(I, lastrad, SH) -- yuf-ill i_base_suffix_c = q(SK, lastrad, I, lastrad) -- yuf-lil else a_base_suffix_v = q(lastrad, SH) -- fā-ll, infa-ll a_base_suffix_c = q(lastrad, A, lastrad) -- fā-lal, infa-lal i_base_suffix_v = q(lastrad, SH) -- yufā-ll, yanfa-ll i_base_suffix_c = q(lastrad, I, lastrad) -- yufā-lil, yanfa-lil end -- past and non-past stems, active and passive, for vowel-initial and -- consonant-initial endings local past_v_stem = q(past_stem_base, a_base_suffix_v) local past_c_stem = q(past_stem_base, a_base_suffix_c) local nonpast_v_stem = q(nonpast_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_v or i_base_suffix_v) local nonpast_c_stem = q(nonpast_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_c or i_base_suffix_c) -- NOTE: Formerly had a comment that "vform III and VI passive past do not have contracted parts, only -- uncontracted parts, which are added separately by those functions". This is based on Mace -- "Arabic Verbs and Essential Grammar" (1999) entry 63 (continued), which shows passive ḥūjija but no ḥūjja; -- but that is apparently a mistake, as (1) verb tables in other books do show contracted passive parts for -- these forms; (2) there is no mention of such an exception on p. 99, which explains how geminate ("doubled") -- verbs work (on the contrary, it says "The contracted and uncontracted pairs (see above) are found all -- over Forms III and VI of the doubled verbs"). local past_pass_v_stem = q(past_pass_stem_base, i_base_suffix_v) local past_pass_c_stem = q(past_pass_stem_base, i_base_suffix_c) local nonpast_pass_v_stem = q(nonpast_stem_base, a_base_suffix_v) local nonpast_pass_c_stem = q(nonpast_stem_base, a_base_suffix_c) -- imperative stem local imp_v_stem = q(past_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_v or i_base_suffix_v) local imp_c_stem = q(past_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_c or i_base_suffix_c) -- make parts make_hollow_geminate_verb(base, "geminate", past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel, altgem_note) -- active participle insert_form_or_forms(base, "ap", q(MU, nonpast_v_stem)) -- passive participle insert_form_or_forms(base, "pp", q(MU, nonpast_pass_v_stem)) end ------------------------------------------------------------------------------- -- Conjugation functions for specific conjugation types -- ------------------------------------------------------------------------------- local function form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1) local imp_vowel = map_vowel(nonpast_vowel, function(vow) if vow == A or vow == I then return I elseif vow == U then return U elseif not skip_slot(base, "imp_2ms") then error(("Internal error: Non-past vowel %s isn't a, i, or u, should have been caught earlier"):format( dump(nonpast_vowel))) else -- Passive-only; imperative won't ever be displayed so it doesn't matter. return I end end) -- Mace ("Arabic Verbs and Essentials of Grammar" p. 63: [https://archive.org/details/arabicverbsessen00john/page/62/mode/2up]) -- claims that initial hamza is assimilated/elided into a long vowel in the form-I imperative, but apparently -- this isn't corrrect. local vowel_on_alif = map_vowel(imp_vowel, function(vow) return ALIF .. vow end) return q(vowel_on_alif, rad1, SK) end -- Implement form-I sound or assimilated verb. ASSIMILATED is true for assimilated verbs. local function make_form_i_sound_assimilated_verb(base, vowel_spec, assimilated) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied -- past and non-past stems, active and passive local past_stem = q(rad1, A, rad2, past_vowel, rad3) local nonpast_stem = assimilated and q(rad2, nonpast_vowel, rad3) or q(rad1, SK, rad2, nonpast_vowel, rad3) local past_pass_stem = q(rad1, U, rad2, I, rad3) local nonpast_pass_stem = q(rad1, SK, rad2, A, rad3) -- imperative stem -- check for irregular verb with reduced imperative (أَخَذَ or أَكَلَ or أَمَرَ) local reducedimp = reduced_imperative_verb(rad1, rad2, rad3) if reducedimp then base.irregular = true end local imp_stem_suffix = q(rad2, nonpast_vowel, rad3) local long_imp_stem_base = form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1) local short_imp_stem_base = "" local imp_stem = q((assimilated or reducedimp) and "" or long_imp_stem_base, imp_stem_suffix) -- make parts make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, "a") if reducedimp == "shortlong" then make_1stem_imperative(base, iut.combine_form_and_footnotes(q(long_imp_stem_base, imp_stem_suffix), mw.getCurrentFrame():preprocess("[used especially with a clitic such as {{m|ar|فَ}} or {{m|ar|وَ}}]"))) end -- Check for irregular verb سَأَلَ with alternative jussive and imperative. Calling this after make_sound_verb() -- adds additional entries to the paradigm parts. if saal_radicals(rad1, rad2, rad3) then base.irregular = true nonpast_1stem_conj(base, "juss", "a", "سَل") nonpast_1stem_conj(base, "juss_pass", "u", "سَل") make_1stem_imperative(base, "سَل") end -- Active participle. insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, I, rad3)) -- Insert alternative active participle (stative type I) فَعِيل. Since not all verbs have this, we require that -- verbs that do have it specify it explicitly; a shortcut ++ is provided to make this easier (e.g. <ap:++> to -- indicate that the alternative form should be used for the active participle, <ap:+,++> to indicate that both -- forms can be used, and <ap:-> to indicate that there is no active participle). The same form is used for -- secondary default passive participle. insert_ap2_pp2(base, q(rad1, A, rad2, II, rad3)) -- Active participle, stative type II فَعِل (+++). insert_form_or_forms(base, "ap3", q(rad1, A, rad2, I, rad3)) -- Active participle, color/defect أَفْعَل (+cd). insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, A, rad3)) -- Active participle, -ān فَعْلَان (+an). insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, UU, rad3)) end conjugations["I-sound"] = function(base, vowel_spec) make_form_i_sound_assimilated_verb(base, vowel_spec, false) end conjugations["none-sound"] = function(base, vowel_spec) -- All default stems are nil. make_sound_verb(base) end conjugations["none-hollow"] = function(base, vowel_spec) -- All default stems are nil. make_hollow_geminate_verb(base, false) end conjugations["none-geminate"] = function(base, vowel_spec) -- All default stems are nil. make_hollow_geminate_verb(base, "geminate") end conjugations["none-final-weak"] = function(base, vowel_spec) -- All default stems are nil. make_final_weak_verb(base) end conjugations["I-assimilated"] = function(base, vowel_spec) make_form_i_sound_assimilated_verb(base, vowel_spec, "assimilated") end local function make_form_i_hayy_verb(base, vowel_spec) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied base.irregular = true -- past and non-past stems, active and passive, and imperative stem local past_c_stem = "حَيِي" local past_v_stem_long = past_c_stem local past_v_stem_short = "حَيّ" local past_pass_c_stem = "حُيِي" local past_pass_v_stem_long = past_pass_c_stem local past_pass_v_stem_short = "حُيّ" local nonpast_stem = "حْي" local nonpast_pass_stem = nonpast_stem local imp_stem = _I .. nonpast_stem -- make parts past_2stem_conj(base, "past", {}, past_c_stem) past_2stem_conj(base, "past_pass", {}, past_pass_c_stem) local variant = vowel_spec.variant or "both" if variant == "short" or variant == "both" then past_2stem_conj(base, "past", past_v_stem_short, {}) past_2stem_conj(base, "past_pass", past_pass_v_stem_short, {}) end function inflect_long_variant(tense, long_stem, short_stem) inflect_tense_1(base, tense, "", {long_stem, long_stem, long_stem, long_stem, short_stem}, {past_endings[4], past_endings[5], past_endings[7], past_endings[8], past_endings[12]}, {"3ms", "3fs", "3md", "3fd", "3mp"}) end if variant == "long" or variant == "both" then inflect_long_variant("past", past_v_stem_long, past_v_stem_short) inflect_long_variant("past_pass", past_pass_v_stem_long, past_pass_v_stem_short) end nonpast_1stem_conj(base, "ind", "a", nonpast_stem, ind_endings_aa) nonpast_1stem_conj(base, "sub", "a", nonpast_stem, sub_endings_aa) nonpast_1stem_conj(base, "juss", "a", nonpast_stem, juss_endings_aa) nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem, ind_endings_aa) nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem, sub_endings_aa) nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem, juss_endings_aa) inflect_tense_imp(base, {imp_stem, all_same = 1}, imp_endings_aa) -- active and passive participles apparently do not exist for this verb end -- Implement form-I final-weak assimilated+final-weak verb. ASSIMILATED is true for assimilated verbs. local function make_form_i_final_weak_verb(base, vowel_spec, assimilated) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- حَيَّ or حَيِيَ is weird enough that we handle it as a separate function. if hayy_radicals(rad1, rad2, rad3) then make_form_i_hayy_verb(base, vowel_spec) return end -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied. -- Past and non-past stems, active and passive, and imperative stem. local past_stem = q(rad1, A, rad2) local past_pass_stem = q(rad1, U, rad2) local nonpast_stem, nonpast_pass_stem, imp_stem if raa_radicals(rad1, rad2, rad3) then base.irregular = true nonpast_stem = rad1 nonpast_pass_stem = rad1 imp_stem = rad1 else nonpast_pass_stem = q(rad1, SK, rad2) if assimilated then nonpast_stem = rad2 imp_stem = rad2 else nonpast_stem = nonpast_pass_stem imp_stem = q(form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1), rad2) end end -- Make parts. local past_ending_vowel = req(rad3, Y) and req(past_vowel, A) and "ay" or req(rad3, W) and req(past_vowel, A) and "aw" or req(past_vowel, I) and "ī" or "ū" -- Try to preserve footnotes attached to the third radical and/or past and/or non-past vowels. local past_footnotes = iut.combine_footnotes(rget_footnotes(rad3), rget_footnotes(past_vowel)) local nonpast_ending_vowel = req(nonpast_vowel, A) and "ā" or req(nonpast_vowel, I) and "ī" or "ū" local nonpast_footnotes = iut.combine_footnotes(rget_footnotes(rad3), rget_footnotes(nonpast_vowel)) make_final_weak_verb(base, iut.combine_form_and_footnotes(past_stem, past_footnotes), iut.combine_form_and_footnotes(past_pass_stem, past_footnotes), iut.combine_form_and_footnotes(nonpast_stem, nonpast_footnotes), iut.combine_form_and_footnotes(nonpast_pass_stem, nonpast_footnotes), iut.combine_form_and_footnotes(imp_stem, nonpast_footnotes), past_ending_vowel, nonpast_ending_vowel, "a") -- Active participle. insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, IN)) -- Active participle, stative type I فَعِيّ (++). FIXME: Is this correct when rad3 is W? insert_ap2_pp2(base, q(rad1, A, rad2, II, SH)) -- Active participle, stative type II فَعٍ (+++). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "ap3", q(rad1, A, rad2, IN)) -- Active participle, color/defect أَفْعَى (+cd). insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, AAMAQ)) -- Active participle, -ān فَعْيَان or فَعْوَان (+an). -- FIXME: Any examples of this for both rad3 = W and y to verify it's correct? insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, req(rad3, Y) and II or UU, SH)) end conjugations["I-final-weak"] = function(base, vowel_spec) make_form_i_final_weak_verb(base, vowel_spec, false) end conjugations["I-assimilated+final-weak"] = function(base, vowel_spec) make_form_i_final_weak_verb(base, vowel_spec, "assimilated") end conjugations["I-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- In some sense, hollow vowels i~i and u~u are more "correct" than a~i and a~u, but the latter follow the -- pattern of other form-I verbs, so we map i~i to a~i and u~u to a~u in infer_radicals(). Now however we have -- to undo this to get the actual past vowel based on the non-past vowel. if req(past_vowel, A) then past_vowel = map_vowel(past_vowel, function(vow) return req(nonpast_vowel, A) and I or rget(nonpast_vowel) end) end local lengthened_nonpast = map_vowel(nonpast_vowel, function(vow) return vow == U and UU or vow == I and II or AA end) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied. -- active past stems - vowel (v) and consonant (c) local past_v_stem = q(rad1, AA, rad3) local past_c_stem = q(rad1, past_vowel, rad3) -- active non-past stems - vowel (v) and consonant (c) local nonpast_v_stem = q(rad1, lengthened_nonpast, rad3) local nonpast_c_stem = q(rad1, nonpast_vowel, rad3) -- passive past stems - vowel (v) and consonant (c) -- 'ufīla, 'ufiltu local past_pass_v_stem = q(rad1, II, rad3) local past_pass_c_stem = q(rad1, I, rad3) -- passive non-past stems - vowel (v) and consonant (c) -- yufāla/yufalna -- stem is built differently but conjugation is identical to sound verbs local nonpast_pass_v_stem = q(rad1, AA, rad3) local nonpast_pass_c_stem = q(rad1, A, rad3) -- imperative stem local imp_v_stem = nonpast_v_stem local imp_c_stem = nonpast_c_stem -- make parts make_hollow_geminate_verb(base, false, past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, "a") if kaan_radicals(rad1, rad2, rad3) then local endings = make_nonpast_endings(U, {}, {}, {}, {}) inflect_tense(base, "juss", nonpast_prefix_consonants, q(A, rad1), endings) base.irregular = true end -- Active participle. insert_form_or_forms(base, "ap1", req(rad3, HAMZA) and q(rad1, AA, HAMZA, IN) or q(rad1, AA, HAMZA, I, rad3)) -- Active participle, stative type I فَيِّد (++). FIXME: Any examples of this to verify it's correct? insert_ap2_pp2(base, q(rad1, A, Y, SH, I, rad3)) -- Active participle, stative type II فَيِد (+++). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "ap3", q(rad1, A, Y, I, rad3)) -- Active participle, color/defect أَفّيَد or أَفّوَد (+cd). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, A, rad3)) -- Active participle, -ān فَيْدَان or فَوْدَان (+an). Example: جَاعَ "to be hungry", act part جَوْعَان insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, req(rad2, Y) and II or UU, rad3)) end conjugations["I-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied. -- active past stems - vowel (v) and consonant (c) local past_v_stem = q(rad1, A, rad2, SH) local past_c_stem = q(rad1, A, rad2, past_vowel, rad2) -- active non-past stems - vowel (v) and consonant (c) local nonpast_v_stem = q(rad1, nonpast_vowel, rad2, SH) local nonpast_c_stem = q(rad1, SK, rad2, nonpast_vowel, rad2) -- passive past stems - vowel (v) and consonant (c) -- dulla/dulilta local past_pass_v_stem = q(rad1, U, rad2, SH) local past_pass_c_stem = q(rad1, U, rad2, I, rad2) -- passive non-past stems - vowel (v) and consonant (c) --yudallu/yudlalna -- stem is built differently but conjugation is identical to sound verbs local nonpast_pass_v_stem = q(rad1, A, rad2, SH) local nonpast_pass_c_stem = q(rad1, SK, rad2, A, rad2) -- imperative stem local imp_v_stem = q(rad1, nonpast_vowel, rad2, SH) local imp_c_stem = q(form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1), rad2, nonpast_vowel, rad2) -- make parts make_hollow_geminate_verb(base, "geminate", past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, "a") -- Active participle. insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, SH)) -- Active participle, stative type I فَعِيع (++). FIXME: Any examples of this to verify it's correct? insert_ap2_pp2(base, q(rad1, A, rad2, II, rad2)) -- Active participle, stative type II فَعّ (+++). Example: بَرَّ "to be pious", active participle بَرّ insert_form_or_forms(base, "ap3", q(rad1, A, rad2, SH)) -- Active participle, color/defect أَفَعّ (+cd). -- Example: لَصَّ "to be thievish, to steal repeatedly", active participle أَلَصّ. insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, A, rad2, SH)) -- Active participle, -ān فَعَّان (+an). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "apan", q(rad1, A, rad2, SH, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, UU, rad2)) end -- Return the ta- (active, past and non-past) and tu- (passive past) prefixes for a form II/III/V/VI verb. -- Form V and VI verbs normally use ta- and tu-, but reduced (base.reduced) verbs use different prefixes. Form II -- and III verbs have no prefix. local function form_ii_iii_v_vi_ta_tu_prefix(base, rad1) local vform = base.verb_form if vform == "V" or vform == "VI" then if base.reduced then -- To simplify the code, we generate two rad1's with a sukūn between them, which is cleaned up in -- postprocessing. return q(_I, rad1, SK), q(rad1, SK), q(_U, rad1, SK) else return TA, TA, TU end else return "", "", "" end end -- Make form II or V sound or final-weak verb. local function make_form_ii_v_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local vform = base.verb_form local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1) local vn = vform == "V" and q(ta_past_prefix, rad1, A, rad2, SH, final_weak and IN or q(U, rad3)) or q(TA, rad1, SK, rad2, II, final_weak and AH or rad3) -- various stem bases local past_stem_base = q(ta_past_prefix, rad1, A, rad2, SH) local nonpast_stem_base = q(ta_nonpast_prefix, rad1, A, rad2, SH) local past_pass_stem_base = q(tu_past_prefix, rad1, U, rad2, SH) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["II-sound"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end conjugations["II-final-weak"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end local function make_form_iii_alt_vn(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) -- Insert alternative verbal noun فِعَال. Since not all verbs have this, we require that verbs that do have it -- specify it explicitly; a shortcut ++ is provided to make this easier (e.g. <vn:+,++> to indicate that -- both the normal verbal noun مُفَاعَلَة and secondary verbal noun فِعَال are available). insert_form_or_forms(base, "vn2", q(rad1, I, rad2, AA, final_weak and HAMZA or rad3)) end -- Make form III or VI sound or final-weak verb. local function make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local vform = base.verb_form local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1) local vn = vform == "VI" and q(ta_past_prefix, rad1, AA, rad2, final_weak and IN or q(U, rad3)) or q(MU, rad1, AA, rad2, final_weak and AAH or q(A, rad3, AH)) -- various stem bases local past_stem_base = q(ta_past_prefix, rad1, AA, rad2) local nonpast_stem_base = q(ta_nonpast_prefix, rad1, AA, rad2) local past_pass_stem_base = q(tu_past_prefix, rad1, UU, rad2) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) if vform == "III" then make_form_iii_alt_vn(base, vowel_spec) end end conjugations["III-sound"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end conjugations["III-final-weak"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end -- Make form III or VI geminate verb. local function make_form_iii_vi_geminate_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vform = base.verb_form local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1) -- Alternative verbal noun فِعَال will be inserted when we add sound parts below. local vn = vform == "VI" and q(ta_past_prefix, rad1, AA, rad2, SH) or q(MU, rad1, AA, rad2, SH, AH) -- Various stem bases. local past_stem_base = q(ta_past_prefix, rad1, AA) local nonpast_stem_base = q(ta_nonpast_prefix, rad1, AA) local past_pass_stem_base = q(tu_past_prefix, rad1, UU) -- Make parts. local variant = vowel_spec.variant or "short" if variant == "short" or variant == "both" then make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Also add alternative sound (non-compressed) parts. This will lead to some duplicate entries, but they are -- removed during addition. if variant == "long" or variant == "both" then make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) elseif vform == "III" then -- Still need to add the alternative form-III verbal noun. make_form_iii_alt_vn(base, vowel_spec) end end conjugations["III-geminate"] = function(base, vowel_spec) make_form_iii_vi_geminate_verb(base, vowel_spec) end -- Make form IV sound or final-weak verb. local function make_form_iv_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) -- core of stem base, minus stem prefixes local stem_core -- check for irregular verb أَرَى local is_raa = raa_radicals(rad1, rad2, rad3) if is_raa then base.irregular = true stem_core = rad1 else stem_core = q(rad1, SK, rad2) end -- verbal noun local vn = is_raa and q(HAMZA, I, stem_core, AA, HAMZA, AH) or q(HAMZA, I, stem_core, AA, final_weak and HAMZA or rad3) -- various stem bases local past_stem_base = q(HAMZA, A, stem_core) local nonpast_stem_base = stem_core local past_pass_stem_base = q(HAMZA, U, stem_core) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IV-sound"] = function(base, vowel_spec) make_form_iv_sound_final_weak_verb(base, vowel_spec) end conjugations["IV-final-weak"] = function(base, vowel_spec) make_form_iv_sound_final_weak_verb(base, vowel_spec) end conjugations["IV-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) -- verbal noun local vn = q(HAMZA, I, rad1, AA, rad3, AH) -- various stem bases local past_stem_base = q(HAMZA, A, rad1) local nonpast_stem_base = rad1 local past_pass_stem_base = q(HAMZA, U, rad1) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IV-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(HAMZA, I, rad1, SK, rad2, AA, rad2) -- various stem bases local past_stem_base = q(HAMZA, A, rad1) local nonpast_stem_base = rad1 local past_pass_stem_base = q(HAMZA, U, rad1) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["V-sound"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end conjugations["V-final-weak"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end conjugations["VI-sound"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end conjugations["VI-final-weak"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end conjugations["VI-geminate"] = function(base, vowel_spec) make_form_iii_vi_geminate_verb(base, vowel_spec) end -- Make a verbal noun of the general form that applies to forms VII and above. RAD12 is the first consonant cluster -- (after initial اِ) and RAD34 is the second consonant cluster. RAD5 is the final consonant. local function high_form_verbal_noun(rad12, rad34, rad5) return q(_I, rad12, I, rad34, AA, rad5) end -- Populate a sound or final-weak verb for any of the various high-numbered augmented forms (form VII and up) that -- have up to 5 consonants in two clusters in the stem and the same pattern of vowels between. Some of these -- consonants in certain verb parts are w's, which leads to apparent anomalies in certain stems of these parts, but -- these anomalies are handled automatically in postprocessing, where we resolve sequences of iwC -> īC, uwC -> ūC, -- w + sukūn + w -> w + shadda. -- RAD12 is the first consonant cluster (after initial اِ) and RAD34 is the second consonant cluster. RAD5 is the -- final consonant. local function make_high_form_sound_final_weak_verb(base, vowel_spec, rad12, rad34, rad5) local final_weak = is_final_weak(base, vowel_spec) local vn = high_form_verbal_noun(rad12, rad34, final_weak and HAMZA or rad5) -- various stem bases local nonpast_stem_base = q(rad12, A, rad34) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, rad12, U, rad34) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end local function form_vii_nrad1(base, rad1) if base.reduced then if not req(rad1, M) then error(("Internal error: Form VII first radical %s is not م but .reduced specified; should have been caught earlier"): format(rget(rad1))) end return M .. SH else return q("نْ", rad1) end end -- Make form VII sound or final-weak verb. local function make_form_vii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high_form_sound_final_weak_verb(base, vowel_spec, form_vii_nrad1(base, rad1), rad2, rad3) end conjugations["VII-sound"] = function(base, vowel_spec) make_form_vii_sound_final_weak_verb(base, vowel_spec) end conjugations["VII-final-weak"] = function(base, vowel_spec) make_form_vii_sound_final_weak_verb(base, vowel_spec) end conjugations["VII-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local nrad1 = form_vii_nrad1(base, rad1) local vn = high_form_verbal_noun(nrad1, Y, rad3) -- various stem bases local nonpast_stem_base = nrad1 local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, nrad1) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["VII-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local nrad1 = form_vii_nrad1(base, rad1) local vn = high_form_verbal_noun(nrad1, rad2, rad2) -- various stem bases local nonpast_stem_base = q(nrad1, A) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, nrad1, U) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Return Form VIII verbal noun. local function form_viii_verbal_noun(base, vowel_spec, rad1, rad2, rad3) local final_weak = is_final_weak(base, vowel_spec) rad3 = final_weak and HAMZA or rad3 return {high_form_verbal_noun(vowel_spec.form_viii_assim, rad2, rad3)} end -- Make form VIII sound or final-weak verb. local function make_form_viii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) -- check for irregular verb اِتَّخَذَ if axadh_radicals(rad1, rad2, rad3) then base.irregular = true rad1 = T end make_high_form_sound_final_weak_verb(base, vowel_spec, vowel_spec.form_viii_assim, rad2, rad3) end conjugations["VIII-sound"] = function(base, vowel_spec) make_form_viii_sound_final_weak_verb(base, vowel_spec) end conjugations["VIII-final-weak"] = function(base, vowel_spec) make_form_viii_sound_final_weak_verb(base, vowel_spec) end conjugations["VIII-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = form_viii_verbal_noun(base, vowel_spec, rad1, Y, rad3) -- various stem bases local nonpast_stem_base = vowel_spec.form_viii_assim local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, nonpast_stem_base) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["VIII-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = form_viii_verbal_noun(base, vowel_spec, rad1, rad2, rad2) -- various stem bases local nonpast_stem_base = q(vowel_spec.form_viii_assim, A) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, vowel_spec.form_viii_assim, U) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IX-sound"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(_I, rad1, SK, rad2, I, rad3, AA, rad3) -- various stem bases local nonpast_stem_base = q(rad1, SK, rad2, A) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, rad1, SK, rad2, U) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IX-final-weak"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high_form_sound_final_weak_verb(base, vowel_spec, q(rad1, SK, rad2), rad3, rad3) end -- Populate a sound or final-weak verb for any of the various high-numbered -- augmented forms that have 5 consonants in the stem and the same pattern of -- vowels. Some of these consonants in certain verb parts are w's, which leads to -- apparent anomalies in certain stems of these parts, but these anomalies -- are handled automatically in postprocessing, where we resolve sequences of -- iwC -> īC, uwC -> ūC, w + sukūn + w -> w + shadda. local function make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, rad3, rad4, rad5) make_high_form_sound_final_weak_verb(base, vowel_spec, q(rad1, SK, rad2), q(rad3, SK, rad4), rad5) end -- Make form X sound or final-weak verb. local function make_form_x_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) -- check for irregular verb اِسْتَحْيَا (also اِسْتَحَى) local is_hayy = hayy_radicals(rad1, rad2, rad3) local variant = vowel_spec.variant or "both" if not is_hayy or variant == "long" or variant == "both" then make_high5_form_sound_final_weak_verb(base, vowel_spec, S, T, rad1, rad2, rad3) end if is_hayy and (variant == "short" or variant == "both") then base.irregular = true -- Add alternative entries to the verbal paradigms. Any duplicates are removed during addition. make_high_form_sound_final_weak_verb(base, vowel_spec, S .. SK .. T, rad1, rad3) end end conjugations["X-sound"] = function(base, vowel_spec) make_form_x_sound_final_weak_verb(base, vowel_spec) end conjugations["X-final-weak"] = function(base, vowel_spec) make_form_x_sound_final_weak_verb(base, vowel_spec) end conjugations["X-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(base.reduced and "اِسْ" or "اِسْتِ", rad1, AA, rad3, AH) -- various stem bases local past_stem_base = q(base.reduced and "اِسْ" or "اِسْتَ", rad1) local nonpast_stem_base = q(base.reduced and "سْ" or "سْتَ", rad1) local past_pass_stem_base = q(base.reduced and "اُسْ" or "اُسْتُ", rad1) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["X-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q("اِسْتِ", rad1, SK, rad2, AA, rad2) -- various stem bases local past_stem_base = q("اِسْتَ", rad1) local nonpast_stem_base = q("سْتَ", rad1) local past_pass_stem_base = q("اُسْتُ", rad1) -- make parts if base.altgem then inflect_tense(base, "past", "", {q(past_stem_base, A, rad2, SH), all_same = 1}, past_endings_ay_12_person_only) end make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn, base.altgem and "[uncommon]" or nil) end conjugations["XI-sound"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(_I, rad1, SK, rad2, II, rad3, AA, rad3) -- various stem bases local nonpast_stem_base = q(rad1, SK, rad2, AA) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, rad1, SK, rad2, UU) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Probably no form XI final-weak, since already geminate in form; would behave as XI-sound. -- Make form XII sound or final-weak verb. local function make_form_xii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, W, rad2, rad3) end conjugations["XII-sound"] = function(base, vowel_spec) make_form_xii_sound_final_weak_verb(base, vowel_spec) end conjugations["XII-final-weak"] = function(base, vowel_spec) make_form_xii_sound_final_weak_verb(base, vowel_spec) end -- Make form XIII sound or final-weak verb. local function make_form_xiii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, W, W, rad3) end conjugations["XIII-sound"] = function(base, vowel_spec) make_form_xiii_sound_final_weak_verb(base, vowel_spec) end conjugations["XIII-final-weak"] = function(base, vowel_spec) make_form_xiii_sound_final_weak_verb(base, vowel_spec) end -- Make a form XIV or XV sound or final-weak verb. Last radical appears twice (if`anlala / yaf`anlilu) so if it were -- w or y you'd get if`anwā / yaf`anwī or if`anyā / yaf`anyī, i.e. unlike for most augmented verbs, the identity of -- the radical matters. local function make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local lastrad = base.verb_form == "XV" and Y or rad3 make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, N, rad3, lastrad) end conjugations["XIV-sound"] = function(base, vowel_spec) make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) end conjugations["XIV-final-weak"] = function(base, vowel_spec) make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) end conjugations["XV-sound"] = function(base, vowel_spec) make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) end -- Probably no form XV final-weak, since already final-weak in form; would behave as XV-sound. -- Make form Iq or IIq sound or final-weak verb. local function make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local vform = base.verb_form local vn = vform == "IIq" and q(TA, rad1, A, rad2, SK, rad3, (final_weak and IN or q(U, rad4))) or q(rad1, A, rad2, SK, rad3, (final_weak and AAH or q(A, rad4, AH))) local ta_pref = vform == "IIq" and TA or "" local tu_pref = vform == "IIq" and TU or "" -- various stem bases local past_stem_base = q(ta_pref, rad1, A, rad2, SK, rad3) local nonpast_stem_base = past_stem_base local past_pass_stem_base = q(tu_pref, rad1, U, rad2, SK, rad3) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["Iq-sound"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end conjugations["Iq-final-weak"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IIq-sound"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IIq-final-weak"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end -- Make form IIIq sound or final-weak verb. local function make_form_iiiq_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, N, rad3, rad4) end conjugations["IIIq-sound"] = function(base, vowel_spec) make_form_iiiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IIIq-final-weak"] = function(base, vowel_spec) make_form_iiiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IVq-sound"] = function(base, vowel_spec) local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) local vn = q(_I, rad1, SK, rad2, I, rad3, SK, rad4, AA, rad4) -- various stem bases local past_stem_base = q(_I, rad1, SK, rad2, A, rad3) local nonpast_stem_base = q(rad1, SK, rad2, A, rad3) local past_pass_stem_base = q(_U, rad1, SK, rad2, U, rad3) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Probably no form IVq final-weak, since already geminate in form; would behave as IVq-sound. end create_conjugations() ------------------------------------------------------------------------------- -- Guts of main conjugation function -- ------------------------------------------------------------------------------- -- Given form, weakness and radicals, check to make sure the radicals present are allowable for the weakness. Hamzas on -- alif/wāw/yāʾ seats are never allowed (should always appear as hamza-on-the-line), and various weaknesses have various -- strictures on allowable consonants. local function check_radicals(form, weakness, rad1, rad2, rad3, rad4) local function hamza_check(index, rad) if rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or rad == HAMZA_ON_W or rad == HAMZA_ON_Y then error("Radical " .. index .. " is " .. rad .. " but should be ء (hamza on the line)") end end local function check_waw_ya(index, rad) if not is_waw_ya(rad) then error("Radical " .. index .. " is " .. rad .. " but should be و or ي") end end local function check_not_waw_ya(index, rad) if is_waw_ya(rad) then error("In a sound verb, radical " .. index .. " should not be و or ي") end end hamza_check(rad1) hamza_check(rad2) hamza_check(rad3) hamza_check(rad4) if weakness == "assimilated" or weakness == "assimilated+final-weak" then if rad1 ~= W then error("Radical 1 is " .. rad1 .. " but should be و") end -- don't check that non-assimilated form I verbs don't have wāw as their -- first radical because some form-I verbs exist where a first-radical wāw -- behaves as sound, e.g. wajuha yawjuhu "to be distinguished". end if weakness == "final-weak" or weakness == "assimilated+final-weak" then if rad4 then check_waw_ya(4, rad4) else check_waw_ya(3, rad3) end elseif vform_supports_final_weak(form) then -- non-final-weak verbs cannot have weak final radical if there's a corresponding -- final-weak verb category. I think this is safe. We may have problems with -- ḥayya/ḥayiya yaḥyā if we treat it as a geminate verb. if rad4 then check_not_waw_ya(4, rad4) else check_not_waw_ya(3, rad3) end end if weakness == "hollow" then check_waw_ya(2, rad2) -- don't check that non-hollow verbs in forms that support hollow verbs -- don't have wāw or yāʾ as their second radical because some verbs exist -- where a middle-radical wāw/yāʾ behaves as sound, e.g. form-VIII izdawaja -- "to be in pairs". end if weakness == "geminate" then if rad4 then error("Internal error: No geminate quadrilaterals, should not be seen") end if rad2 ~= rad3 then error("Weakness is geminate; radical 3 is " .. rad3 .. " but should be same as radical 2 " .. rad2) end elseif vform_supports_geminate(form) then -- non-geminate verbs cannot have second and third radical same if there's -- a corresponding geminate verb category. I think this is safe. We -- don't fuss over double wāw or double yāʾ because this could legitimately -- be a final-weak verb with middle wāw/yāʾ, treated as sound. if rad4 then error("Internal error: No quadrilaterals should support geminate verbs") end if rad2 == rad3 and not is_waw_ya(rad2) then error("Weakness is '" .. weakness .. "'; radical 2 and 3 are same at " .. rad2 .. " but should not be; consider making weakness 'geminate'") end end end -- array of substitutions; each element is a 2-entry array FROM, TO; do it -- this way so the concatenations only get evaluated once local postprocess_subs = { -- reorder short-vowel + shadda -> shadda + short-vowel for easier processing {"(" .. AIU .. ")" .. SH, SH .. "%1"}, ----------same letter separated by sukūn should instead use shadda--------- ------------happens e.g. in kun-nā "we were".----------------- {"(.)" .. SK .. "%1", "%1" .. SH}, ---------------------------- assimilated verbs ---------------------------- -- iw, iy -> ī (assimilated verbs) {I .. W .. SK, II}, {I .. Y .. SK, II}, -- uw, uy -> ū (assimilated verbs) {U .. W .. SK, UU}, {U .. Y .. SK, UU}, -------------- final -yā uses tall alif not alif maqṣūra ------------------ {"(" .. Y .. SH .. "?" .. A .. ")" .. AMAQ, "%1" .. ALIF}, ----------------------- handle hamza assimilation ------------------------- -- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel {HAMZA .. A .. HAMZA .. SK, HAMZA .. A .. ALIF}, {HAMZA .. I .. HAMZA .. SK, HAMZA .. I .. Y}, {HAMZA .. U .. HAMZA .. SK, HAMZA .. U .. W} } local postprocess_tr_subs = { {"ī([" .. vowels .. "y*])", "iy%1"}, {"ū([" .. vowels .. "w*])", "uw%1"}, {"(.)%*", "%1%1"}, -- implement shadda ---------------------------- assimilated verbs ---------------------------- -- iw, iy -> ī (assimilated verbs) {"iw([^" .. vowels .. "w])", "ī%1"}, {"iy([^" .. vowels .. "y])", "ī%1"}, -- uw, uy -> ū (assimilated verbs) {"uw([^" .. vowels .. "w])", "ū%1"}, {"uy([^" .. vowels .. "y])", "ū%1"}, ----------------------- handle hamza assimilation ------------------------- -- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel {"ʔaʔ(" .. NV .. ")", "ʔā%1"}, {"ʔiʔ(" .. NV .. ")", "ʔī%1"}, {"ʔuʔ(" .. NV .. ")", "ʔū%1"}, } -- Post-process verb parts to eliminate phonological anomalies. Many of the changes, particularly the tricky ones, -- involve converting hamza to have the proper seat. The rules for this are complicated and are documented on the -- [[w:Hamza]] Wikipedia page. In some cases there are alternatives allowed, and we handle them below by returning -- multiple possibilities. local function postprocess_term(term) if term == "?" then return "?" end -- Add BORDER at text boundaries. term = BORDER .. term .. BORDER -- Do the main post-processing, based on the pattern substitutions in postprocess_subs. for _, sub in ipairs(postprocess_subs) do term = rsub(term, sub[1], sub[2]) end term = term:gsub(BORDER, "") if not rfind(term, HAMZA) then return term end term = term:gsub(HAMZA, HAMZA_PH) term = ar_utilities.process_hamza(term) if #term == 1 then term = term[1] end return term end local function postprocess_translit(translit) if translit == "?" then return "?" end -- Add BORDER at text boundaries. translit = BORDER .. translit .. BORDER -- Do the main post-processing, based on the pattern substitutions in postprocess_tr_subs. for _, sub in ipairs(postprocess_tr_subs) do translit = rsub(translit, sub[1], sub[2]) end translit = translit:gsub(BORDER, "") return translit end local function postprocess_forms(base) local converted_values = {} for slot, forms in pairs(base.forms) do local need_dedup = false for i, form in ipairs(forms) do local term = postprocess_term(form.form) local translit = form.translit and postprocess_translit(form.translit) or nil if term ~= form.form or translit ~= form.translit then need_dedup = true end converted_values[i] = {term, translit} end if need_dedup then local temp_dedup = {} for i = 1, #forms do local new_term, new_translit = unpack(converted_values[i]) if type(new_term) == "table" then for _, nt in ipairs(new_term) do local new_formobj = { form = nt, translit = new_translit, footnotes = forms[i].footnotes, } iut.insert_form(temp_dedup, "temp", new_formobj) end else local new_formobj = { form = new_term, translit = new_translit, footnotes = forms[i].footnotes, } iut.insert_form(temp_dedup, "temp", new_formobj) end end base.forms[slot] = temp_dedup.temp end end end local function process_slot_overrides(base) for slot, forms in pairs(base.slot_overrides) do local existing_values = base.forms[slot] base.forms[slot] = nil for _, form in ipairs(forms) do -- + in active participle for form I requests slot ap1 if form.form == "+" and (base.verb_form ~= "I" or slot ~= "ap") then if not existing_values then error(("Slot '%s' requested the default value but no such value available"):format(slot)) end -- We maintain an invariant that no two slots share a form object (although they may share the footnote -- lists inside the form objects). However, there is no need to copy the form objects here because there -- is a one-to-one correspondence between slots and slot overrides, i.e. you can't have a default value -- go into two slots. insert_form_or_forms(base, slot, existing_values, "allow overrides", form.uncertain) elseif default_indicator_to_active_participle_slot[form.form] then if form.form == "++" then if slot ~= "vn" and slot ~= "ap" and slot ~= "pp" then error(("Secondary default value request '++' only applicable to verbal nouns and pariciples, but found in slot '%s'"): format(slot)) end else if slot ~= "ap" then error(("Secondary default value request '%s' only applicable to active pariciples, but found in slot '%s'"): format(form.form, slot)) end end local secondary_default_slot = slot == "vn" and "vn2" or slot == "pp" and "pp2" or default_indicator_to_active_participle_slot[form.form] local existing_values = base.forms[secondary_default_slot] if not existing_values then error(("Slot '%s' requested a secondary default value using '%s' but no such value available"): format(slot, form.form)) end -- See comment above about the lack of need to copy the form objects. insert_form_or_forms(base, slot, existing_values, "allow overrides", form.uncertain) -- To make sure there aren't shared form objects. base.forms[secondary_default_slot] = nil else insert_form_or_forms(base, slot, form, "allow overrides", form.uncertain) end end end -- Now, for non-stative form-I verbs, fill the active participle slot from ap1 unless it should be missing (e.g. -- passive-only or user specified 'ap:-'). if base.verb_form == "I" and not base.forms.ap and base.forms.ap1 and not skip_slot(base, "ap") then local saw_non_stative = false for _, vowel_spec in ipairs(base.conj_vowels) do if req(vowel_spec.past, A) then saw_non_stative = true break end end if saw_non_stative then base.forms.ap = base.forms.ap1 -- To make sure there aren't shared form objects. base.forms.ap1 = nil end end end local function handle_lemma_linked(base) -- Compute linked versions of potential lemma slots, for use in {{ar-verb}}. We substitute the original lemma -- (before removing links) for forms that are the same as the lemma, if the original lemma has links. for _, slot in ipairs(export.potential_lemma_slots) do if base.forms[slot] then insert_form_or_forms(base, slot .. "_linked", iut.map_forms(base.forms[slot], function(form) if form == base.lemma and rfind(base.linked_lemma, "%[%[") then return base.linked_lemma else return form end end)) end end end -- Process specs given by the user using 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'. local function process_addnote_specs(base) for _, spec in ipairs(base.addnote_specs) do for _, slot_spec in ipairs(spec.slot_specs) do slot_spec = "^" .. slot_spec .. "$" for slot, forms in pairs(base.forms) do if rfind(slot, slot_spec) then -- To save on memory, side-effect the existing forms. for _, form in ipairs(forms) do form.footnotes = iut.combine_footnotes(form.footnotes, spec.footnotes) end end end end end end local function add_missing_links_to_forms(base) -- Any forms without links should get them now. Redundant ones will be stripped later. for slot, forms in pairs(base.forms) do for _, form in ipairs(forms) do if not form.form:find("%[%[") then form.form = "[[" .. form.form .. "]]" end end end end local function conjugate_verb(base) construct_stems(base) for _, vowel_spec in ipairs(base.conj_vowels) do -- Reconstruct conjugation type from verb form and (possibly inferred) weakness. conj_type = base.verb_form .. "-" .. vowel_spec.weakness -- Check that the conjugation type is recognized. if not conjugations[conj_type] then error("Unknown conjugation type '" .. conj_type .. "'") end -- The way the conjugation functions work is they always add entries to the appropriate parts of the paradigm -- (each of which is an array), rather than setting the values. This makes it possible to call more than one -- conjugation function and essentially get a paradigm of the "either A or B" kind. Doing this may insert -- duplicate entries into a particular paradigm part, but this is not a problem because we check for duplicate -- entries when adding them, and don't insert in that case. conjugations[conj_type](base, vowel_spec) end postprocess_forms(base) process_slot_overrides(base) -- This should happen before add_missing_links_to_forms() so that the comparison `form == base.lemma` in -- handle_lemma_linked() works correctly and compares unlinked forms to unlinked forms. handle_lemma_linked(base) process_addnote_specs(base) if not base.alternant_multiword_spec.args.noautolinkverb then add_missing_links_to_forms(base) end end local function parse_indicator_spec(angle_bracket_spec) -- Store the original angle bracket spec so we can reconstruct the overall conj spec with the lemma(s) in them. local base = { angle_bracket_spec = angle_bracket_spec, conj_vowels = {}, root_consonants = {}, user_stem_overrides = {}, user_slot_overrides = {}, slot_explicitly_missing = {}, slot_uncertain = {}, slot_override_uses_default = {}, addnote_specs = {}, } local function parse_err(msg) error(msg .. ": " .. angle_bracket_spec) end local function fetch_footnotes(separated_group) local footnotes for j = 2, #separated_group - 1, 2 do if separated_group[j + 1] ~= "" then parse_err("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'") end if not footnotes then footnotes = {} end table.insert(footnotes, separated_group[j]) end return footnotes end local inside = angle_bracket_spec:match("^<(.*)>$") assert(inside) local segments = put.parse_multi_delimiter_balanced_segment_run(inside, {{"[", "]"}, {"<", ">"}}) local dot_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "%.") -- The first dot-separated element must specify the verb form, e.g. IV or IIq. If the form is I, it needs to include -- the the past and non-past vowels, e.g. I/a~u for kataba ~ yaktubu. More than one vowel can be given, -- comma-separated, and more than one past~non-past pair can be given, slash-separated, e.g. I/a,u~u/i~a for form I -- كمل, which can be conjugated as kamala/kamula ~ yakmulu or kamila ~ yakmalu. An individual vowel spec must be one -- of a, i or u and in general (a) at least one past~non-past pair most be given, and (b) both past and non-past -- vowels must be given even though sometimes the vowel can be determined from the unvocalized form. An exception is -- passive-only verbs, where the vowels can't in general be determined (except indirectly in some cases by looking -- at an associated non-passive verb); in that case, the vowel~vowel spec can left out. local slash_separated_groups = put.split_alternating_runs_and_strip_spaces(dot_separated_groups[1], "/") local form_spec = slash_separated_groups[1] base.form_footnotes = fetch_footnotes(form_spec) if form_spec[1] == "" then parse_err("Missing verb form") end if not allowed_vforms_with_weakness_set[form_spec[1]] then parse_err(("Unrecognized verb form '%s', should be one of %s"):format( form_spec[1], list_to_text(allowed_vforms, nil, " or "))) end if form_spec[1]:find("%-") then base.verb_form, base.explicit_weakness = form_spec[1]:match("^(.-)%-(.*)$") else base.verb_form = form_spec[1] end if #slash_separated_groups > 1 then if base.verb_form ~= "I" then parse_err(("Past~non-past vowels can only be specified when verb form is I, but saw form '%s'"):format( base.verb_form)) end for i = 2, #slash_separated_groups do local slash_separated_group = slash_separated_groups[i] local tilde_separated_groups = put.split_alternating_runs_and_strip_spaces(slash_separated_group, "~") if #tilde_separated_groups ~= 2 then parse_err(("Expected two tilde-separated vowel specs: %s"):format(table.concat(slash_separated_group))) end local function parse_conj_vowels(tilde_separated_group, vtype) local conj_vowel_objects = {} local comma_separated_groups = put.split_alternating_runs_and_strip_spaces(tilde_separated_group, ",") for _, comma_separated_group in ipairs(comma_separated_groups) do local conj_vowel = comma_separated_group[1] if conj_vowel ~= "a" and conj_vowel ~= "i" and conj_vowel ~= "u" then parse_err(("Expected %s conjugation vowel '%s' to be one of a, i or u in %s"):format( vtype, conj_vowel, table.concat(slash_separated_group))) end conj_vowel = dia[conj_vowel] local conj_vowel_footnotes = fetch_footnotes(comma_separated_group) -- Try to use strings when possible as it makes q() significantly more efficient. if conj_vowel_footnotes then table.insert(conj_vowel_objects, {form = conj_vowel, footnotes = conj_vowel_footnotes}) else table.insert(conj_vowel_objects, conj_vowel) end end return conj_vowel_objects end local conj_vowel_spec = { past = parse_conj_vowels(tilde_separated_groups[1], "past"), nonpast = parse_conj_vowels(tilde_separated_groups[2], "non-past"), } table.insert(base.conj_vowels, conj_vowel_spec) end end for i = 2, #dot_separated_groups do local dot_separated_group = dot_separated_groups[i] local first_element = dot_separated_group[1] if first_element == "addnote" then local spec_and_footnotes = fetch_footnotes(dot_separated_group) if #spec_and_footnotes < 2 then parse_err("Spec with 'addnote' should be of the form 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'") end local slot_spec = table.remove(spec_and_footnotes, 1) local slot_spec_inside = rmatch(slot_spec, "^%[(.*)%]$") if not slot_spec_inside then parse_err("Internal error: slot_spec " .. slot_spec .. " should be surrounded with brackets") end local slot_specs = rsplit(slot_spec_inside, ",") -- FIXME: Here, [[Module:it-verb]] called strip_spaces(). Generally we don't do this. Should we? table.insert(base.addnote_specs, {slot_specs = slot_specs, footnotes = spec_and_footnotes}) elseif first_element:find("^var:") then if #dot_separated_group > 1 then parse_err(("Can't attach footnotes to 'var:' spec '%s'"):format(first_element)) end base.variant = first_element:match("^var:(.*)$") elseif first_element:find("^I+V?:") then local root_cons, root_cons_value = first_element:match("^(I+V?):(.*)$") local root_index if root_cons == "I" then root_index = 1 elseif root_cons == "II" then root_index = 2 elseif root_cons == "III" then root_index = 3 elseif root_cons == "IV" then root_index = 4 if not base.verb_form:find("q$") then parse_err(("Can't specify root consonant IV for non-quadriliteral verb form '%s': %s"):format( base.verb_form, first_element)) end end local cons, translit = root_cons_value:match("^(.*)//(.*)$") if not cons then cons = root_cons_value end local root_footnotes = fetch_footnotes(dot_separated_group) if not translit and not root_footnotes then base.root_consonants[root_index] = cons else base.root_consonants[root_index] = {form = cons, translit = translit, footnotes = root_footnotes} end elseif first_element:find("^[a-z][a-z0-9_]*:") then local slot_or_stem, remainder = first_element:match("^(.-):(.*)$") dot_separated_group[1] = remainder local comma_separated_groups = put.split_alternating_runs_and_strip_spaces(dot_separated_group, "[,،]") if overridable_stems[slot_or_stem] then if base.user_stem_overrides[slot_or_stem] then parse_err("Overridable stem '" .. slot_or_stem .. "' specified twice") end base.user_stem_overrides[slot_or_stem] = overridable_stems[slot_or_stem](comma_separated_groups, {prefix = slot_or_stem, base = base, parse_err = parse_err, fetch_footnotes = fetch_footnotes}) else -- assume a form override; we validate further later when the possible slots are available if base.user_slot_overrides[slot_or_stem] then parse_err("Form override '" .. slot_or_stem .. "' specified twice") end base.user_slot_overrides[slot_or_stem] = allow_multiple_values_for_override(comma_separated_groups, {prefix = slot_or_stem, base = base, parse_err = parse_err, fetch_footnotes = fetch_footnotes}, "is form override") end elseif indicator_flags[first_element] then if #dot_separated_group > 1 then parse_err("No footnotes allowed with '" .. first_element .. "' spec") end if base[first_element] then parse_err("Spec '" .. first_element .. "' specified twice") end base[first_element] = true else local passive, uncertain = first_element:match("^(.*)(%?)$") passive = passive or first_element uncertain = not not uncertain if passive_types[passive] then if #dot_separated_group > 1 then parse_err("No footnotes allowed with '" .. passive .. "' spec") end if base.passive then parse_err("Value for passive type specified twice") end base.passive = passive base.passive_uncertain = uncertain else parse_err("Unrecognized spec '" .. first_element .. "'") end end end return base end -- Normalize all lemmas, substituting the pagename for blank lemmas and adding links to multiword lemmas. local function normalize_all_lemmas(alternant_multiword_spec, head) -- (1) Add links to all before and after text. Remember the original text so we can reconstruct the verb spec later. if not alternant_multiword_spec.args.noautolinktext then iut.add_links_to_before_and_after_text(alternant_multiword_spec, "remember original") end -- (2) Remove any links from the lemma, but remember the original form so we can use it below in the 'lemma_linked' -- form. iut.map_word_specs(alternant_multiword_spec, function(base) if base.lemma == "" then base.lemma = head end base.user_specified_lemma = base.lemma base.lemma = m_links.remove_links(base.lemma) base.user_specified_verb = base.lemma base.verb = base.user_specified_verb local linked_lemma if alternant_multiword_spec.args.noautolinkverb or base.user_specified_lemma:find("%[%[") then linked_lemma = base.user_specified_lemma else -- Add links to the lemma so the user doesn't specifically need to, since we preserve -- links in multiword lemmas and include links in non-lemma forms rather than allowing -- the entire form to be a link. linked_lemma = iut.add_links(base.user_specified_lemma) end base.linked_lemma = linked_lemma end) end --W: Lexical exceptions — yāʾ-middle roots whose Form IV (أفعل) keeps the yāʾ SOUND -- instead of undergoing the regular إعلال to a hollow verb (أَفْيَلَ, not أَفَالَ). -- These are attested verbs (not the صفة مشبهة like أَخْيَف/خَيْفاء); each is sourced below. -- Only Form IV is affected; forms VII/VIII/X of these roots remain hollow. -- Keyed by rad1..rad2..rad3. local SOUND_FORM_IV_ROOTS = { ["خيل"] = true, -- أَخْيَلَ: أخيلت السماءُ تهيأت للمطر — المعجم الوسيط ج1 ص266 (وبه أَخالت أيضًا) ["غيل"] = true, -- أَغْيَلَ: أغيلته فهي مُغْيِل، سقته الغَيْل — لسان العرب ج11 ص111 (وبه أغالت أيضًا) ["حيج"] = true, -- أَحْيَجَ: أحيجت الأرضُ، على خلاف القياس — تاج العروس ج5 ص500 ["حين"] = true, -- أَحْيَنَ: أحين القومُ آن لهم أن يبلغوا ما أملوه — المعجم الوسيط ج1 ص212 ["خيف"] = true, -- أَخْيَفَ: أخيف الحاجُّ نزلوا خيف منى — كتاب الأفعال لابن القوطية ص35 ["ريف"] = true, -- أَرْيَفَ: أريفت الأرضُ إريافًا (خصبت) — المحيط في اللغة ج10 ص256 ["زين"] = true, -- أَزْيَنَ: أزينت الأرضُ، حسّنها عشبها — مقاييس اللغة ج3 ص41 } -- Determine weakness from radicals. Used when root given in place of lemma (e.g. for {{ar-verb forms}}). local function weakness_from_radicals(form, rad1, rad2, rad3, rad4) local weakness = nil local quadlit = form:find("q$") -- If weakness unspecified, derive from radicals. if not quadlit then if is_waw_ya(rad3) and rad1 == W and form == "I" then weakness = "assimilated+final-weak" elseif is_waw_ya(rad3) and vform_supports_final_weak(form) then weakness = "final-weak" elseif rad2 == rad3 and vform_supports_geminate(form) then weakness = "geminate" elseif is_waw_ya(rad2) and vform_supports_hollow(form) then --W: Both wāw (و) and yāʾ (ي) in the middle position create hollow verbs, in form I -- (قَالَ from ق-و-ل, بَاعَ from ب-ي-ع) and in the augmented forms IV, VII, VIII, X -- (أَتَاسَ from ت-ي-س, أَبَانَ from ب-ي-ن, أَطَاحَ from ط-ي-ح — معجم متن اللغة). if form == "IV" and rad2 == Y and SOUND_FORM_IV_ROOTS[rad1 .. rad2 .. rad3] then -- Lexical exceptions whose Form IV keeps the yāʾ sound (أَفْيَلَ); see SOUND_FORM_IV_ROOTS. weakness = "sound" else weakness = "hollow" end elseif rad1 == W and form == "I" then weakness = "assimilated" else weakness = "sound" end else if is_waw_ya(rad4) then weakness = "final-weak" else weakness = "sound" end end return weakness end -- Join the infixed tāʔ (ت) to the first radical in form VIII verbs. This may cause assimilation of the tāʔ to the -- radical or in some cases the radical to the tāʔ. Used when a root is supplied instead of a lemma (which already has -- the appropriate assimilation in it). local function form_viii_join_ta(rad) if rad == W or rad == Y or rad == "ت" then return "تّ" elseif rad == "د" then return "دّ" elseif rad == "ث" then return "ثّ" elseif rad == "ذ" then return "ذّ" elseif rad == "ز" then return "زْد" elseif rad == "ص" then return "صْط" elseif rad == "ض" then return "ضْط" elseif rad == "ط" then return "طّ" elseif rad == "ظ" then return "ظّ" else return rad .. SK .. "ت" end end local function detect_indicator_spec(base) base.forms = {} base.stem_overrides = {} base.slot_overrides = {} if not base.conj_vowels[1] then -- These may be converted to inferred vowels. If not, we throw an error if form I and not passive-only. base.conj_vowels = {{ past = "-", nonpast = "-", }} else -- If multiple vowels specified for a given vowel type (e.g. a,u~u), expand so that each spec in local expansion = {} for _, spec in ipairs(base.conj_vowels) do for _, past in ipairs(spec.past) do for _, nonpast in ipairs(spec.nonpast) do table.insert(expansion, {past = past, nonpast = nonpast}) end end end base.conj_vowels = expansion end local vform = base.verb_form -- check for quadriliteral form (Iq, IIq, IIIq, IVq) base.quadlit = not not vform:find("q$") -- Infer radicals as necessary. We infer a separate set of radicals for each past~non-past vowel combination because -- they may be different (particularly with form-I hollow verbs). for _, vowel_spec in ipairs(base.conj_vowels) do -- NOTE: rad1, rad2, etc. refer to user-specified radicals, which are formobj tables that optionally specify an -- explicit manual translit, whereas ir1, ir2, etc. refer to inferred radicals, which are either strings or -- lists of possible radicals. local rads = base.root_consonants local rad1, rad2, rad3, rad4 = rads[1], rads[2], rads[3], rads[4] -- Default any unspecified radicals to radicals determined from the headword. The returned radicals may be -- lists of possible radicals, where the first radical should be chosen if the user didn't explicitly specify a -- radical but all are allowed. If `ambig = true` is set in the table, the radical is considered ambiguous and -- categories won't be created for weak radicals. local weakness, ir1, ir2, ir3, ir4 if vform ~= "none" then ir1, ir2, ir3 = rmatch(base.lemma, "^([^_])_([^_])_([^_])$") if not ir1 then ir1, ir2, ir3, ir4 = rmatch(base.lemma, "^([^_])_([^_])_([^_])_([^_])$") end if ir1 then -- root given instead of lemma weakness = weakness_from_radicals(vform, ir1, ir2, ir3, ir4) if vform == "VIII" then vowel_spec.form_viii_assim = form_viii_join_ta(ir1) end else local ret = export.infer_radicals { headword = base.lemma, vform = vform, passive = base.passive, past_vowel = vowel_spec.past, nonpast_vowel = vowel_spec.nonpast, is_reduced = base.reduced, } weakness, ir1, ir2, ir3, ir4 = ret.weakness, ret.rad1, ret.rad2, ret.rad3, ret.rad4 vowel_spec.form_viii_assim = ret.form_viii_assim vowel_spec.past = ret.past_vowel vowel_spec.nonpast = ret.nonpast_vowel vowel_spec.variant = base.variant or ret.variant end end -- For most ambiguous radicals, the choice of radical doesn't matter because it doesn't affect the conjugation -- one way or another. For form I hollow verbs, however, it definitely does. In fact, the choice of radical is -- critical even beyond the past and non-past vowels because it affects the form of the passive participle. So, -- check for this and signal an error if the radical could not be inferred and is not given explicitly. if vform == "I" and type(ir2) == "table" and ir2.need_radical and not rad2 then error("Unable to guess middle radical of hollow form I verb; need to specify radical explicitly") end if vform == "I" and not is_passive_only(base.passive) and ( rget(vowel_spec.past) == "-" or rget(vowel_spec.nonpast) == "-") then error("Form I verb that isn't passive-only or final-weak must have past~non-past vowels specified") end -- Convert ambiguous radicals. local function regularize_inferred_radical(rad) if type(rad) == "table" then if rad.ambig then return {form = rad[1], ambig = true} else return rad[1] end else return rad end end -- Return the appropriate radical at index `index` (1 through 4), based either on the user-specified radical -- `user_radical` or (if unspecified) `inferred_radical`, inferred from the unvocalized lemma. Two values are -- returned, the "regularized" version of the radical (where ambiguous inferred radicals are converted to their -- most likely actual radical) and the non-regularized version. The returned values are form objects rather than -- strings. local function fetch_radical(user_radical, inferred_radical, index) if not user_radical then return regularize_inferred_radical(inferred_radical), inferred_radical else local rad_formval = rget(user_radical) if type(inferred_radical) == "table" then local allowed_radical_set = m_table.listToSet(inferred_radical) if not allowed_radical_set[rad_formval] then error(("For lemma %s, radical %s ambiguously inferred as %s but user radical incompatibly given as %s"): format(base.lemma, index, list_to_text(inferred_radical, nil, " or "), rad_formval)) end elseif rad_formval ~= inferred_radical then error(("For lemma %s, radical %s inferred as %s but user radical incompatibly given as %s"): format(base.lemma, index, inferred_radical, rad_formval)) end return user_radical, user_radical end end if vform ~= "none" then vowel_spec.rad1, vowel_spec.unreg_rad1 = fetch_radical(rad1, ir1, 1) vowel_spec.rad2, vowel_spec.unreg_rad2 = fetch_radical(rad2, ir2, 2) vowel_spec.rad3, vowel_spec.unreg_rad3 = fetch_radical(rad3, ir3, 3) if base.quadlit then vowel_spec.rad4, vowel_spec.unreg_rad4 = fetch_radical(rad4, ir4, 4) end end if vform == "I" then -- If explicit weakness given using 'I-sound' or 'I-assimilated', we may need to adjust the inferred weakness. if base.explicit_weakness == "sound" then if weakness == "assimilated" then weakness = "sound" elseif weakness == "assimilated+final-weak" then -- Verbs like waniya~yawnā "to be faint; to languish" (although the defaults should handle this -- correctly) weakness = "final-weak" else error(("Can't specify form 'I-sound' when inferred weakness is '%s' for lemma %s"):format( weakness, base.lemma)) end elseif base.explicit_weakness == "assimilated" then if weakness == "sound" then -- i~a verbs like waṭiʔa~yaṭaʔu "to tread, to trample"; wasiʕa~yasaʕu "to be spacious; to be well-off"; -- waṯiʔa~yaṯaʔu "to get bruised, to be sprained", which would default to sound. weakness = "assimilated" elseif weakness == "final-weak" then -- For completeness; not clear if any verbs occur where this is needed. (There are plenty of -- assimilated+final-weak verbs but the defaults should take care of them.) weakness = "assimilated+final-weak" else error(("Can't specify form 'I-assimilated' when inferred weakness is '%s' for lemma %s"):format( weakness, base.lemma)) end elseif base.explicit_weakness then error(("Internal error: Unrecognized value '%s' for base.explicit_weakness"):format(base.explicit_weakness)) end elseif vform == "none" then weakness = base.explicit_weakness elseif base.explicit_weakness then error(("Internal error: Explicit weakness should not be specifiable except with forms I and none, but saw explicit weakness '%s' with verb form '%s'"): format(base.explicit_weakness, vform)) end vowel_spec.weakness = weakness if vform ~= "none" then -- Error if radicals are wrong given the weakness. More likely to happen if the weakness is explicitly given -- rather than inferred. Will also happen if certain incorrect letters are included as radicals e.g. hamza on -- top of various letters, alif maqṣūra, tā' marbūṭa. check_radicals(vform, weakness, rget(vowel_spec.rad1), rget(vowel_spec.rad2), rget(vowel_spec.rad3), base.quadlit and rget(vowel_spec.rad4) or nil) end -- Check the variant value. local form_iii_vi_geminate = (vform == "III" or vform == "VI") and rget(vowel_spec.rad2) == rget(vowel_spec.rad3) and not req(vowel_spec.rad2, Y) local hayy_i_x = hayy_radicals(vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3) and (vform == "I" or vform == "X") if form_iii_vi_geminate or hayy_i_x then if vowel_spec.variant and vowel_spec.variant ~= "long" and vowel_spec.variant ~= "short" and vowel_spec.variant ~= "both" then error(("For form-III/VI geminate verb or form-I/X verb with ح-ي-ي radicals, saw unrecognized 'var:%s' value; should be 'var:long', 'var:short' or 'var:both'"):format( vowel_spec.variant)) end elseif vowel_spec.variant then error(("Variant value 'var:%s' not allowed in this context"):format(vowel_spec.variant)) end end -- If form I, regroup expanded vowels for display purposes. if vform == "I" then local group_by_past = {} for _, vowel_spec in ipairs(base.conj_vowels) do m_table.insertIfNot(group_by_past, { past = undia[rget(vowel_spec.past)], nonpasts = {undia[rget(vowel_spec.nonpast)]}, }, { key = function(obj) return obj.past end, combine = function(obj1, obj2) for _, nonpast in ipairs(obj2.nonpasts) do m_table.insertIfNot(obj1.nonpasts, nonpast) end end, }) end local group_by_nonpast = {} for _, vowel_spec in ipairs(group_by_past) do m_table.insertIfNot(group_by_nonpast, { pasts = {vowel_spec.past}, nonpasts = vowel_spec.nonpasts, }, { key = function(obj) return obj.nonpasts end, combine = function(obj1, obj2) for _, past in ipairs(obj2.pasts) do m_table.insertIfNot(obj1.pasts, past) end end, }) end base.grouped_conj_vowels = group_by_nonpast end -- Set value of passive. If not specified, default is yes for forms II, III, IV and Iq; no but uncertainly for -- forms VII, IX, XI - XV and IIIq - IVq, as well as form I with past vowel u; impersonal but uncertainly for form -- V, VI, X and IIq, as well as form I with past vowel i; and yes but uncertainly for the remainder (form I with -- past vowel only a and form VIII). if not base.passive then base.passive_defaulted = true -- Temporary tracking for defaulted passives by verb form, weakness and (for form I) past/non-past vowels. track_if_ar_conj(base, "passive-defaulted/" .. vform) for _, vowel_spec in ipairs(base.conj_vowels) do track_if_ar_conj(base, "passive-defaulted/" .. vform.. "/" .. vowel_spec.weakness) if vform == "I" then local past_nonpast = ("%s~%s"):format(undia[vowel_spec.past], undia[vowel_spec.nonpast]) track_if_ar_conj(base, "passive-defaulted/I/" .. past_nonpast) track_if_ar_conj(base, "passive-defaulted/I/" .. vowel_spec.weakness .. "/" .. past_nonpast) end end if vform_probably_full_passive(vform) then base.passive = "pass" else base.passive_uncertain = true for _, vowel_spec in ipairs(base.conj_vowels) do if vform_probably_no_passive(vform, vowel_spec.weakness, vowel_spec.past, vowel_spec.nonpast) then base.passive = "nopass" break elseif vform_probably_impersonal_passive(vform, vowel_spec.weakness, vowel_spec.past, vowel_spec.nonpast) then base.passive = "ipass" break end end base.passive = base.passive or "pass" end end -- NOTE: Currently there are no built-in stems or form overrides for Arabic; this code is inherited from -- [[Module:ca-verb]], where such things do exist, and is kept for generality in case we decide in the future to -- implement such things. -- Override built-in verb stems and overrides with user-specified ones. for stem, values in pairs(base.user_stem_overrides) do base.stem_overrides[stem] = values end for slot, values in pairs(base.user_slot_overrides) do if not base.alternant_multiword_spec.verb_slots_map[slot] then error("Unrecognized override slot '" .. slot .. "': " .. base.angle_bracket_spec) end if export.unsettable_slots_set[slot] then error("Slot '" .. slot .. "' cannot be set using an override: " .. base.angle_bracket_spec) end if skip_slot(base, slot, "allow overrides") then error("Override slot '" .. slot .. "' would be skipped based on the passive, 'noimp' and/or 'no_nonpast' settings: " .. base.angle_bracket_spec) end base.slot_overrides[slot] = values end if base.verb_form == "none-final-weak" then for _, stem_type in ipairs { "past", "past_pass", "nonpast", "nonpast_pass" } do if base.stem_overrides[stem_type .. "_c"] or base.stem_overrides[stem_type .. "_v"] then error(("Specify past stem for verb type 'none-final-weak' using '%s:...' not '%s_c:...' or '%s_v:...'"): format(stem_type, stem_type, stem_type)) end end for _, stem_type in ipairs { "past", "nonpast" } do if base.stem_overrides[stem_type] or not base.stem_overrides[stem_type .. "_final_weak_vowel"] then error(("For verb type 'none-final-weak', if '%s:...' specified, so must '%s_final_weak_vowel:...'"): format(stem_type, stem_type)) end end end end local function detect_all_indicator_specs(alternant_multiword_spec) add_slots(alternant_multiword_spec) alternant_multiword_spec.verb_forms = {} -- This means at least one individual base had the slot marked as explicitly missing. Another base (e.g. when -- there are multiple alternants) might have a value for the slot. In practice, we only respect this when there are -- no overall values in the slot and `slot_uncertain` isn't set; in this case, we display "no ..." for the slot -- instead of simply not displaying anything for the slot. alternant_multiword_spec.slot_explicitly_missing = {} -- This means at least one individual base had no values for the slot and the slot marked as explicitly uncertain. -- Note that this is different from a value being present but marked as uncertain (e.g. if an override was given -- with a ? after it); this causes the form object for the value to have `uncertain = true` set. If there are no -- overall values in the slot and `slot_uncertain` is set, we display this in the headword. alternant_multiword_spec.slot_uncertain = {} iut.map_word_specs(alternant_multiword_spec, function(base) -- So arguments, etc. can be accessed. WARNING: Creates circular reference. base.alternant_multiword_spec = alternant_multiword_spec detect_indicator_spec(base) if not base.nocat then m_table.insertIfNot(alternant_multiword_spec.verb_forms, base.verb_form) end if base.passive then alternant_multiword_spec.passive = base.passive end if base.passive_uncertain then alternant_multiword_spec.passive_uncertain = true end for slot, _ in pairs(base.slot_explicitly_missing) do alternant_multiword_spec.slot_explicitly_missing[slot] = true end end) end local function determine_slot_uncertainty_from_forms(alternant_multiword_spec) iut.map_word_specs(alternant_multiword_spec, function(base) -- If no verbal noun and verb form is not 'none' (manually-specified stems) — which currently only happens for -- form I — and the verbal noun wasn't explicitly indicated as missing using <vn:->, we assume it's just -- unknown/unspecified rather than missing. Same with active participles. for uncertain_slot, _ in pairs(slots_that_may_be_uncertain) do if not base.forms[uncertain_slot] and vform ~= "none" and not skip_slot(base, uncertain_slot) then base.slot_uncertain[uncertain_slot] = true end end -- Propagate slot uncertainty up. Currently only the verbal noun can have this set but we write the code -- generally. for slot, _ in pairs(base.slot_uncertain) do alternant_multiword_spec.slot_uncertain[slot] = true end end) -- If slot is uncertain and has no value, explicitly set its value to "?". for uncertain_slot, _ in pairs(slots_that_may_be_uncertain) do if not alternant_multiword_spec.forms[uncertain_slot] and alternant_multiword_spec.slot_uncertain[uncertain_slot] then alternant_multiword_spec.forms[uncertain_slot] = {{form = "?"}} end end end -- Determine certain properties of the verb from the overall forms, such as whether the verb is active-only or -- passive-only, is impersonal, lacks an imperative, etc. local function determine_verb_properties_from_forms(alternant_multiword_spec) alternant_multiword_spec.has_active = false alternant_multiword_spec.has_passive = false alternant_multiword_spec.has_non_impers_active = false alternant_multiword_spec.has_non_impers_passive = false alternant_multiword_spec.has_imp = false alternant_multiword_spec.has_past = false alternant_multiword_spec.has_nonpast = false for slot, _ in pairs(alternant_multiword_spec.forms) do if slot == "ap" or slot:find("[123]") and not slot:find("_pass") then alternant_multiword_spec.has_active = true end if slot == "pp" or slot:find("[123]") and slot:find("_pass") then alternant_multiword_spec.has_passive = true end if slot:find("[123]") and not slot:find("pass_[123]") and not slot:find("3ms") then alternant_multiword_spec.has_non_impers_active = true end if slot:find("pass_[123]") and not slot:find("3ms") then alternant_multiword_spec.has_non_impers_passive = true end if slot:find("^imp_") then alternant_multiword_spec.has_imp = true end if slot:find("^past_") then alternant_multiword_spec.has_past = true end if slot:find("^ind_") or slot:find("^sub_") or slot:find("^juss_") then alternant_multiword_spec.has_nonpast = true end end end local function add_categories_and_annotation(alternant_multiword_spec, base, multiword_lemma, insert_ann, insert_cat) -- Useful e.g. in constructing suppletive verbs out of parts. For a verb like جاء or أتى whose imperative comes -- from the unrelated verb تعالى, we don't want the latter verb showing up in categories or annotations. if base.nocat then return end local vform = base.verb_form if vform ~= "none" then insert_ann("form", vform) insert_cat("form-" .. vform .. " verbs") --W: Add morphological pattern corresponding to the form local morph_pattern = FORM_TO_MORPHOLOGICAL_PATTERN[vform] if morph_pattern then insert_ann("morph_pattern", morph_pattern) end end if base.reduced then insert_ann("reduced", "reduced") if vform ~= "none" then insert_cat("form-" .. vform .. " reduced verbs") end end if base.quadlit then insert_cat("verbs with quadriliteral roots") end if base.passive_defaulted then insert_cat("verbs with defaulted passive") end for _, vowel_spec in ipairs(base.conj_vowels) do local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local weakness = vowel_spec.weakness -- We have to distinguish weakness by form and weakness by conjugation. Weakness by form merely indicates the -- presence of weak letters in certain positions in the radicals. Weakness by conjugation is related to how the -- verbs are conjugated. For example, form-II verbs that are "hollow by form" (middle radical is wāw or yāʾ) are -- conjugated as sound verbs. Another example: form-I verbs with initial wāw are "assimilated by form" and most -- are assimilated by conjugation as well, but a few are sound by conjugation, e.g. wajuha yawjuhu "to be -- distinguished" (rather than wajuha yajuhu); similarly for some hollow-by-form verbs in various forms, e.g. -- form VIII izdawaja yazdawiju "to be in pairs" (rather than izdāja yazdāju). Categories referring to weakness -- always refer to weakness by conjugation; weakness by form is distinguished only by categories such as -- [[:Category:Arabic form-III verbs with و as second radical]]. insert_ann("weakness", weakness) if vform ~= "none" then insert_cat(("%s form-%s verbs"):format(weakness, vform)) end local function radical_is_ambiguous(rad) return type(rad) == "table" and rad.ambig end local function radical_is_unambiguous_weak(rad) return not radical_is_ambiguous(rad) and (is_waw_ya(rad) or req(rad, HAMZA)) end if vform ~= "none" then local ur1, ur2, ur3, ur4 = vowel_spec.unreg_rad1, vowel_spec.unreg_rad2, vowel_spec.unreg_rad3, vowel_spec.unreg_rad4 -- Create headword categories based on the radicals. Do the following before -- converting the Latin radicals into Arabic ones so we distinguish -- between ambiguous and non-ambiguous radicals. if radical_is_ambiguous(ur1) or radical_is_ambiguous(ur2) or radical_is_ambiguous(ur3) or ur4 and radical_is_ambiguous(ur4) then insert_cat("verbs with ambiguous radicals") end if radical_is_unambiguous_weak(ur1) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur1) .. " as first radical") end if radical_is_unambiguous_weak(ur2) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur2) .. " as second radical") end if radical_is_unambiguous_weak(ur3) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur3) .. " as third radical") end if ur4 and radical_is_unambiguous_weak(ur4) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur4) .. " as fourth radical") end end end if vform == "I" and not is_passive_only(base.passive) then for _, vowel_spec in ipairs(base.grouped_conj_vowels) do insert_ann("vowels", ("%s ~ %s"):format(table.concat(vowel_spec.pasts, "/"), table.concat(vowel_spec.nonpasts, "/"))) for _, past in ipairs(vowel_spec.pasts) do for _, nonpast in ipairs(vowel_spec.nonpasts) do if past == "-" or nonpast == "-" then error("Internal error: Saw form I past vowel %s and non-past vowel %s but - in place of vowel should have triggered an error earlier") end insert_cat(("form-I verbs with past vowel %s and non-past vowel %s"):format(past, nonpast)) --W: Add morphological pattern for form I based on vowels local form_i_with_vowels = ("I/%s~%s"):format(past, nonpast) local morph_pattern = FORM_TO_MORPHOLOGICAL_PATTERN[form_i_with_vowels] if morph_pattern then insert_ann("morph_pattern", morph_pattern) end end end end end for slot, name in pairs(slots_that_may_be_uncertain) do if base.slot_uncertain[slot] then -- An unspecified and non-defaulted verbal noun (form I) is considered uncertain rather than explicitly -- missing. Use <vn:-> to explicitly indicate the lack of verbal noun. Same for form-I stative active -- participles. insert_cat(("verbs with unknown or uncertain %ss"):format(name)) end end if base.irregular then insert_ann("irreg", "irregular") insert_cat("irregular verbs") end end -- Compute the categories to add the verb to, as well as the annotation to display in the conjugation title bar. We -- combine the code to do these functions as both categories and title bar contain similar information. local function compute_categories_and_annotation(alternant_multiword_spec) alternant_multiword_spec.categories = {} local ann = {} alternant_multiword_spec.annotation = ann ann.form = {} ann.morph_pattern = {} --W: arabic morphological pattern corresponding to the form ann.weakness = {} ann.vowels = {} ann.passive = nil ann.reduced = {} ann.irreg = {} ann.defective = {} local multiword_lemma = false for _, slot in ipairs(export.potential_lemma_slots) do if alternant_multiword_spec.forms[slot] then for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do if formobj.form:find(" ") then multiword_lemma = true break end end break end end local function insert_ann(anntype, value) m_table.insertIfNot(alternant_multiword_spec.annotation[anntype], value) end local function insert_cat(cat, also_when_multiword) -- Don't place multiword terms in categories like 'Arabic form-II verbs' to avoid spamming the categories with -- such terms. if also_when_multiword or not multiword_lemma then m_table.insertIfNot(alternant_multiword_spec.categories, "Arabic " .. cat) end end iut.map_word_specs(alternant_multiword_spec, function(base) add_categories_and_annotation(alternant_multiword_spec, base, multiword_lemma, insert_ann, insert_cat) end) for slot, name in pairs(slots_that_may_be_uncertain) do if alternant_multiword_spec.forms[slot] then for _, form in ipairs(alternant_multiword_spec.forms[slot]) do if form.uncertain then if form.form == "?" then insert_cat(("verbs with explicitly unknown %ss"):format(name)) else insert_cat(("verbs needing %s checked"):format(name)) end break end end end end if alternant_multiword_spec.has_active then if alternant_multiword_spec.has_passive and alternant_multiword_spec.has_non_impers_passive then insert_cat("verbs with full passive") ann.passive = "full passive" elseif alternant_multiword_spec.has_passive then insert_cat("verbs with impersonal passive") ann.passive = "impersonal passive" else insert_cat("verbs lacking passive forms") ann.passive = "no passive" end else if alternant_multiword_spec.has_non_impers_passive then insert_cat("passive verbs") insert_cat("verbs with full passive") ann.passive = "passive-only" else insert_cat("passive verbs") insert_cat("impersonal verbs") insert_cat("verbs with impersonal passive") ann.passive = "impersonal (passive-only)" end end if alternant_multiword_spec.passive_uncertain then insert_cat("verbs needing passive checked") ann.passive = ann.passive .. ' <abbr title="passive status uncertain">(?)</abbr>' end if alternant_multiword_spec.has_active and not alternant_multiword_spec.has_imp then insert_ann("defective", "no imperative") insert_cat("verbs lacking imperative forms") end if not alternant_multiword_spec.has_past then insert_ann("defective", "no past") insert_cat("verbs lacking past forms") end if not alternant_multiword_spec.has_nonpast then insert_ann("defective", "no non-past") insert_cat("verbs lacking non-past forms") end local ann_parts = {} local function insert_ann_part(part, conj) local val = table.concat(ann[part], conj or " or ") if val ~= "" and val ~= "regular" then table.insert(ann_parts, val) end end insert_ann_part("form") insert_ann_part("weakness") insert_ann_part("reduced") insert_ann_part("vowels") if ann.passive then table.insert(ann_parts, ann.passive) end insert_ann_part("irreg") insert_ann_part("defective", "، ") --W: Save morph_pattern before overwriting annotation with string alternant_multiword_spec.morph_pattern = ann.morph_pattern alternant_multiword_spec.annotation = table.concat(ann_parts, "، ") end local function show_forms(alternant_multiword_spec) local lemmas = {} for _, slot in ipairs(export.potential_lemma_slots) do if alternant_multiword_spec.forms[slot] then for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do table.insert(lemmas, formobj) end break end end alternant_multiword_spec.lemmas = lemmas -- save for later use in make_table() alternant_multiword_spec.vn = alternant_multiword_spec.forms.vn -- save for later use in make_table() -- Reconstruct the original verb spec without overrides for verbal nouns and participles, since those specific slots -- are ignored by {{ar-verb form}}. Compute this once beforehand; `transform_accel_obj` is called repeatedly on each -- form and we don't want to compute this repeatedly. local reconstructed_verb_spec = iut.reconstruct_original_spec(alternant_multiword_spec, { preprocess_angle_bracket_spec = function(spec) spec = spec:match("^<(.*)>$") assert(spec) local segments = put.parse_multi_delimiter_balanced_segment_run(spec, {{"[", "]"}, {"<", ">"}}) local dot_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "%.") -- Rejoin each dot-separated group into a single string, since we aren't actually going to do any parsing -- of bracket-bounded textual runs; then filter out overrides for verbal nouns and participles. local filtered_indicators = {} for _, dot_separated_group in ipairs(dot_separated_groups) do local indicator = table.concat(dot_separated_group) -- FIXME: Do we want to filter out any other indicators? if not (indicator:find("^vn:") or indicator:find("^[ap]p:")) then table.insert(filtered_indicators, indicator) end end return ("<%s>"):format(table.concat(filtered_indicators, ".")) end, }) -- If we're dealing with a single word, no alternants and a single verb form, use the auto-conjugation-fetching -- variant. local reconstructed_lemma, inside = reconstructed_verb_spec:match("^([^ <>()]+)(%b<>)$") if inside and alternant_multiword_spec.verb_forms[1] and not alternant_multiword_spec.verb_forms[2] then reconstructed_verb_spec = ("+%s<%s>"):format(reconstructed_lemma, alternant_multiword_spec.verb_forms[1]) end local function transform_accel_obj(slot, formobj, accel_obj) if not accel_obj then return accel_obj end if slot == "ap" or slot == "pp" or slot == "vn" then -- FIXME: [[Module:accel]] can't correctly handle more than one verb form for participles and verbal nouns accel_obj.form = slot .. "-" .. table.concat(alternant_multiword_spec.verb_forms, "،") else accel_obj.form = "verb-form-" .. reconstructed_verb_spec end return accel_obj end local function generate_link(data) local form = data.form local term = form.formval_for_link local alt = form.alt if term == "?" then term = nil alt = "?" end --W: Create simple link for verb forms without #Arabic anchor local display = alt or term local link if display then link = link_term_simple(display) else link = "" end link = link .. iut.get_footnote_text(form.footnotes, data.footnote_obj) --W: qualifier/label rendering removed along with the q/qq/l/ll modifiers; -- it required [[Module:pron qualifier]], absent on ar.wiktionary. return link end local props = { lang = lang, lemmas = lemmas, transform_accel_obj = transform_accel_obj, generate_link = generate_link, slot_list = alternant_multiword_spec.verb_slots, include_translit = false, } iut.show_forms(alternant_multiword_spec.forms, props) end ------------------------------------------------------------------------------- -- Functions to create inflection tables -- ------------------------------------------------------------------------------- -- Make the conjugation table. Called from export.show(). local function make_table(alternant_multiword_spec) local text = mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-top', args = { title = 'تصريف {title}', tall = 'yes', palette = 'green', category = 'conjugation', class = 'tr-alongside', -- temp hack to prevent extra line break } } text = text .. [=[ ! colspan="6" | <<الْمَصْدَر>> | colspan="7" | {vn} ]=] if alternant_multiword_spec.has_active then text = text .. [=[ |- ! colspan="6" | <<اِسْم الْفَاعِل>> | colspan="7" | {ap} ]=] end if alternant_multiword_spec.has_passive then text = text .. [=[ |- ! colspan="6" | <<اِسْم الْمَفْعُول>> | colspan="7" | {pp} ]=] end text = text .. [=[ |- ! colspan="999" class="separator" | ]=] if alternant_multiword_spec.has_active then text = text .. [=[ |- ! colspan="12" class="outer" | <<الْفِعْل الْمَعْلُوم>> |- ! colspan="2" | ! colspan="3" | <<الْمُفْرَد>> ! rowspan="12" class="separator" | ! colspan="2" | <<الْمُثَنَّى>> ! rowspan="12" class="separator" | ! colspan="3"| <<الْجَمْع>> |- ! colspan="2"| ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> |- ! rowspan="2" | <<الْمَاضِي>> ! class="secondary" | المذكر | rowspan="2" | {past_1s} | {past_2ms} | {past_3ms} | rowspan="2" | {past_2d} | {past_3md} | rowspan="2" | {past_1p} | {past_2mp} | {past_3mp} |- ! class="secondary" | المؤنث | {past_2fs} | {past_3fs} | {past_3fd} | {past_2fp} | {past_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَرْفُوع>> ! class="secondary" | المذكر | rowspan="2" | {ind_1s} | {ind_2ms} | {ind_3ms} | rowspan="2" | {ind_2d} | {ind_3md} | rowspan="2" | {ind_1p} | {ind_2mp} | {ind_3mp} |- ! class="secondary" | المؤنث | {ind_2fs} | {ind_3fs} | {ind_3fd} | {ind_2fp} | {ind_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَنْصُوب>> ! class="secondary" | المذكر | rowspan="2" | {sub_1s} | {sub_2ms} | {sub_3ms} | rowspan="2" | {sub_2d} | {sub_3md} | rowspan="2" | {sub_1p} | {sub_2mp} | {sub_3mp} |- ! class="secondary" | المؤنث | {sub_2fs} | {sub_3fs} | {sub_3fd} | {sub_2fp} | {sub_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَجْزُوم>> ! class="secondary" | المذكر | rowspan="2" | {juss_1s} | {juss_2ms} | {juss_3ms} | rowspan="2" | {juss_2d} | {juss_3md} | rowspan="2" | {juss_1p} | {juss_2mp} | {juss_3mp} |- ! class="secondary" | المؤنث | {juss_2fs} | {juss_3fs} | {juss_3fd} | {juss_2fp} | {juss_3fp} |- ! rowspan="2" | <<الْأَمْر>> ! class="secondary" | المذكر | rowspan="2" | | {imp_2ms} | rowspan="2" | | rowspan="2" | {imp_2d} | rowspan="2" | | rowspan="2" | | {imp_2mp} | rowspan="2" | |- ! class="secondary" | المؤنث | {imp_2fs} | {imp_2fp} ]=] end if alternant_multiword_spec.has_passive then text = text .. [=[ |- ! colspan="999" class="separator" | |- ! colspan="12" class="outer" | <<الْفِعْل الْمَجْهُول>> |- ! colspan="2" | ! colspan="3" | <<الْمُفْرَد>> ! rowspan="10" class="separator" | ! colspan="2" | <<الْمُثَنَّى>> ! rowspan="10" class="separator" | ! colspan="3" | <<الْجَمْع>> |- ! colspan="2" | ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> |- ! rowspan="2" | <<الْمَاضِي>> ! class="secondary" | المذكر | rowspan="2" | {past_pass_1s} | {past_pass_2ms} | {past_pass_3ms} | rowspan="2" | {past_pass_2d} | {past_pass_3md} | rowspan="2" | {past_pass_1p} | {past_pass_2mp} | {past_pass_3mp} |- ! class="secondary" | المؤنث | {past_pass_2fs} | {past_pass_3fs} | {past_pass_3fd} | {past_pass_2fp} | {past_pass_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَرْفُوع>> ! class="secondary" | المذكر | rowspan="2" | {ind_pass_1s} | {ind_pass_2ms} | {ind_pass_3ms} | rowspan="2" | {ind_pass_2d} | {ind_pass_3md} | rowspan="2" | {ind_pass_1p} | {ind_pass_2mp} | {ind_pass_3mp} |- ! class="secondary" | المؤنث | {ind_pass_2fs} | {ind_pass_3fs} | {ind_pass_3fd} | {ind_pass_2fp} | {ind_pass_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَنْصُوب>> ! class="secondary" | المذكر | rowspan="2" | {sub_pass_1s} | {sub_pass_2ms} | {sub_pass_3ms} | rowspan="2" | {sub_pass_2d} | {sub_pass_3md} | rowspan="2" | {sub_pass_1p} | {sub_pass_2mp} | {sub_pass_3mp} |- ! class="secondary" | المؤنث | {sub_pass_2fs} | {sub_pass_3fs} | {sub_pass_3fd} | {sub_pass_2fp} | {sub_pass_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَجْزُوم>> ! class="secondary" | المذكر | rowspan="2" | {juss_pass_1s} | {juss_pass_2ms} | {juss_pass_3ms} | rowspan="2" | {juss_pass_2d} | {juss_pass_3md} | rowspan="2" | {juss_pass_1p} | {juss_pass_2mp} | {juss_pass_3mp} |- ! class="secondary" | المؤنث | {juss_pass_2fs} | {juss_pass_3fs} | {juss_pass_3fd} | {juss_pass_2fp} | {juss_pass_3fp} ]=] end text = text .. mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-bottom', args = { notes = '{footnote}', } } local forms = alternant_multiword_spec.forms if not alternant_multiword_spec.lemmas then forms.title = "—" else local linked_lemmas = {} for _, form in ipairs(alternant_multiword_spec.lemmas) do --W: Create simple link for lemmas without #Arabic anchor -- table.insert(linked_lemmas, link_term(form.form, "term")) table.insert(linked_lemmas, link_term_simple(form.form)) end forms.title = table.concat(linked_lemmas, "، ") end local ann_parts = {} if alternant_multiword_spec.annotation ~= "" then table.insert(ann_parts, alternant_multiword_spec.annotation) end if alternant_multiword_spec.vn then local linked_vns = {} for _, form in ipairs(alternant_multiword_spec.vn) do table.insert(linked_vns, link_term(form.form, "term")) end table.insert(ann_parts, (#linked_vns > 1 and "verbal nouns" or "verbal noun") .. " " .. table.concat(linked_vns, "، ")) end local annotation = table.concat(ann_parts, "، ") --W: use only arabic morphological pattern in title annotation local morph_patterns = alternant_multiword_spec.morph_pattern if morph_patterns and #morph_patterns > 0 then annotation = table.concat(morph_patterns, "، ") forms.title = forms.title .. " (" .. annotation .. ")" end -- Format the table. local tagged_table = rsub(text, "<<(.-)>>", tag_text) return m_string_utilities.format(tagged_table, forms) end -- Helper function to get root letters and verb classification for simplified table header -- Returns: root_display (e.g., "ح س ب"), verb_type (e.g., "فعل ثلاثي صحيح سالم") local function get_verb_info(alternant_multiword_spec) local root_display = "" local verb_type = "فعل" local rad1, rad2, rad3, rad4 -- Extract radicals from the base word spec using iut.map_word_specs -- This accesses the radicals directly from the parsed verb specification iut.map_word_specs(alternant_multiword_spec, function(base) -- Get radicals from the first conjugation vowel spec if base.conj_vowels and #base.conj_vowels > 0 then local vowel_spec = base.conj_vowels[1] -- Extract radicals, handling both table and string types local function extract_radical(rad) if type(rad) == "table" then -- If it's a table, get the first element or the string representation return rad[1] or rget(rad) else return rad end end rad1 = extract_radical(vowel_spec.rad1) rad2 = extract_radical(vowel_spec.rad2) rad3 = extract_radical(vowel_spec.rad3) rad4 = vowel_spec.rad4 and extract_radical(vowel_spec.rad4) end end) -- Get basic/derived classification from morphological pattern local basic_deriv if alternant_multiword_spec.morph_pattern and #alternant_multiword_spec.morph_pattern > 0 then local morph_pattern = alternant_multiword_spec.morph_pattern[1] basic_deriv = MORPHOLOGICAL_PATTERN_TO_BASIC_DERIV[morph_pattern] end -- Format root display with spaces between letters if rad1 and rad2 and rad3 then if rad4 then -- Quadrilateral root_display = rad1 .. " " .. rad2 .. " " .. rad3 .. " " .. rad4 verb_type = "فعل رباعي" if basic_deriv then verb_type = verb_type .. " " .. basic_deriv end else -- triliteral root_display = rad1 .. " " .. rad2 .. " " .. rad3 verb_type = "فعل ثلاثي" if basic_deriv then verb_type = verb_type .. " " .. basic_deriv if basic_deriv == "مُجرَّد" then --W: the classification concerns only triliteral basic verbs (الثلاثي المُجرَّد) local classification = export.classify_triliteral_verb(rad1, rad2, rad3) verb_type = verb_type .. " " .. classification end end end end return root_display, verb_type end -- Make simplified conjugation table for ar.wiktionary. Called from export.show(). -- This table shows only past, present indicative, and imperative forms. -- No passive voice, no participles, no verbal nouns. local function make_table_ar(alternant_multiword_spec) local text = mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-top', args = { title = 'تصريف {title}', tall = 'yes', palette = 'green', category = 'conjugation', class = 'tr-alongside', -- temp hack to prevent extra line break } } -- Get verb root and classification local root_display, verb_type = get_verb_info(alternant_multiword_spec) -- First row: Root and verb type spanning all columns text = text .. "! colspan=\"2\" | <<الجذر>>: " .. root_display .. "\n" text = text .. "| colspan=\"3\" | <<'''" .. verb_type .. "'''>>\n" text = text .. [=[ |- ! colspan="2" | <<الضمائر>> ! <<الماضي>> ! <<المضارع>> ! <<الأمر>> |- ! rowspan="2" | <<المتكلّم>> | <<أنا>> | {past_1s} | {ind_1s} | |- | <<نحن>> | {past_1p} | {ind_1p} | |- ! rowspan="5" | <<المخاطب>> | <<أنتَ>> | {past_2ms} | {ind_2ms} | {imp_2ms} |- | <<أنتِ>> | {past_2fs} | {ind_2fs} | {imp_2fs} |- | <<أنتما>> | {past_2d} | {ind_2d} | {imp_2d} |- | <<أنتم>> | {past_2mp} | {ind_2mp} | {imp_2mp} |- | <<أنتنّ>> | {past_2fp} | {ind_2fp} | {imp_2fp} |- ! rowspan="6" | <<الغائب>> | <<هو>> | {past_3ms} | {ind_3ms} | |- | <<هي>> | {past_3fs} | {ind_3fs} | |- | <<هما>> | {past_3md} | {ind_3md} | |- | <<هما>> | {past_3fd} | {ind_3fd} | |- | <<هم>> | {past_3mp} | {ind_3mp} | |- | <<هنّ>> | {past_3fp} | {ind_3fp} | ]=] text = text .. mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-bottom', args = { notes = '{footnote}', } } local forms = alternant_multiword_spec.forms if not alternant_multiword_spec.lemmas then forms.title = "—" else local linked_lemmas = {} for _, form in ipairs(alternant_multiword_spec.lemmas) do --W: Create simple link for lemmas without #Arabic anchor -- table.insert(linked_lemmas, link_term(form.form, "term")) table.insert(linked_lemmas, link_term_simple(form.form)) end forms.title = table.concat(linked_lemmas, "، ") end local ann_parts = {} if alternant_multiword_spec.annotation ~= "" then table.insert(ann_parts, alternant_multiword_spec.annotation) end local annotation = table.concat(ann_parts, "، ") --W: use only arabic morphological pattern in title annotation local morph_patterns = alternant_multiword_spec.morph_pattern if morph_patterns and #morph_patterns > 0 then annotation = table.concat(morph_patterns, "، ") forms.title = forms.title .. " (" .. annotation .. ")" end -- Format the table. local tagged_table = rsub(text, "<<(.-)>>", tag_text) return m_string_utilities.format(tagged_table, forms) end ------------------------------------------------------------------------------- -- Verb classification functions -- ------------------------------------------------------------------------------- --[=[ تصنيف الفعل الثلاثي المُجرَّد حسب نوعه Classify an Arabic triliteral basic verb root based on its three letters. @param r1 الحرف الأول (first root letter - الفاء) @param r2 الحرف الثاني (second root letter - العين) @param r3 الحرف الثالث (third root letter - اللام) @return str: نوع الفعل (verb type classification in Arabic) Examples: classify_triliteral_verb('ك', 'ت', 'ب') --> 'صحيح سالم' classify_triliteral_verb('أ', 'ت', 'ي') --> 'معتل ناقص يائي مهموز الفاء' classify_triliteral_verb('ق', 'ل', 'و') --> 'معتل ناقص واوي' ]=] function export.classify_triliteral_verb(r1, r2, r3) -- تعريف أحرف العلة والهمزة -- الحروف الضعيفة (أحرف العلة): واو وياء فقط local weak_letters = {[W] = true, [Y] = true} -- الهمزة: جميع أشكال الهمزة (ء، أ، إ، ؤ، ئ) local hamza_letters = { [HAMZA] = true, -- ء [HAMZA_ON_ALIF] = true, -- أ [HAMZA_UNDER_ALIF] = true, -- إ [HAMZA_ON_W] = true, -- ؤ [HAMZA_ON_Y] = true -- ئ } -- فحص كل حرف local is_hamza = {hamza_letters[r1] or false, hamza_letters[r2] or false, hamza_letters[r3] or false} local is_weak = {weak_letters[r1] or false, weak_letters[r2] or false, weak_letters[r3] or false} -- عدد أحرف العلة local weak_count = (is_weak[1] and 1 or 0) + (is_weak[2] and 1 or 0) + (is_weak[3] and 1 or 0) -- فحص التضعيف (الحرف الثاني = الثالث) local is_doubled = (r2 == r3) -- ═══════════════════════════════════════════════════════════ -- المُعتل: الفعل الذي يحتوي على حرف أو أكثر من أحرف العلة -- ═══════════════════════════════════════════════════════════ if weak_count > 0 then -- ───────────────────────────────────────────────────── -- لفيف: يحتوي على حرفين أو أكثر من أحرف العلة -- ───────────────────────────────────────────────────── if weak_count >= 2 then -- مقرون: حرفا العلة متجاوران if is_weak[2] and is_weak[3] then -- الثاني والثالث if is_hamza[1] then return "معتل لفيف مقرون مهموز الفاء" end return "معتل لفيف مقرون" elseif is_weak[1] and is_weak[2] then -- الأول والثاني return "معتل لفيف مقرون" -- مفروق: الأول والثالث حرفا علة elseif is_weak[1] and is_weak[3] then if is_hamza[2] then return "معتل لفيف مفروق مهموز العين" end return "معتل لفيف مفروق" end -- ───────────────────────────────────────────────────── -- مثال: الحرف الأول حرف علة -- ───────────────────────────────────────────────────── elseif is_weak[1] then if r1 == W then -- واوي if is_hamza[3] then return "معتل مثال واوي مهموز اللام" elseif is_doubled then return "معتل مثال واوي مُضعَّف" elseif is_hamza[2] then return "معتل مثال واوي مهموز العين" end return "معتل مثال واوي" else -- يائي (ي) if is_doubled then return "معتل مثال يائي مُضعَّف" elseif is_hamza[2] then return "معتل مثال يائي مهموز العين" end return "معتل مثال يائي" end -- ───────────────────────────────────────────────────── -- أجوف: الحرف الثاني حرف علة -- ───────────────────────────────────────────────────── elseif is_weak[2] then if r2 == W then -- واوي if is_hamza[3] then return "معتل أجوف واوي مهموز اللام" elseif is_hamza[1] then return "معتل أجوف واوي مهموز الفاء" end return "معتل أجوف واوي" else -- يائي (ي) if is_hamza[3] then return "معتل أجوف يائي مهموز اللام" elseif is_hamza[1] then return "معتل أجوف يائي مهموز الفاء" end return "معتل أجوف يائي" end -- ───────────────────────────────────────────────────── -- ناقص: الحرف الثالث حرف علة -- ───────────────────────────────────────────────────── elseif is_weak[3] then if r3 == W then -- واوي if is_hamza[1] then return "معتل ناقص واوي مهموز الفاء" elseif is_hamza[2] then return "معتل ناقص واوي مهموز العين" end return "معتل ناقص واوي" else -- يائي (ي) if is_hamza[1] then return "معتل ناقص يائي مهموز الفاء" elseif is_hamza[2] then return "معتل ناقص يائي مهموز العين" end return "معتل ناقص يائي" end end -- ═══════════════════════════════════════════════════════════ -- الصحيح: الفعل الذي لا يحتوي على أحرف العلة -- ═══════════════════════════════════════════════════════════ else -- ───────────────────────────────────────────────────── -- مهموز: يحتوي على همزة -- ───────────────────────────────────────────────────── if is_hamza[1] or is_hamza[2] or is_hamza[3] then -- مهموز الفاء واللام: الأول والثالث همزة if is_hamza[1] and is_hamza[3] then return "صحيح مهموز الفاء واللام" -- مهموز الفاء elseif is_hamza[1] then if is_doubled then return "صحيح مُضعَّف مهموز الفاء" end return "صحيح مهموز الفاء" -- مهموز العين elseif is_hamza[2] then return "صحيح مهموز العين" -- مهموز اللام elseif is_hamza[3] then return "صحيح مهموز اللام" end -- ───────────────────────────────────────────────────── -- مُضعَّف: الحرف الثاني = الثالث -- ───────────────────────────────────────────────────── elseif is_doubled then return "صحيح مُضعَّف" -- ───────────────────────────────────────────────────── -- سالم: لا همزة، لا حرف علة، لا تضعيف -- ───────────────────────────────────────────────────── else return "صحيح سالم" end end return "غير معروف" end ------------------------------------------------------------------------------- -- External entry points -- ------------------------------------------------------------------------------- --W: Normalize root to use underscores as separators -- Accepts: "كتب", "ك_ت_ب", or "ك ت ب" -- Returns: "ك_ت_ب" local function normalize_root(root) if not root then return nil end -- Helper function to normalize a single character/radical local function normalize_char(char) -- Convert all hamza variants to canonical bare hamza if char == HAMZA_ON_ALIF or char == HAMZA_UNDER_ALIF or char == HAMZA_ON_W or char == HAMZA_ON_Y then return HAMZA -- Convert alif maqṣūra to yaa (ى → ي) elseif char == AMAQ then return Y -- Reject alif - it's not a root radical elseif char == ALIF then error("Root contains alif (ا) which is not a valid root radical. For weak verbs, use و (waw) or ي (yaa) instead.") end return char end -- Convert alif madda to hamza + alif for processing (will be caught as error below) root = root:gsub(AMAD, HAMZA .. ALIF) -- Replace spaces with underscores root = root:gsub(" ", "_") -- If no underscores yet, insert them between each character if not root:find("_") then -- Extract only Arabic letters (excluding diacritics) -- Arabic letter range: U+0621 to U+064A (basic letters) local chars = {} for char in mw.ustring.gmatch(root, "[ء-ي]") do table.insert(chars, normalize_char(char)) end root = table.concat(chars, "_") else -- Root already has underscores (e.g., "أ_م_ن"), normalize each radical local parts = mw.text.split(root, "_") for i, part in ipairs(parts) do parts[i] = normalize_char(part) end root = table.concat(parts, "_") end return root end --W: Construct the [1] parameter from root (جذر) and morphological pattern (وزن) -- Example: -- input: root="كتب", pattern="فعَل يفعُل" -- output: "ك_ت_ب<I/a~u>" local function construct_verb_spec(root, pattern) if not root or not pattern then return nil end local normalized_root = normalize_root(root) local form_code = MORPHOLOGICAL_PATTERN_TO_FORM[pattern] if not form_code then error("Unknown morphological pattern (وزن صرفي): " .. pattern) end return normalized_root .. "<" .. form_code .. ">" end -- Wrapper function that accepts جذر (root) and وزن (pattern) as parameters -- Returns: verb_type (e.g., "فعل ثلاثي مُجرَّد صحيح سالم") -- Example: export.get_verb_type({["جذر"] = "كتب", ["وزن"] = "فعَل يفعُل"}) function export.get_verb_type(args) local verb_spec if args["جذر"] and args["وزن"] then verb_spec = construct_verb_spec(args["جذر"], args["وزن"]) elseif args[1] then verb_spec = args[1] else error("Must provide either جذر and وزن, or a verb specification in args[1]") end local alternant_multiword_spec = export.do_generate_forms({[1] = verb_spec}) local _, verb_type = get_verb_info(alternant_multiword_spec) return verb_type end -- Append two lists `l1` and `l2`, removing duplicates. If either is {nil}, just return the other. local function combine_lists(l1, l2) -- combine_footnotes() does exactly what we want. return iut.combine_footnotes(l1, l2) end local function combine_metadata(data) local src1 = data.form1 local src2 = data.form2 local dest = data.dest_form dest.uncertain = src1.uncertain or src2.uncertain if src1.genders and src2.genders and not m_table.deepEquals(src1.genders, src2.genders) then -- do nothing else dest.genders = src1.genders or src2.genders end if src1.pos and src2.pos and src1.pos ~= src2.pos then -- do nothing else dest.pos = src1.pos or src2.pos end -- Don't copy .alt, .gloss, .lit, .id, which describe a single term and don't extend to multiword terms. dest.q = combine_lists(src1.q, src2.q) dest.qq = combine_lists(src1.qq, src2.qq) dest.l = combine_lists(src1.l, src2.l) dest.ll = combine_lists(src1.ll, src2.ll) end -- Externally callable function to parse and conjugate a verb given user-specified arguments. -- Return value is WORD_SPEC, an object where the conjugated forms are in `WORD_SPEC.forms` -- for each slot. If there are no values for a slot, the slot key will be missing. The value -- for a given slot is a list of objects {form=FORM, footnotes=FOOTNOTES}. function export.do_generate_forms(args, source_template, headword_head) local PAGENAME = mw.loadData("Module:headword/data").pagename local function in_template_space() local nsText = mw.title.getCurrentTitle().nsText return nsText == "Template" or nsText == "قالب" end -- Determine the verb spec we're being asked to generate the conjugation of. This may be taken from the current page -- title or the value of |pagename=; but not when called from {{ar-verb form}}, where the page title is a -- non-lemma form. Note that the verb spec may omit the lemma; e.g. it may be "<II>". For this reason, we use the -- value of `pagename` computed here down below, when calling normalize_all_lemmas(). local pagename = source_template ~= "ar-verb form" and args.pagename or PAGENAME local head = headword_head or pagename --W: If both root (جذر) and morphological pattern (وزن) are provided, construct [1] if args["جذر"] and args["جذر"] ~= "" and args["وزن"] and args["وزن"] ~= "" then local constructed = construct_verb_spec(args["جذر"], args["وزن"]) if constructed and constructed ~= "" then args[1] = constructed end end local arg1 = args[1] if not arg1 then if (pagename == "ar-conj" or pagename == "ar-verb" or pagename == "ar-verb form" or pagename == "تصريف") and in_template_space() then arg1 = "كتب<I/a~u.pass>" else arg1 = "<>" end end -- When called from {{ar-verb form}}, determine the non-lemma form whose inflections we're being asked to -- determine. This normally comes from the page title or the value of |pagename=. local verb_form_of_form if source_template == "ar-verb form" then verb_form_of_form = args.pagename if not verb_form_of_form then if PAGENAME == "ar-verb form" and in_template_space() then verb_form_of_form = "كتبت" else verb_form_of_form = PAGENAME end end end local incorporated_headword_head_into_lemma = false if arg1:find("^<.*>$") then -- missing lemma if head:find(" ") then -- If multiword lemma, try to add arg spec after the first word. -- Try to preserve the brackets in the part after the verb, but don't do it -- if there aren't the same number of left and right brackets in the verb -- (which means the verb was linked as part of a larger expression). local first_word, post = rmatch(head, "^(.-)( .*)$") local left_brackets = rsub(first_word, "[^%[]", "") local right_brackets = rsub(first_word, "[^%]]", "") if #left_brackets == #right_brackets then arg1 = iut.remove_redundant_links(first_word) .. arg1 .. post incorporated_headword_head_into_lemma = true else -- Try again using the form without links. local linkless_head = m_links.remove_links(head) if linkless_head:find(" ") then first_word, post = rmatch(linkless_head, "^(.-)( .*)$") arg1 = first_word .. arg1 .. post else error("Unable to incorporate <...> spec into explicit head due to a multiword linked verb or " .. "unbalanced brackets; please include <> explicitly: " .. arg1) end end else -- Will be incorporated through `head` below in the call to normalize_all_lemmas(). incorporated_headword_head_into_lemma = true end end local parse_props = { parse_indicator_spec = parse_indicator_spec, angle_brackets_omittable = true, allow_blank_lemma = true, } local alternant_multiword_spec = iut.parse_inflected_text(arg1, parse_props) alternant_multiword_spec.pos = pos or "verbs" alternant_multiword_spec.args = args alternant_multiword_spec.source_template = source_template alternant_multiword_spec.verb_form_of_form = verb_form_of_form alternant_multiword_spec.incorporated_headword_head_into_lemma = incorporated_headword_head_into_lemma normalize_all_lemmas(alternant_multiword_spec, head) detect_all_indicator_specs(alternant_multiword_spec) local inflect_props = { lang = lang, slot_list = alternant_multiword_spec.verb_slots, inflect_word_spec = conjugate_verb, combine_metadata = combine_metadata, -- We add links around the generated verbal forms rather than allow the entire multiword -- expression to be a link, so ensure that user-specified links get included as well. include_user_specified_links = true, } iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props) if debug_translit then for slot, forms in pairs(alternant_multiword_spec.forms) do for _, form in ipairs(forms) do if form.translit then local full_form_translit = (lang:transliterate(m_links.remove_links(form.form))) if full_form_translit ~= form.translit then error(("Internal error: For slot '%s', form '%s' incremental translit '%s' not same as full translit '%s'"): format(slot, form.form, form.translit, full_form_translit)) end end form.form = iut.remove_redundant_links(form.form) end end end -- Remove redundant brackets around entire forms. for slot, forms in pairs(alternant_multiword_spec.forms) do for _, form in ipairs(forms) do form.form = iut.remove_redundant_links(form.form) end end -- Apply NFC-style normalization: reorder shadda+vowel to vowel+shadda -- to match MediaWiki's automatic NFC normalization (which is applied on enwiktionary -- but not in local Lua environments). This reverses the postprocessing step. for slot, forms in pairs(alternant_multiword_spec.forms) do for _, form in ipairs(forms) do form.form = apply_nfc_shadda(form.form) end end determine_slot_uncertainty_from_forms(alternant_multiword_spec) determine_verb_properties_from_forms(alternant_multiword_spec) compute_categories_and_annotation(alternant_multiword_spec) if args.json and source_template == "ar-conj" then -- There is a circular reference in `base.alternant_multiword_spec`, which points back to top level. iut.map_word_specs(alternant_multiword_spec, function(base) base.alternant_multiword_spec = nil end) return require("Module:JSON").toJSON(alternant_multiword_spec) end return alternant_multiword_spec end -- Entry point for {{ar-conj}}. Template-callable function to parse and conjugate a verb given -- user-specified arguments and generate a displayable table of the conjugated forms. function export.show(frame) local parent_args = frame:getParent() and frame:getParent().args or frame.args --W local params = { [1] = {}, ["noautolinktext"] = {type = "boolean"}, ["noautolinkverb"] = {type = "boolean"}, ["t"] = {}, -- for use by {{ar-verb form}}; otherwise ignored ["id"] = {}, -- for use by {{ar-verb form}}; otherwise ignored ["pagename"] = {}, -- for testing/documentation pages ["json"] = {type = "boolean"}, -- for bot use --W: additional parameters for ar.wiktionary.org ["جذر"] = {type = "string"}, --W: الجذر اللغوي, root ["وزن"] = {type = "string"}, --W: الوزن الصرفي, morphological pattern ["كامل"] = {type = "boolean"}, --W: show full table (default is simplified table) } local args = require("Module:parameters").process(parent_args, params) --W: If both root (جذر) and morphological pattern (وزن) are provided, construct [1] if args["جذر"] and args["جذر"] ~= "" and args["وزن"] and args["وزن"] ~= "" then local constructed = construct_verb_spec(args["جذر"], args["وزن"]) if constructed and constructed ~= "" then args[1] = constructed end end local alternant_multiword_spec = export.do_generate_forms(args, "ar-conj") if type(alternant_multiword_spec) == "string" then -- JSON return value return alternant_multiword_spec end show_forms(alternant_multiword_spec) --W: Choose table type: simplified (default) or full (comprehensive) local table_html if args["كامل"] then table_html = make_table(alternant_multiword_spec) else table_html = make_table_ar(alternant_multiword_spec) end --W: don't add categories --W: TODO: all lines with insert_cat() are to be removed. -- return table_html .. -- require("Module:utilities").format_categories(alternant_multiword_spec.categories, lang, nil, nil, force_cat) return table_html end function export.verb_forms(frame) local parargs = frame:getParent().args local params = { [1] = {}, [2] = {}, [3] = {}, [4] = {}, [5] = {}, pagename = {}, } for _, form in ipairs(allowed_vforms) do -- FIXME: We go up to 5 here. The code supports unlimited variants but it's unlikely we will ever see more than -- 2. for index = 1, 5 do local prefix = index == 1 and form or form .. index params[prefix .. "-pv"] = {} for _, extn in ipairs { "", "-vn", "-ap", "-pp" } do params[prefix .. extn] = {} params[prefix .. extn .. "-head"] = {} -- FIXME: No -tr? params[prefix .. extn .. "-gloss"] = {} end end end local args = require("Module:parameters").process(parargs, params) local i = 1 local past_vowel_re = "^[aui,]*$" local combined_root = nil if not args[i] or rfind(args[i], past_vowel_re) then combined_root = args.pagename or mw.loadData("Module:headword/data").pagename if not rfind(combined_root, "^([^ ]) ([^ ]) ([^ ])$") and not rfind(combined_root, "^([^ ]) ([^ ]) ([^ ]) ([^ ])$") then error("When inferring roots from page title, need three or four space-separated radicals: " .. combined_root) end elseif rfind(args[i], " ") then combined_root = args[i] i = i + 1 else local separate_roots = {} while args[i] and not rfind(args[i], past_vowel_re) do table.insert(separate_roots, args[i]) i = i + 1 end combined_root = table.concat(separate_roots, " ") end local past_vowel = args[i] i = i + 1 if past_vowel and not rfind(past_vowel, past_vowel_re) then error("Unrecognized past vowel, should be 'a', 'i', 'u', 'a,u', etc. or empty: " .. past_vowel) end -- Spaces interfere with parsing as a unit in [[Module:inflection utilities]], so replace with underscore. combined_root = combined_root:gsub(" ", "_") local split_root = rsplit(combined_root, "_") -- Map from verb forms (I, II, etc.) to a table of verb properties, -- which has entries e.g. for "verb" (either true to autogenerate the verb -- head, or an explicitly specified verb head using e.g. argument "I-head"), -- and for "verb-gloss" (which comes from e.g. the argument "I" or "I-gloss"), -- and for "vn" and "vn-gloss", "ap" and "ap-gloss", "pp" and "pp-gloss". local verb_properties = {} for _, form in ipairs(allowed_vforms) do local formpropslist = {} local derivs = {{"verb", ""}, {"vn", "-vn"}, {"ap", "-ap"}, {"pp", "-pp"}} local index = 1 while true do local formprops = {} local prefix = index == 1 and form or form .. index if prefix == "I" then formprops.pv = past_vowel end if args[prefix .. "-pv"] then formprops.pv = args[prefix .. "-pv"] end for _, deriv in ipairs(derivs) do local prop = deriv[1] local extn = deriv[2] if args[prefix .. extn] == "+" then formprops[prop] = true elseif args[prefix .. extn] == "-" then formprops[prop] = false elseif args[prefix .. extn] then formprops[prop] = true formprops[prop .. "-gloss"] = args[prefix .. extn] end if args[prefix .. extn .. "-head"] then if formprops[prop] == nil then formprops[prop] = true end formprops[prop] = args[prefix .. extn .. "-head"] end if args[prefix .. extn .. "-gloss"] then if formprops[prop] == nil then formprops[prop] = true end formprops[prop .. "-gloss"] = args[prefix .. extn .. "-gloss"] end end if formprops.verb then -- If a verb form specified, also turn on vn (unless form I, with -- unpredictable vn) and ap, and maybe pp, according to form, -- weakness and past vowel. But don't turn these on if there's -- an explicit on/off specification for them (e.g. I-pp=-). if form ~= "I" and formprops.vn == nil then formprops.vn = true end if formprops.ap == nil then formprops.ap = true end local weakness = weakness_from_radicals(form, split_root[1], split_root[2], split_root[3], split_root[4]) if formprops.pp == nil and not vform_probably_no_passive(form, weakness, rsplit(formprops.pv or "", ","), {}) then formprops.pp = true end if formprops.verb == true or formprops.vn == true or formprops.ap == true or formprops.pp == true then formprops.need_autogen = true end table.insert(formpropslist, formprops) index = index + 1 else break end end table.insert(verb_properties, {form, formpropslist}) end -- Go through and create the verb form derivations as necessary, when they haven't been explicitly given. for _, vplist in ipairs(verb_properties) do local vform = vplist[1] for _, props in ipairs(vplist[2]) do if props.need_autogen then local form_with_vowels if vform == "I" then local pv = props.pv if not pv then -- Make up likely past vowels based on weakness and actual radical. if split_root[3] == W then -- final-weak form_with_vowels = "I/a~u" elseif split_root[3] == Y then form_with_vowels = "I/a~i" elseif split_root[2] == W then --hollow form_with_vowels = "I/u~u" elseif split_root[2] == Y then form_with_vowels = "I/i~i" else -- most common; doesn't matter so much since we're not displaying the non-past form_with_vowels = "I/a~u" end else local pvs = rsplit(pv, ",") local vowel_sufs = {} for _, pv in ipairs(pvs) do local vowel_spec if pv == "a" then -- Make up likely past vowels based on weakness and actual radical. if split_root[3] == W then -- final-weak vowel_spec = "a~u" elseif split_root[3] == Y then vowel_spec = "a~i" elseif split_root[2] == W then --hollow vowel_spec = "a~u" elseif split_root[2] == Y then vowel_spec = "a~i" else -- most common; doesn't matter so much since we're not displaying the non-past vowel_spec = "a~u" end elseif pv == "i" then -- most common; doesn't matter so much since we're not displaying the non-past vowel_spec = "i~a" elseif pv == "u" then -- most common; doesn't matter so much since we're not displaying the non-past vowel_spec = "u~u" else error(("Internal error: Bad past vowel '%s' in {{ar-verb forms}}"):format(pv)) end table.insert(vowel_sufs, vowel_spec) end form_with_vowels = "I/" .. table.concat(vowel_sufs, "/") end else form_with_vowels = vform end local angle_bracket_spec = ("%s<%s.pass>"):format(combined_root, form_with_vowels) local alternant_multiword_spec = export.do_generate_forms({angle_bracket_spec}, "ar-verb forms") local function format_forms(forms) if not forms then return "-" -- FIXME: Throw an error? end local formatted = {} for _, form in ipairs(forms) do if form.translit then table.insert(formatted, ("%s//%s"):format(form.form, form.translit)) else table.insert(formatted, form.form) end end return table.concat(formatted, "،") end if props.verb == true then props.verb = format_forms(alternant_multiword_spec.forms.past_3ms) end for _, deriv in ipairs({"vn", "ap", "pp"}) do if props[deriv] == true then props[deriv] = format_forms(alternant_multiword_spec.forms[deriv]) end end end end end -- Go through and output the result local formtextarr = {} for _, vplist in ipairs(verb_properties) do local form = vplist[1] for _, props in ipairs(vplist[2]) do local textarr = {} if props.verb then local text = "* '''[[Appendix:Arabic verbs#Form " .. form .. "|Form " .. form .. "]]''': " local linktext = {} local splitheads = rsplit(props.verb, "[,،]") for _, head in ipairs(splitheads) do table.insert(linktext, m_links.full_link({lang = lang, term = head, gloss = props["verb-gloss"]})) end text = text .. table.concat(linktext, "، ") table.insert(textarr, text) for _, derivengl in ipairs({{"vn", "Verbal noun"}, {"ap", "Active participle"}, {"pp", "Passive participle"}}) do local deriv = derivengl[1] local engl = derivengl[2] if props[deriv] then local text = "** " .. engl .. ": " local linktext = {} local splitheads = rsplit(props[deriv], "[,،]") for _, head in ipairs(splitheads) do local ar, translit = head:match("^(.*)//(.-)$") if not ar then ar = head end table.insert(linktext, m_links.full_link {lang = lang, term = ar, tr = translit, gloss = props[deriv .. "-gloss"]} ) end text = text .. table.concat(linktext, "، ") table.insert(textarr, text) end end table.insert(formtextarr, table.concat(textarr, "\n")) end end end return table.concat(formtextarr, "\n") end -- Infer radicals from lemma headword (i.e. 3rd masculine singular past) and verb form (I, II, etc.). Throw an error if -- headword is malformed. A given returned radical may be actually be a list of possible radicals, where the first one -- should be used if the user didn't explicitly give the radical. If the list contains a field `ambig = true`, the -- radical is considered ambiguous and should not be categorized. `is_reduced` indicates that the user specified -- `.reduced` to indicate that the verb form is reduced by assimilation and/or haplology (typically archaic Koranic -- forms such as اِدَّارَأَ instead of تَدَارَأَ; or اِسْطَاعَ instead of اِسْتِطَاعَ; etc. function export.infer_radicals(data) local headword, vform, passive, past_vowel, nonpast_vowel, is_reduced = data.headword, data.vform, data.passive, data.past_vowel, data.nonpast_vowel, data.is_reduced past_vowel = past_vowel or "-" nonpast_vowel = nonpast_vowel or "-" local function verify_vowel(vowel, param) --W: A past/non-past vowel may legitimately be a form object {form=..., footnotes=...} -- when the user attaches a footnote, e.g. <I/a[نادر]~u>, so compare the formval. -- Comparing the raw value crashed on Lua 5.1 ("bad argument #2 to 'format'"). local formval = rget(vowel) if formval ~= A and formval ~= I and formval ~= U and formval ~= "-" then error(("Internal error: Bad value for %s: %s (should be Arabic diacritic vowel or '-')"):format( param, dump(vowel))) end end verify_vowel(past_vowel, "past_vowel") verify_vowel(nonpast_vowel, "nonpast_vowel") local ch = {} local form_viii_assim, variant -- sub out alif-madda for easier processing headword = rsub(headword, AMAD, HAMZA .. ALIF) local function infer_err(msg, noann) local anns = {} local nohead, novform if noann == "nohead" then nohead = true elseif noann == "novform" then novform = true elseif noann == "nohead-vform" then nohead = true novform = true elseif noann then error(("Internal error: Unrecognized value for 'noann': %s"):format(dump(noann))) end if not nohead then table.insert(anns, ("headword=%s"):format(data.headword)) end if not novform then table.insert(anns, ("verb form=%s"):format(data.vform)) end anns = table.concat(anns, "، ") if anns ~= "" then anns = ": " .. anns end error(msg .. anns) end local len = ulen(headword) local expected_length -- extract the headword letters into an array for i = 1, len do table.insert(ch, usub(headword, i, i)) end -- check that the letter at the given index is the given string, or -- is one of the members of the given array local function check(index, must) local letter = ch[index] if type(must) == "string" then if not letter then infer_err("Letter " .. index .. " is nil") end if letter ~= must then infer_err(("For verb form %s, letter %s must be %s, not %s"):format(vform, index, must, letter), "novform") end elseif not m_table.contains(must, letter) then infer_err("For verb form " .. vform .. ", radical " .. index .. " must be one of " .. table.concat(must, " ") .. ", not " .. letter, "novform") end end -- Check that length of headword is within [min, max] local function check_len(min, max) if min and len < min then infer_err(("Not enough letters for verb form %s, expected at least %s"):format(vform, min), "novform") end if max and len > max then infer_err(("Too many letters for verb form %s, expected at most %s"):format(vform, max), "novform") end end -- If the vowels are i~a or u~u, a form I verb beginning with w- normally keeps the w in the non-past. Otherwise it -- loses it (i.e. it is "assimilated"). local function form_I_w_non_assimilated() return req(past_vowel, I) and req(nonpast_vowel, A) or req(past_vowel, U) and req(nonpast_vowel, U) end -- Convert radicals to canonical form (handle various hamza varieties and check for misplaced alif or alif maqṣūra; -- legitimate cases of these letters are handled above). local function convert(rad, index) if type(rad) == "table" then for i, r in ipairs(rad) do rad[i] = convert(r, index) end return rad elseif rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or rad == HAMZA_ON_W or rad == HAMZA_ON_Y then return HAMZA elseif rad == AMAQ then infer_err("Radical " .. index .. " must not be alif maqṣūra") elseif rad == ALIF then infer_err("Radical " .. index .. " must not be alif") else return rad end end local quadlit = vform:find("q$") -- find first radical, start of second/third radicals, check for -- required letters local radstart, rad1, rad2, rad3, rad4 local weakness if vform == "I" or vform == "II" then rad1 = ch[1] radstart = 2 elseif vform == "III" then rad1 = ch[1] check(2, {ALIF, W}) -- W occurs in passive-only verbs radstart = 3 elseif vform == "IV" then -- this would be alif-madda but we replaced it with hamza-alif above. if ch[1] == HAMZA and ch[2] == ALIF then rad1 = HAMZA else check(1, HAMZA_ON_ALIF) rad1 = ch[2] end radstart = 3 elseif vform == "V" then check(1, is_reduced and ALIF or T) rad1 = ch[2] radstart = 3 elseif vform == "VI" then check(1, is_reduced and ALIF or T) if ch[2] == AMAD then rad1 = HAMZA radstart = 3 else rad1 = ch[2] check(3, {ALIF, W}) -- W occurs in passive-only verbs radstart = 4 end elseif vform == "VII" then check(1, ALIF) if is_reduced then check(2, M) rad1 = M radstart = 3 else check(2, N) rad1 = ch[3] radstart = 4 end elseif vform == "VIII" then check(1, ALIF) rad1 = ch[2] if rad1 == "د" then rad1 = {"د", "ذ"} -- not considered ambiguous since it's usually د radstart = 3 form_viii_assim = "دّ" elseif rad1 == "ظ" and ch[3] == "ط" and len >= 5 then -- [[اظطلم]], variant of [[اظلم]] radstart = 4 form_viii_assim = "ظْط" elseif rad1 == "ذ" and ch[3] == "د" and len >= 5 then -- [[اذدكر]], variant of [[اذكر]] radstart = 4 form_viii_assim = "ذْد" elseif rad1 == T or rad1 == "ث" or rad1 == "ذ" or rad1 == "ط" or rad1 == "ظ" then radstart = 3 form_viii_assim = rad1 .. SH elseif rad1 == "ز" then check(3, "د") radstart = 4 form_viii_assim = "زْد" elseif rad1 == "ص" or rad1 == "ض" then check(3, "ط") radstart = 4 form_viii_assim = rad1 .. SK .. "ط" else check(3, T) radstart = 4 rad1 = convert(rad1, 1) form_viii_assim = rad1 .. SK .. "ت" end if rad1 == T then -- Radical is ambiguous, might be ت or و or ي but doesn't affect conjugation. Note that there are no -- form-VIII verbs with initial radical ي given in Hans Wehr but Lane mentions at least: -- - (page 2973) اِتَّأَسَ, with assimilation of the ي to ت, from root ي ء س; -- - (page 2975) اِتَّبَسَ non-past يَتَّبِسُ and alternative اِيتَبَسَ non-past يَاتَبِسُ from the root ي ب س; -- - (page 2976) اِتَّسَرَ non-past يَتَّسِرُ or alternatively يَأْتَسِرُ with hamza preserved from the root ي س ر. -- These alternative forms seem very rare and probably not worth worrying about, but if we want to handle -- them, we can do it when the time comes. rad1 = {T, W, Y, ambig = true} -- اِتَّخَذَ irregularly has hamza as the radical but assimilates like و if ch[3] == "خ" and ch[4] == "ذ" then rad1[4] = HAMZA end end elseif vform == "IX" then check(1, ALIF) rad1 = ch[2] radstart = 3 elseif vform == "X" then check(1, ALIF) check(2, S) if is_reduced then rad1 = ch[3] radstart = 4 else check(3, T) rad1 = ch[4] radstart = 5 end elseif vform == "Iq" then rad1 = ch[1] rad2 = ch[2] radstart = 3 elseif vform == "IIq" then check(1, T) rad1 = ch[2] rad2 = ch[3] radstart = 4 elseif vform == "IIIq" then check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, N) radstart = 5 elseif vform == "IVq" then check(1, ALIF) rad1 = ch[2] rad2 = ch[3] radstart = 4 elseif vform == "XI" then check_len(5, 5) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, ALIF) rad3 = ch[5] weakness = "sound" elseif vform == "XII" then check(1, ALIF) rad1 = ch[2] if ch[3] ~= ch[5] then infer_err("For verb form XII, letters 3 and 5 should be the same", "novform") end check(4, W) radstart = 5 elseif vform == "XIII" then check_len(5, 5) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, W) rad3 = ch[5] if rad3 == AMAQ then weakness = "final-weak" else weakness = "sound" end elseif vform == "XIV" then check_len(6, 6) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, N) rad3 = ch[5] if ch[6] == AMAQ then check_waw_ya(rad3) weakness = "final-weak" else if ch[5] ~= ch[6] then infer_err("For verb form XIV, letters 5 and 6 should be the same", "novform") end weakness = "sound" end elseif vform == "XV" then check_len(6, 6) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, N) rad3 = ch[5] if rad3 == Y then check(6, ALIF) else check(6, AMAQ) end weakness = "sound" else error("Internal error: Unrecognized verb form " .. vform) end -- Process the last two radicals. RADSTART is the index of the first of the two. If it's nil then all radicals have -- already been processed above, and we don't do anything. if radstart then -- There must (normally) be one or two letters left. if len == radstart then if vform == "I" and ch[len] == Y then -- short form حَيَّ weakness = "final-weak" rad2 = Y rad3 = Y variant = "short" elseif vform == "IV" and rad1 == "ر" and ch[len] == AMAQ then -- irregular verb أَرَى weakness = "final-weak" rad2 = HAMZA rad3 = Y elseif vform == "X" and rad1 == "ح" and ch[len] == AMAQ then -- irregular verb اِسْتَحَى weakness = "final-weak" rad2 = Y rad3 = Y variant = "short" else -- If one letter left, then it's a geminate verb. If the letter is alif or alif maqṣūra, it will trigger -- an error down the line. if vform_supports_geminate(vform) then weakness = "geminate" rad2 = ch[len] rad3 = ch[len] if vform == "III" or vform == "VI" then variant = "short" end else infer_err("Apparent geminate verb, but geminate verbs not allowed for this verb form") end end elseif quadlit then -- Process last two radicals of a quadriliteral verb form. rad3 = ch[radstart] rad4 = ch[radstart + 1] expected_length = radstart + 1 check_len(expected_length) if rad4 == AMAQ or rad4 == ALIF and rad3 == Y or rad4 == Y then -- rad4 can be Y in passive-only verbs. if vform_supports_final_weak(vform) then weakness = "final-weak" -- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws in a row); it could be wāw or -- yāʾ, but doesn't affect the conjugation. rad4 = rad3 == W and {Y, W, ambig = true} or {W, Y, ambig = true} else infer_err("Last radical is " .. rad4 .. " but verb form " .. vform .. " doesn't support final-weak verbs", "novform") end else weakness = "sound" end else -- Process last two radicals of a triliteral verb form. rad2 = ch[radstart] rad3 = ch[radstart + 1] expected_length = radstart + 1 check_len(expected_length) if vform == "I" and (is_waw_ya(rad3) or rad3 == ALIF or rad3 == AMAQ) then local inferred_past_vowel, inferred_nonpast_vowel -- Check for final-weak form I verb. It can end in tall alif (rad3 = wāw) or alif maqṣūra (rad3 = yāʾ) -- or a wāw or yāʾ (with a past vowel of i or u, e.g. nasiya/yansā "forget" or with a passive-only -- verb). if rad1 == W and not form_I_w_non_assimilated() then weakness = "assimilated+final-weak" else weakness = "final-weak" end if rad3 == ALIF then rad3 = W inferred_past_vowel = A inferred_nonpast_vowel = U if is_passive_only(passive) then infer_err("Final-weak form-I passive verbs should end in yāʔ (ي), not tall alif (ا)", "novform") end elseif rad3 == AMAQ then rad3 = Y inferred_past_vowel = A inferred_nonpast_vowel = I if is_passive_only(passive) then infer_err("Final-weak form-I passive verbs should end in yāʔ (ي), not alif maqṣūra (ى)", "novform") end elseif rad1 == "ح" and rad2 == Y and rad3 == Y then -- Long variant حَيِيَ. inferred_past_vowel = I inferred_nonpast_vowel = A variant = "long" else if not is_passive_only(passive) then -- does a non-passive final-weak verb in -uwa ever happen? (YES: e.g. [[رجو]] "to be slack") inferred_past_vowel = rad3 == Y and I or U inferred_nonpast_vowel = A end -- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws); it could be wāw or yāʾ, but -- doesn't affect the conjugation. rad3 = (rad1 == W or rad2 == W) and {Y, W, ambig = true} or {W, Y, ambig = true} -- ambiguous end if inferred_past_vowel then local raw_past_vowel = rget(past_vowel) local raw_nonpast_vowel = rget(nonpast_vowel) if raw_past_vowel ~= "-" then if raw_past_vowel ~= inferred_past_vowel then infer_err(("Final-weak form-I verb inferred past vowel %s, which disagrees with " .. "explicitly specified %s"):format(undia[inferred_past_vowel], undia[raw_past_vowel]), "novform") else -- in case of footnote in past_vowel inferred_past_vowel = past_vowel end end if raw_nonpast_vowel ~= "-" and raw_nonpast_vowel ~= A and inferred_nonpast_vowel == U then -- if inferred as I or A, the reality can be the reverse; form-I final-weak verbs with a~a and -- i~i exist, e.g. سَعَى/يَسْعَى, وَلِيَ/يَلِي. Weird verb [[صها]] (also written [[صهى]]) has non-past -- يصهى so we can't throw an error in this situation. if raw_nonpast_vowel ~= inferred_nonpast_vowel then infer_err(("Final-weak form-I verb inferred non-past vowel %s, which disagrees with " .. "explicitly specified %s"):format(undia[inferred_nonpast_vowel], undia[raw_nonpast_vowel]), "novform") else -- in case of footnote in nonpast_vowel inferred_nonpast_vowel = nonpast_vowel end end end if not is_passive_only(passive) then if rget(past_vowel) == "-" then past_vowel = inferred_past_vowel end if rget(nonpast_vowel) == "-" then nonpast_vowel = inferred_nonpast_vowel end end elseif vform == "IX" and is_waw_ya(rad3) and len == radstart + 2 and ch[len] == AMAQ then -- Final-weak form IX verbs like اِرْعَوَى "to desist, to repent, to see the light". weakness = "final-weak" expected_length = radstart + 2 elseif vform == "X" and rad1 == "ح" and rad2 == Y and rad3 == ALIF then -- Long variant اِسْتَحْيَا. weakness = "final-weak" rad3 = Y variant = "long" elseif rad3 == AMAQ or rad2 == Y and rad3 == ALIF or rad3 == Y then -- rad3 == Y happens in passive-only verbs. if vform_supports_final_weak(vform) then weakness = "final-weak" else infer_err("Last radical is " .. rad3 .. " but verb form doesn't support final-weak verbs") end -- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws); it could be wāw or yāʾ, but -- doesn't affect the conjugation. rad3 = (rad1 == W or rad2 == W) and {Y, W, ambig = true} or {W, Y, ambig = true} elseif rad2 == ALIF then if vform_supports_hollow(vform) then weakness = "hollow" local function set_past_to_a() if req(past_vowel, A) then -- already set elseif req(past_vowel, "-") or req(past_vowel, rget(nonpast_vowel)) then past_vowel = A else infer_err(("Form I hollow verb with nonpast vowel set to '%s' must have past vowel set to 'a' or the same value, not %s"): format(undia[rget(nonpast_vowel)], undia[rget(past_vowel)]), "novform") end end if vform == "I" and req(nonpast_vowel, U) then rad2 = W set_past_to_a() elseif vform == "I" and req(nonpast_vowel, I) then rad2 = Y set_past_to_a() else if req(nonpast_vowel, A) and not req(past_vowel, I) then infer_err(("Form I hollow verb with nonpast vowel set to 'a' must have past vowel set to 'i', not %s"): format(undia[rget(past_vowel)]), "novform") end -- Ambiguous radical; could be wāw or yāʾ; if verb form I, it's critical to get this right, and -- the caller checks for this situation and throws an error if non-past vowel is "a" and second -- radical isn't explicitly given. rad2 = {W, Y, ambig = true, need_radical = true} end else infer_err("Second radical is alif but verb form doesn't support hollow verbs") end elseif vform == "I" and rad1 == W and not form_I_w_non_assimilated() then weakness = "assimilated" elseif rad2 == rad3 and (vform == "III" or vform == "VI") then weakness = "geminate" variant = "long" else weakness = "sound" end end if expected_length then check_len(expected_length, expected_length) end end rad1 = convert(rad1, 1) rad2 = convert(rad2, 2) rad3 = convert(rad3, 3) rad4 = convert(rad4, 4) if not weakness then error("Internal error: Returned weakness from infer_radicals() is nil") end return { weakness = weakness, rad1 = rad1, rad2 = rad2, rad3 = rad3, rad4 = rad4, past_vowel = past_vowel, nonpast_vowel = nonpast_vowel, form_viii_assim = form_viii_assim, variant = variant, } end -- bot interface to infer_radicals() function export.infer_radicals_json(frame) local iparams = { headword = {}, vform = {}, passive = {}, past_vowel = {}, nonpast_vowel = {}, is_reduced = {type = "boolean"}, } local iargs = require("Module:parameters").process(frame.args, iparams) return require("Module:JSON").toJSON(export.infer_radicals(iargs)) end -- Infer vocalization from participle headword (active or passive), verb form (I, II, etc.) and whether the headword is -- active or passive. Throw an error if headword is malformed. Returned radicals may contain Latin letters "t", "w" or "y" -- indicating ambiguous radicals guessed to be tāʾ, wāw or yāʾ respectively. function export.infer_participle_vocalization(headword, vform, weakness, is_active) local chars = {} local orig_headword = headword -- Sub out alif-madda for easier processing. headword = rsub(headword, AMAD, HAMZA .. ALIF) local len = ulen(headword) -- Extract the headword letters into an array. for i = 1, len do table.insert(chars, usub(headword, i, i)) end local function form_intro_error_msg() return ("For verb form %s %s%s participle %s, "):format(vform, orig_headword ~= headword and "normalized " or "", is_active and "active" or "passive", headword) end local function err(msg) error(form_intro_error_msg() .. msg, 1) end -- Check that length of headword is within [min, max]. local function check_len(min, max) if min and len < min then err(("expected at least %s letters but saw %s"):format(min, len)) elseif max and len > max then err(("expected at most %s letters but saw %s"):format(max, len)) end end -- Get the character at `ind`, making sure it exists. local function c(ind) check_len(ind) return chars[ind] end -- Check that the letter at the given index is the given string, or is one of the members of the given array local function check(index, must) local letter = chars[index] local function make_possible_values() if type(must) == "string" then return must else return list_to_text(must, nil, " or ") end end if not letter then err(("expected a letter (specifically %s) at position %s, but participle is too short"):format( make_possible_values(), index)) end local matches if type(must) == "string" then matches = letter == must else matches = m_table.contains(must, letter) end if not matches then err(("letter %s at index %s must be %s"):format(letter, index, make_possible_values())) end end local function check_weakness(values, allow_missing, invert_condition) local function make_possible_weaknesses() for i, val in ipairs(values) do values[i] = "'" .. val .. "'" end return list_to_text(values, nil, " or ") end if allow_missing and invert_condition then error("Internal error: Can't specify both allow_missing and invert_condition") end if not weakness then if allow_missing or invert_condition then return else err(("weakness is unspecified but must be %s"):format(make_possible_weaknesses())) end else local matches = m_table.contains(values, weakness) if invert_condition and matches then err(("weakness '%s' must not be %s"):format(weakness, make_possible_weaknesses())) elseif not invert_condition and not matches then err(("weakness '%s' must be %s"):format(weakness, make_possible_weaknesses())) end end end local vocalized local function handle_possibly_final_weak(sound_prefix, expected_length) check_len(expected_length, expected_length) if c(expected_length) == AMAQ then -- passive final-weak if is_active then err("participle in -ِى only allowed for passive participles") end check_weakness({"final-weak", "assimilated+final-weak"}, "allow missing") vocalized = sound_prefix .. AN .. AMAQ else -- all others behave as if sound check_weakness({"final-weak", "assimilated+final-weak"}, nil, "invert condition") vocalized = sound_prefix .. (is_active and I or A) .. c(expected_length) end end if not (vform == "I" and is_active) then -- all participles except verb form I active begin in م-. check(1, M) end if vform == "I" then if is_active then check(2, ALIF) local sound_prefix = c(1) .. AA .. c(3) if len == 3 then if c(3) == HAMZA then -- Either hollow with hamzated third radical, e.g. [[شاء]] active participle 'شَاءٍ', or final-weak -- with hamzated second radical, e.g. [[رأى]] active participle 'رَاءٍ'. Theoretically (?), also -- geminate with hamzated second/third radical, but I don't know if any such verbs exist. if weakness == "geminate" then vocalized = sound_prefix .. SH else check_weakness({"hollow", "final-weak"}, "allow missing") vocalized = sound_prefix .. IN end else check_weakness({"final-weak", "geminate"}) if weakness == "geminate" then vocalized = sound_prefix .. SH else vocalized = sound_prefix .. IN end end else check_len(4, 4) -- we will convert back to alif maqṣūra below as needed vocalized = sound_prefix .. I .. c(4) end else -- assimilated verbs: regular, e.g. مَوْزُون "weighed" -- geminate verbs: regular, e.g. مَبْلُول "moistened" -- third-hamzated verbs: مَبْرُوء -- hollow verbs: مَقُود "led, driven"; مَزِيد "added, increased" -- hollow first-hamzated verbs: مَئِيض "returned, reverted"; مَأْيُوس "despaired" (NOTE: formation is sound); -- مَأُود or مَؤُود "bent; depleted" -- hollow third-hamzated verbs: مَشِيء "willed, intended", مَضُوء "glittered?" -- final-weak: مَلْقِيّ "found, encountered"; مَصْغُوّ "inclined" -- hollow + final-weak: مَشْوِيّ "fried, grilled", مَهْوِيّ "loved" -- first-hamzated + hollow + final-weak: مَأْوِيّ "received hospitably" local sound_prefix = MA .. c(2) .. SK .. c(3) if len == 5 then -- sound, assimilated or geminate check(4, W) vocalized = sound_prefix .. UU .. c(5) else check_len(4, 4) if c(4) == W then -- final-weak third-wāw vocalized = sound_prefix .. U .. W .. SH elseif c(4) == Y then -- final-weak third-yāʾ vocalized = sound_prefix .. I .. Y .. SH else -- hollow check(3, {W, Y}) if c(3) == W then vocalized = MA .. c(2) .. UU .. c(4) else vocalized = MA .. c(2) .. II .. c(4) end end end end elseif vform == "II" or vform == "V" or vform == "XII" or vform == "XIII" or vform == "Iq" or vform == "IIq" or vform == "IIIq" then local sound_prefix, expected_length if vform == "II" then sound_prefix = MU .. c(2) .. A .. c(3) .. SH expected_length = 4 elseif vform == "V" then check(2, T) sound_prefix = MU .. T .. A .. c(3) .. A .. c(4) .. SH expected_length = 5 elseif vform == "XII" then -- e.g. [[احدودب]] "to be or become convex or humpbacked", مُحْدَوْدِب (active); -- [[اثنونى]] "to be bent; to be doubled up", مُثْنَوْنٍ (active) check(4, W) if c(3) ~= c(5) then err(("third letter %s should be the same as the fifth letter %s"):format(c(3), c(5))) end sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. W .. SK .. c(5) expected_length = 6 elseif vform == "XIII" then -- e.g. [[اخروط]] "to get entangled; to extend", مُخْرَوِّط (active), مُخْرَوَّط (passive) check(4, W) sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. W .. SH expected_length = 5 elseif vform == "Iq" then sound_prefix = MU .. c(2) .. A .. c(3) .. SK .. c(4) expected_length = 5 elseif vform == "IIq" then check(2, T) sound_prefix = MU .. T .. A .. c(3) .. A .. c(4) .. SK .. c(5) expected_length = 6 elseif vform == "IIIq" then -- e.g. [[اخرنطم]] "to be proud and angry" check(4, T) sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. N .. SK .. c(5) expected_length = 6 else error("Internal error: Unhandled verb form " .. vform) end if len == expected_length - 1 then -- active final-weak if not is_active then err(("length-%s participle only allowed for active participles"):format(len)) end check_weakness({"final-weak", "assimilated+final-weak"}, "allow missing") vocalized = sound_prefix .. IN else handle_possibly_final_weak(sound_prefix, expected_length) end elseif vform == "III" or vform == "VI" then local sound_prefix, expected_length if vform == "VI" then check(2, T) check(4, ALIF) sound_prefix = MU .. T .. A .. c(3) .. AA .. c(5) expected_length = 6 else sound_prefix = MU .. c(2) .. AA .. c(4) expected_length = 5 end if len == expected_length - 1 then -- active final-weak or active or passive geminate if is_active then check_weakness({"geminate", "final-weak", "assimilated+final-weak"}) if weakness == "geminate" then vocalized = sound_prefix .. SH else vocalized = sound_prefix .. IN end else check_weakness({"geminate"}, "allow missing") vocalized = sound_prefix .. SH end else handle_possibly_final_weak(sound_prefix, expected_length) end elseif vform == "IV" or vform == "X" then -- form IV: -- sound: مُرْسِخ (active, "entrenching"), مُرْسَخ (passive, "entrenched") -- first-hamzated (like sound): مُؤْيِس (active, "causing to despair"), مُؤْيَس (passive, "caused to despair") -- final-weak: مُكْرٍ (active, "renting out"), مُكْرًى (passive, "rented out") -- assimilated: مُورِد (active, "transferring"), مُورَد (passive, "transferred"); same when first-Y, e.g. -- أَيْقَنَ "to be certain of": مُوقِن (active), مُوقَن (passive) -- assimilated + final-weak: مُورٍ (active, "setting fire, kindling"), مُورًى (passive, "set fire, kindled") -- geminate: مُمِدّ (active, "granting, helping"), مُمَدّ (passive, "granted, helped") -- hollow: مُزِيل (active, "eliminating"), مُزَال (passive, "eliminated") -- hollow + final-weak: مُعْيٍ (active, "tiring"), مُعْيًى (passive, "tired") local sound_prefix, expected_length if vform == "X" then check(2, S) check(3, T) sound_prefix = MU .. S .. SK .. T .. A .. c(4) expected_length = 6 else sound_prefix = MU .. c(2) expected_length = 4 end if len == expected_length and c(len - 1) == Y and c(len) ~= AMAQ then -- active hollow if not is_active then err("this shape only allowed for active participles") end check_weakness({"hollow"}, "allow missing") vocalized = sound_prefix .. II .. c(len) elseif len == expected_length and c(len - 1) == ALIF then -- passive hollow if is_active then err("this shape only allowed for passive participles") end check_weakness({"hollow"}, "allow missing") vocalized = sound_prefix .. AA .. c(len) elseif len == expected_length - 1 then -- active final-weak or active or passive geminate if is_active then check_weakness({"geminate", "final-weak", "assimilated+final-weak"}) if weakness == "geminate" then vocalized = sound_prefix .. I .. c(len) .. SH elseif vform == "IV" and c(2) == W then -- assimilated final-weak vocalized = sound_prefix .. c(len) .. IN else vocalized = sound_prefix .. SK .. c(len) .. IN end else check_weakness({"geminate"}, "allow missing") vocalized = sound_prefix .. A .. c(len) .. SH end else if vform == "IV" and c(2) == W then -- assimilated, possibly final-weak sound_prefix = sound_prefix .. c(expected_length - 1) else sound_prefix = sound_prefix .. SK .. c(expected_length - 1) end handle_possibly_final_weak(sound_prefix, expected_length) end elseif vform == "VII" or vform == "VIII" then -- form VII (passive participles are fairly rare but do exist): -- sound: مُنْكَتِب (active "subscribing"), مُنْكَتَب (passive "subscribed") -- geminate: مُنْضَمّ (both active "joining, containing" and passive "joined, contained") -- final-weak: مُنْطَلٍ (active "fooling (someone)"), مُنْطَلًى (passive "fooled") -- final-weak with medial wāw: مُنْطَوٍ (active "involving"), مُنْطَوًى (passive "involved") -- hollow: مُنْقَاد (both active "complying with" and passive "complied with") -- -- for form VIII, the same variants exist but things are complicated by assimilations involving the template T. -- sound third-hamzated no assimilation: مُبْتَدِئ (active "beginning"), مُبْتَدَأ (passive "begun") -- geminate no assimilation: مُبْتَزّ (both active "robbing" and passive "robbed") -- final-weak no assimilation: مُبْتَنٍ (active "building"), مُبْتَنًى (passive "built") -- final-weak with medial wāw no assimilation: مُحْتَوٍ (active "containing"), مُحْتَوًى (passive "contained") -- hollow no assimilation: مُخْتَار (both active "choosing" and passive "chosen") -- -- sound with total assimilation: مُتَّبِع (active "following"), مُتَّبَع (passive "followed") -- sound with total assimilation, assimilating wāw: مُتَّعِد (active "threatening"), مُتَّعَد (passive "threatened") -- sound with total assimilation, irregularly assimilating hamza: مُتَّخِذ (active "taking"), مُتَّخَذ (passive "taken") -- sound with total assimilation (to ḏāl, producing dāl): مُدَّخِر (active "reserving"), مُدَّخَر (passive "reserved") -- sound with total assimilation (to ḏāl): مُذَّكِر (active "remembering"), مُذَّكَر (passive "remembered") -- sound with total assimilation (to ṭāʔ): مُطَّرِح (active "discarding"), مُطَّرَح (passive "discarded") -- sound with total assimilation (to ẓāʔ): مُظَّلِم (active "tolerating"), مُظَّلَم (passive "tolerated") -- final-weak with total assimilation, assimilating wāw: مُتَّقٍ (active "guarding against"), مُتَّقًى (passive "guarded against") -- final-weak with total assimilation (to ṯāʔ): مُثَّنٍ (active "undulating"), مُثَّنًى (passive "undulated") -- final-weak with total assimilation (to dāl): مُدَّعٍ (active "claiming"), مُدَّعًى (passive "claimed") -- sound with partial assimilation (to zayn): مُزْدَهِر (active "thriving"), مُزْدَهَر (passive "thrived") -- sound with medial wāw with partial assimilation (to zayn): مُزْدَوِج (active "appearing twice") -- sound with partial assimilation (to ṣād): مُصْطَبِح (active "illuminating"), مُصْطَبَح (passive, "illuminated") -- sound with partial assimilation (to ḍād): مُضْطَرِب (active "to be disturbed"; no passive) -- geminate with partial assimilation (to ṣād): مُصْطَبّ (both active "effusing" and passive "effused") -- geminate with partial assimilation (to ḍād): مُضْطَرّ (both active "forcing" and passive "forced") -- final-weak with partial assimilation (to ṣād): مُصْطَلٍ (active "warming"), مُصْطَلًى (passive "warmed") -- hollow with partial assimilation (to zayn): مُزْدَاد (both active "increasing" and passive "increased") -- hollow with partial assimilation (to ṣad): مُصْطَاد (both active "hunting" and passive "hunted") local sound_prefix, sufind if vform == "VII" then check(2, N) sound_prefix = MU .. N .. SK .. c(3) sufind = 4 else local c2 = c(2) if c2 == T or c2 == "د" or c2 == "ث" or c2 == "ذ" or c2 == "ط" or c2 == "ظ" then -- full assimilation sound_prefix = MU .. c2 .. SH sufind = 3 else -- partial or no assimilation if c2 == "ز" then check(3, "د") elseif c2 == "ص" or c2 == "ض" then check(3, "ط") else check(3, T) end sound_prefix = MU .. c2 .. SK .. c(3) sufind = 4 end end if c(sufind) == ALIF then -- hollow, active or passive check_len(sufind + 1, sufind + 1) check_weakness({"hollow"}, "allow missing") vocalized = sound_prefix .. AA .. c(sufind + 1) elseif len == sufind then -- active final-weak or active or passive geminate if is_active then check_weakness({"geminate", "final-weak", "assimilated+final-weak"}) if weakness == "geminate" then vocalized = sound_prefix .. A .. c(len) .. SH else vocalized = sound_prefix .. A .. c(len) .. IN end else check_weakness({"geminate"}, "allow missing") vocalized = sound_prefix .. A .. c(len) .. SH end else sound_prefix = sound_prefix .. A .. c(sufind) handle_possibly_final_weak(sound_prefix, sufind + 1) end elseif vform == "IX" then check_len(4, 4) vocalized = MU .. c(2) .. SK .. c(3) .. A .. c(4) .. SH elseif vform == "IVq" then -- e.g. [[اذلعب]] "to scamper away", مُذْلَعِبّ (active), مُذْلَعَبّ (passive); -- [[اطمأن]] "to remain quietly; to be certain", مُطْمَئِنّ (active), مُطْمَأَنّ (passive) check_len(5, 5) local sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. c(4) if is_active then vocalized = sound_prefix .. I .. c(5) .. SH else vocalized = sound_prefix .. A .. c(5) .. SH end elseif vform == "XI" then check_len(5, 5) check(4, ALIF) vocalized = MU .. c(2) .. SK .. c(3) .. AA .. c(5) .. SH -- e.g. [[احمار]] "to turn red, to blush", مُحْمَارّ (active) elseif vform == "XIV" or vform == "XV" then -- FIXME: Implement. No examples in Wiktionary currently; need to look up in a grammar. error("Support for verb form " .. vform .. " not implemented yet") else error("Don't recognize verb form " .. vform) end vocalized = rsub(vocalized, HAMZA .. AA, AMAD) local reconstructed_headword = lang:makeEntryName(vocalized) if reconstructed_headword ~= orig_headword then error(("Internal error: Vocalized participle %s doesn't match original participle %s"):format( vocalized, orig_headword)) end -- Apply NFC-style normalization to match MediaWiki's automatic normalization return apply_nfc_shadda(vocalized) end function export.infer_participle_vocalization_json(frame) local iparams = { [1] = {required = true}, [2] = {required = true}, ["weakness"] = {}, ["passive"] = {type = "boolean"} } local iargs = require("Module:parameters").process(frame.args, iparams) return export.infer_participle_vocalization(iargs[1], iargs[2], iargs.weakness, not iargs.passive) end return export a3ln6botcxcngowbzk1dniy7eh8by3h 1097563 1097559 2026-07-26T17:37:54Z ForzaGreen 28665 إزالة الاعتماد على وحدات الملعب المنسوخة (languages/ملعب، links/ملعب2، ar-utilities/en) وحذف استدعاء ar-nominals غير المستعمل، وإزالة معدّلات q/qq/l/ll التي تستدعي pron qualifier المفقودة، وإصلاح خطأ حواشي الحركات 1097563 Scribunto text/plain local export = {} --[=[ This module implements {{ar-conj}} and provides the underlying conjugation functions for {{ar-verb}} (whose actual formatting is done in [[Module:ar-headword]]). Author: User:Benwing, from an early version (2013-2014) by User:Atitarev, User:ZxxZxxZ. ]=] --[=[ TERMINOLOGY: -- "slot" = A particular combination of tense/mood/person/number/etc. Example slot names for verbs are "past_1s" (past tense first-person singular), "juss_pass_3fp" (non-past jussive passive third-person feminine plural) "ap" (active participle). Each slot is filled with zero or more forms. -- "form" = The conjugated Arabic form representing the value of a given slot. -- "lemma" = The dictionary form of a given Arabic term. For Arabic, normally the third person masculine singular past, although other forms may be used if this form is missing (e.g. in passive-only verbs or verbs lacking the past). ]=] --[=[ FIXME: 1. Finish unimplemented conjugation types. Only IX-final-weak left (extremely rare, possibly only one verb اِعْمَايَ (according to Haywood and Nahmad p. 244, who are very specific about the irregular occurrence of alif + yā instead of expected اِعْمَيَّ with doubled yā). Not in Hans Wehr. NOTE: Not true about this, cf. form IX اِرْعَوَى "to desist, to repent, to see the light". Also note form XII اِخْضَوْضَرَ = form IX اِخْضَرَّ "to be or become green". [DONE except for اِعْمَايَ] 2. Implement irregular verbs as special cases and recognize them, e.g. -- laysa "to not be"; only exists in the past tense, no non-past, no imperative, no participles, no passive, no verbal noun. Irregular alternation las-/lays-. [IMPLEMENTABLE USING OVERRIDES] -- istaḥā yastaḥī "be ashamed of" -- this is complex according to Hans Wehr because there are two verbs, regular istaḥyā yastaḥyī "to spare (someone)'s life" and irregular istaḥyā yastaḥyī "to be ashamed to face (someone)", which is irregular because it has the alternate irregular form istaḥā yastaḥī which only applies to this meaning. Currently we follow Haywood and Nahmad in saying that both varieties can be spelled istaḥyā/istaḥā/istaḥḥā, but we should instead use a variant= param similar to حَيَّ to distinguish the two possibilities, and maybe not include istaḥḥā. -- ʿayya/ʿayiya yaʿayyu/yaʿyā "to not find the right way, be incapable of, stammer, falter, fall ill". This appears to be a mixture of a geminate and final-weak verb. Unclear what the whole paradigm looks like. Do the consonant-ending parts in the past follow the final-weak paradigm? Is it the same in the non-past? Or can you conjugate the non-past fully as either geminate or final-weak? -- اِنْمَحَى inmaḥā or يمَّحَى immaḥā "to be effaced, obliterated; to disappear, vanish" has irregular assimilation of inm- to imm- as an alternative. inmalasa "to become smooth; to glide; to slip away; to escape" also has immalasa as an alternative. The only other form VII verbs in Hans Wehr beginning with -m- are inmalaḵa "to be pulled out, torn out, wrenched" and inmāʿa "to be melted, to melt, to dissolve", which are not listed with imm- alternatives, but might have them; if so, we should handle this generally. [DONE] -- يَرَعَ yaraʕa yariʕu "to be a coward, to be chickenhearted" as an alternative form of يَرِعَ yariʕa yayraʕu (as given in Wehr). [IMPLEMENTABLE USING OVERRIDES] 3. Implement individual override parameters for each paradigm part. See Module:fro-verb for an example of how to do this generally. Note that {{temp|ar-conj-I}} and other of the older templates already had such individual override params. [DONE] Irregular verbs already implemented: -- [ḥayya/ḥayiya yaḥyā "live" -- behaves like a normal final-weak verb (e.g. past first singular ḥayītu) except in the past-tense parts with vowel-initial endings (all the third person except for the third feminine plural). The normal singular and dual endings have -yiya- in them, which compresses to -yya-, with the normal endings the less preferred ones. In masculine third plural, expected ḥayū is replaced by ḥayyū by analogy to the -yy- parts, and the regular form is not given as an alternant in John Mace. Barron's 201 verbs appears to have the regular ḥayū as the part, however. Note also that final -yā appears with tall alif. This appears to be a spelling convention of Arabic, also applying in ḥayyā (form II, "to keep (someone) alive") and 'aḥyā (form IV, "to animate, revive, give birth to, give new life to").] -- implemented -- [ittaxadha yattaxidhu "take"] -- implemented -- [sa'ala yas'alu "ask" with alternative jussive/imperative yasal/sal] -- implemented -- [ra'ā yarā "see"] -- implemented -- ['arā yurī "show"] -- implemented -- ['akala ya'kulu "eat" with imperative kul] -- implemented -- ['axadha ya'xudhu "take" with imperative xudh] -- implemented -- ['amara ya'muru "order" with imperative mur] -- implemented --]=] local force_cat = false -- set to true for debugging -- if true, always maintain manual translit during processing, and compare against full translit at the end local debug_translit = false local lang = require("Module:languages").getByCode("ar") local m_links = require("Module:links") local m_string_utilities = require("Module:string utilities") local m_table = require("Module:table") local ar_utilities = require("Module:ar-utilities") local iut = require("Module:inflection utilities") local put = require("Module:parse utilities") local list_to_text = mw.text.listToText local rfind = m_string_utilities.find local rsubn = m_string_utilities.gsub local rmatch = m_string_utilities.match local rsplit = m_string_utilities.split local usub = m_string_utilities.sub local ulen = m_string_utilities.len local u = m_string_utilities.char local unpack = unpack or table.unpack -- Lua 5.2 compatibility local dump = mw.dumpObject -- Within this module, conjugations are the functions that do the actual -- conjugating by creating the parts of a basic verb. -- They are defined further down. local conjugations = {} -- hamza variants local HAMZA = u(0x0621) -- hamza on the line (stand-alone hamza) = ء local HAMZA_ON_ALIF = u(0x0623) local HAMZA_ON_W = u(0x0624) local HAMZA_UNDER_ALIF = u(0x0625) local HAMZA_ON_Y = u(0x0626) local HAMZA_ANY = "[" .. HAMZA .. HAMZA_ON_ALIF .. HAMZA_UNDER_ALIF .. HAMZA_ON_W .. HAMZA_ON_Y .. "]" local HAMZA_PH = u(0xFFF0) -- hamza placeholder local BAD = u(0xFFF1) local BORDER = u(0xFFF2) -- diacritics local A = u(0x064E) -- fatḥa local AN = u(0x064B) -- fatḥatān (fatḥa tanwīn) local U = u(0x064F) -- ḍamma local UN = u(0x064C) -- ḍammatān (ḍamma tanwīn) local I = u(0x0650) -- kasra local IN = u(0x064D) -- kasratān (kasra tanwīn) local SK = u(0x0652) -- sukūn = no vowel local SH = u(0x0651) -- šadda = gemination of consonants local DAGGER_ALIF = u(0x0670) local DIACRITIC_ANY_BUT_SH = "[" .. A .. I .. U .. AN .. IN .. UN .. SK .. DAGGER_ALIF .. "]" -- Pattern matching short vowels local AIU = "[" .. A .. I .. U .. "]" -- Pattern matching short vowels or sukūn local AIUSK = "[" .. A .. I .. U .. SK .. "]" -- Pattern matching any diacritics that may be on a consonant local DIACRITIC = SH .. "?" .. DIACRITIC_ANY_BUT_SH -- translit_patterns local vowels = "aeiouāēīōū" local NV = "[^" .. vowels .. "]" local dia = {a = A, i = I, u = U} local undia = {[A] = "a", [I] = "i", [U] = "u", ["-"] = "-"} -- various letters and signs local ALIF = u(0x0627) -- ʾalif = ا local AMAQ = u(0x0649) -- ʾalif maqṣūra = ى local AMAD = u(0x0622) -- ʾalif madda = آ local TAM = u(0x0629) -- tāʾ marbūṭa = ة local T = u(0x062A) -- tāʾ = ت local HYPHEN = u(0x0640) local N = u(0x0646) -- nūn = ن local W = u(0x0648) -- wāw = و local Y = u(0x064A) -- yāʾ = ي local S = "س" local M = "م" local LRM = u(0x200e) -- left-to-right mark -- common combinations local AH = A .. TAM local AT = A .. T local AA = A .. ALIF local AAMAQ = A .. AMAQ local AAH = AA .. TAM local AAT = AA .. T local II = I .. Y local UU = U .. W local AY = A .. Y local AW = A .. W local AYSK = AY .. SK local AWSK = AW .. SK local NA = N .. A local NI = N .. I local AAN = AA .. N local AANI = AA .. NI local AYNI = AYSK .. NI local AWNA = AWSK .. NA local AYNA = AYSK .. NA local AYAAT = AY .. AAT local UNU = "[" .. UN .. U .. "]" local MA = M .. A local MU = M .. U local TA = T .. A local TU = T .. U local _I = ALIF .. I local _U = ALIF .. U --W: Morphological pattern (الوزن الصرفي) to verb form mapping local MORPHOLOGICAL_PATTERN_TO_FORM = { ["فعَل يفعُل"] = "I/a~u", ["فعَل يفعِل"] = "I/a~i", ["فعَل يفعَل"] = "I/a~a", ["فعُل يفعُل"] = "I/u~u", ["فعِل يفعَل"] = "I/i~a", ["فعِل يفعِل"] = "I/i~i", ["فعّل"] = "II", ["فاعل"] = "III", ["أفعل"] = "IV", ["تفعّل"] = "V", ["تفاعل"] = "VI", ["انفعل"] = "VII", ["افتعل"] = "VIII", ["افعلّ"] = "IX", ["استفعل"] = "X", ["افعالّ"] = "XI", ["افعوعل"] = "XII", ["افعوّل"] = "XIII", ["فعلل"] = "Iq", ["تفعلل"] = "IIq", ["افعنلل"] = "IIIq", -- "XIV" should not be used ["افعللّ"] = "IVq", } --W: Create reverse mapping from form to morphological pattern local FORM_TO_MORPHOLOGICAL_PATTERN = {} for pattern, form in pairs(MORPHOLOGICAL_PATTERN_TO_FORM) do FORM_TO_MORPHOLOGICAL_PATTERN[form] = pattern end --W: local MORPHOLOGICAL_PATTERN_TO_BASIC_DERIV = { ["فعَل يفعُل"] = "مُجرَّد", ["فعَل يفعِل"] = "مُجرَّد", ["فعَل يفعَل"] = "مُجرَّد", ["فعُل يفعُل"] = "مُجرَّد", ["فعِل يفعَل"] = "مُجرَّد", ["فعِل يفعِل"] = "مُجرَّد", ["فعّل"] = "مزيد بحرف", ["فاعل"] = "مزيد بحرف", ["أفعل"] = "مزيد بحرف", ["تفعّل"] = "مزيد بحرفين", ["تفاعل"] = "مزيد بحرفين", ["انفعل"] = "مزيد بحرفين", ["افتعل"] = "مزيد بحرفين", ["افعلّ"] = "مزيد بحرفين", ["استفعل"] = "مزيد بثلاثة أحرف", ["افعالّ"] = "مزيد بثلاثة أحرف", ["افعوعل"] = "مزيد بثلاثة أحرف", ["افعوّل"] = "مزيد بثلاثة أحرف", ["فعلل"] = "مُجرَّد", ["تفعلل"] = "مزيد بحرف", ["افعنلل"] = "مزيد بحرفين", ["افعللّ"] = "مزيد بحرفين", } local translit_cache = { -- hamza variants [HAMZA] = "ʔ", [HAMZA_ON_ALIF] = "ʔ", [HAMZA_ON_W] = "ʔ", [HAMZA_UNDER_ALIF] = "ʔ", [HAMZA_ON_Y] = "ʔ", [HAMZA_PH] = "ʔ", -- diacritics [A] = "a", [AN] = "an", [U] = "u", [UN] = "un", [I] = "i", [IN] = "in", [SK] = "", [SH] = "*", -- handled specially [DAGGER_ALIF] = "ā", -- various letters and signs [""] = "", [ALIF] = BAD, -- we should never be transliterating ALIF by itself, as its translit in isolation is ambiguous [AMAQ] = BAD, [AMAD] = "ʔā", [TAM] = "", [T] = "t", [N] = "n", [W] = "w", [Y] = "y", [S] = "s", [M] = "m", [LRM] = "", -- common combinations [AH] = "a", [AT] = "at", [AA] = "ā", [AAMAQ] = "ā", [AAH] = "āh", [AAT] = "āt", [II] = "ī", [UU] = "ū", [AY] = "ay", [AW] = "aw", [AYSK] = "ay", [AWSK] = "aw", [NA] = "na", [NI] = "ni", [AAN] = "ān", [AANI] = "āni", [AYNI] = "ayni", [AWNA] = "awna", [AYNA] = "ayna", [AYAAT] = "ayāt", [MA] = "ma", [MU] = "mu", [TA] = "ta", [TU] = "tu", [_I] = "i", [_U] = "u", } local function transliterate(text) local cached = translit_cache[text] if cached then if cached == BAD then error(("Internal error: Unable to transliterate %s because explicitly marked as BAD"):format(text)) end return cached end local tr = (lang:transliterate(text)) if not tr then error(("Internal error: Unable to transliterate: %s"):format(text)) end translit_cache[text] = tr return tr end local all_person_number_list = { "1s", "2ms", "2fs", "3ms", "3fs", "2d", "3md", "3fd", "1p", "2mp", "2fp", "3mp", "3fp" } local function make_person_number_slot_accel_list(list) local slot_accel_list = {} return slot_accel_list end local imp_person_number_list = {} for _, pn in ipairs(all_person_number_list) do if pn:find("^2") then table.insert(imp_person_number_list, pn) end end local passive_types = m_table.listToSet { "pass", -- verb has both active and passive "ipass", -- verb is active with impersonal passive "nopass", -- verb is active-only "onlypass", -- verb is passive-only "onlypass-impers", -- verb itself is impersonal, meaning passive-only with impersonal passive } local indicator_flags = m_table.listToSet { "nopast", "no_nonpast", "noimp", "nocat", -- don't categorize or include annotations about this; useful in suppletive parts of verbs "reduced", -- verb has assimilation/reduction of initial coronals "altgem", -- form X with alternative past geminate forms with final-weak endings } export.potential_lemma_slots = {"past_3ms", "past_pass_3ms", "ind_3ms", "ind_pass_3ms", "imp_2ms"} export.unsettable_slots = {} for _, potential_lemma_slot in ipairs(export.potential_lemma_slots) do table.insert(export.unsettable_slots, potential_lemma_slot .. "_linked") end -- We don't set the active participle directly for form I because we don't want stative verbs (with past vowel i or u) -- to default to فَاعِل. Instead we set the special slot 'ap1' and later copy it to 'ap' for non-stative verbs. The user -- meanwhile can explicitly request the فَاعِل form for active participles for stative verbs using `ap:+`. table.insert(export.unsettable_slots, "ap1") -- primary default فَاعِل for form I active participles table.insert(export.unsettable_slots, "ap2") -- secondary default فَعِيل for form I active participles (stative I) table.insert(export.unsettable_slots, "ap3") -- secondary default فَعِل for form I active participles (stative II) table.insert(export.unsettable_slots, "apcd") -- secondary default أَفْعَل for form I active participles (color/defect) table.insert(export.unsettable_slots, "apan") -- secondary default فَعْلَان for form I active participles (in -ān) table.insert(export.unsettable_slots, "pp2") -- secondary default فَعِيل for form I passive participles (same as ap2) table.insert(export.unsettable_slots, "vn2") -- secondary default فِعَال for form III verbal nouns export.unsettable_slots_set = m_table.listToSet(export.unsettable_slots) local default_indicator_to_active_participle_slot = { ["+"] = "ap1", ["++"] = "ap2", ["+++"] = "ap3", ["+cd"] = "apcd", ["+an"] = "apan", } local slots_that_may_be_uncertain = { vn = "verbal noun", ap = "active participle", } -- Initialize all the slots for which we generate forms. local function add_slots(alternant_multiword_spec) alternant_multiword_spec.verb_slots = { {"ap", "act|part"}, {"pp", "pass|part"}, {"vn", "vnoun"}, } for _, unsettable_slot in ipairs(export.unsettable_slots) do table.insert(alternant_multiword_spec.verb_slots, {unsettable_slot, "-"}) end -- Add entries for a slot with person/number variants. -- `slot_prefix` is the prefix of the slot, typically specifying the tense/aspect. -- `tag_suffix` is a string listing the set of inflection tags to add after the person/number tags. -- `person_number_list` is a list of the person/number slot suffixes to add to `slot_prefix`. local function add_personal_slot(slot_prefix, tag_suffix, person_number_list) for _, persnum in ipairs(person_number_list) do local slot = slot_prefix .. "_" .. persnum local accel = persnum:gsub("(.)", "%1|") .. tag_suffix table.insert(alternant_multiword_spec.verb_slots, {slot, accel}) end end local tenses = { {"past", "past|%s"}, {"ind", "non-past|%s|ind"}, {"sub", "non-past|%s|sub"}, {"juss", "non-past|%s|juss"}, } for _, slot_accel in ipairs(tenses) do local slot, accel = unpack(slot_accel) for _, voice in ipairs {"act", "pass"} do add_personal_slot(voice == "act" and slot or slot .. "_pass", accel:format(voice), all_person_number_list) end end add_personal_slot("imp", "imp", imp_person_number_list) alternant_multiword_spec.verb_slots_map = {} for _, slot_accel in ipairs(alternant_multiword_spec.verb_slots) do local slot, accel = unpack(slot_accel) alternant_multiword_spec.verb_slots_map[slot] = accel end end local overridable_stems = {} local slot_override_param_mods = { footnote = { item_dest = "footnotes", store = "insert", }, alt = {}, t = { -- [[Module:links]] expects the gloss in "gloss". item_dest = "gloss", }, gloss = {}, g = { -- [[Module:links]] expects the genders in "g". `sublist = true` automatically splits on comma (optionally -- with surrounding whitespace). item_dest = "genders", sublist = true, }, pos = {}, lit = {}, id = {}, --W: The q/qq/l/ll (qualifier and label) modifiers were removed: rendering them -- needs [[Module:pron qualifier]], which does not exist on ar.wiktionary, so any -- use of them raised a script error. Accepting them here again requires importing -- that module first and restoring the block in generate_link() below. } local function generate_obj(formval, parse_err, prefix, is_slot_override) local val, uncertain = formval:match("^(.*)(%?)$") val = val or formval uncertain = not not uncertain local ar, translit = val:match("^(.*)//(.*)$") if not ar then ar = val end if ar == "" then if uncertain then ar = "?" else error(("Can't specify blank value for override for %s override '%s'"):format( is_slot_override and "slot" or "stem", prefix)) end end return {form = ar, translit = translit, uncertain = uncertain} end local function parse_inline_modifiers(comma_separated_group, parse_err, prefix, is_slot_override) local function this_generate_obj(formval, parse_err) return generate_obj(formval, parse_err, prefix, is_slot_override) end return put.parse_inline_modifiers_from_segments { group = comma_separated_group, props = { param_mods = slot_override_param_mods, parse_err = parse_err, generate_obj = this_generate_obj, pre_normalize_modifiers = function(data) local modtext = data.modtext modtext = modtext:match("^(%[.*%])$") if modtext then return ("<footnote:%s>"):format(modtext) end return data.modtext end, }, } end local function allow_multiple_values_for_override(comma_separated_groups, data, is_slot_override) local retvals = {} for _, comma_separated_group in ipairs(comma_separated_groups) do local retval if is_slot_override then retval = parse_inline_modifiers(comma_separated_group, data.parse_err) else retval = generate_obj(comma_separated_group[1], data.parse_err, data.prefix, is_slot_override) retval.footnotes = data.fetch_footnotes(comma_separated_group) end table.insert(retvals, retval) end for _, form in ipairs(retvals) do if form.form == "+" or default_indicator_to_active_participle_slot[form.form] then if form.form ~= "+" and default_indicator_to_active_participle_slot[form.form] and not is_slot_override then error(("Stem override '%s' cannot use %s to request a secondary default"):format( data.prefix, form.form)) end data.base.slot_override_uses_default[data.prefix] = true end end for _, form in ipairs(retvals) do if form.form == "-" then data.base.slot_explicitly_missing[data.prefix] = true break end end if data.base.slot_explicitly_missing[data.prefix] then for _, form in ipairs(retvals) do if form.form ~= "-" then data.parse_err(("For slot or stem '%s', saw both - and a value other than -, which isn't allowed"): format(data.prefix)) end end return nil end return retvals end local function simple_choice(choices) return function(separated_groups, data) if #separated_groups > 1 then data.parse_err("For spec '" .. data.prefix .. ":', only one value currently allowed") end if #separated_groups[1] > 1 then data.parse_err("For spec '" .. data.prefix .. ":', no footnotes currently allowed") end local choice = separated_groups[1][1] if not m_table.contains(choices, choice) then data.parse_err("For spec '" .. data.prefix .. ":', saw value '" .. choice .. "' but expected one of '" .. table.concat(choices, "،") .. "'") end return choice end end for _, overridable_stem in ipairs { "past", "past_v", "past_c", "past_pass", "past_pass_v", "past_pass_c", "nonpast", "nonpast_v", "nonpast_c", "nonpast_pass", "nonpast_pass_v", "nonpast_pass_c", "imp", "imp_v", "imp_c", } do overridable_stems[overridable_stem] = allow_multiple_values_for_override end overridable_stems.past_final_weak_vowel = simple_choice { "ay", "aw", "ī", "ū" } overridable_stems.past_pass_final_weak_vowel = simple_choice { "ay", "aw", "ī", "ū" } overridable_stems.nonpast_final_weak_vowel = simple_choice { "ā", "ī", "ū" } overridable_stems.nonpast_pass_final_weak_vowel = simple_choice { "ā", "ī", "ū" } ------------------------------------------------------------------------------- -- Utility functions -- ------------------------------------------------------------------------------- -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) return (rsubn(term, foo, bar)) end -- version of rsubn() that returns a 2nd argument boolean indicating whether a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end -- Concatenate one or more strings or form objects. local function q(...) local not_all_strings = debug_translit local has_manual_translit = debug_translit for i = 1, select("#", ...) do local argt = select(i, ...) if not argt then error(("Internal error: Saw nil at index %s: %s"):format(i, dump({...}))) end if type(argt) ~= "string" then not_all_strings = true if argt.translit then has_manual_translit = true break end end end if not not_all_strings then -- just strings, concatenate directly return table.concat({...}) end local formvals = {} local translit = has_manual_translit and {} or nil local footnotes for i = 1, select("#", ...) do local argt = select(i, ...) if type(argt) == "string" then formvals[i] = argt if has_manual_translit then translit[i] = transliterate(argt) end else formvals[i] = argt.form if has_manual_translit then translit[i] = argt.translit or transliterate(argt.form) end footnotes = iut.combine_footnotes(footnotes, argt.footnotes) end end -- FIXME: Do we want to support other properties? return { form = table.concat(formvals), translit = has_manual_translit and table.concat(translit) or nil, footnotes = footnotes, } end -- Return the formval associated with `rad` (a radical or past/non-past vowel, either a string or form object). local function rget(rad) if type(rad) == "string" then return rad elseif type(rad) == "table" then return rad.form else error(("Internal error: Unexpected type for radical or past/non-past vowel: %s"):format(dump(rad))) end end export.rget = rget -- for use in [[Module:ar-headword]] -- Return the footnotes associated with `rad` (a radical or past/non-past vowel, either a string or form object). local function rget_footnotes(rad) if type(rad) == "string" then return nil elseif type(rad) == "table" then return rad.footnotes else error(("Internal error: Unexpected type for radical or past/non-past vowel: %s"):format(dump(rad))) end end -- Return true if the formval associated with `rad` (a radical or past/non-past vowel, either a string or form object) -- is `val`. local function req(rad, val) return rget(rad) == val end -- Map `vow` (a past/non-past vowel, either a string or form object without translit) by passing the formval through -- `fn`. Don't call this on radicals because they may have manual translit and it isn't clear how to handle that. local function map_vowel(vow, fn) if type(vow) == "string" then return fn(vow) elseif type(vow) == "table" then return {form = fn(vow.form), footnotes = vow.footnotes} else error(("Internal error: Unexpected type for past/non-past vowel: %s"):format(dump(vow))) end end local function get_radicals_3(vowel_spec) return vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3, vowel_spec.past, vowel_spec.nonpast end local function get_radicals_4(vowel_spec) return vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3, vowel_spec.rad4 end local function is_final_weak(base, vowel_spec) return vowel_spec.weakness == "final-weak" or base.form == "XV" end --W: Create a simple link local function link_term_simple(text) return "[[" .. text .. "]]" end local function link_term(text, face, id) return m_links.full_link({lang = lang, term = text, tr = "-", id = id}, face) end local function tag_text(text, tag, class) return m_links.full_link({lang = lang, alt = text, tr = "-"}) end local function track(page) require("Module:debug/track")("ar-verb/" .. page) return true end local function track_if_ar_conj(base, page) if base.alternant_multiword_spec.source_template == "ar-conj" then require("Module:debug/track")("ar-verb/" .. page) end return true end local function reorder_shadda(word) -- shadda+short-vowel (including tanwīn vowels, i.e. -an -in -un) gets -- replaced with short-vowel+shadda during NFC normalisation, which -- MediaWiki does for all Unicode strings; however, it makes various -- processes inconvenient, so undo it. word = rsub(word, "(" .. DIACRITIC_ANY_BUT_SH .. ")" .. SH, SH .. "%1") return word end local function apply_nfc_shadda(word) -- Apply NFC-style normalization: reorder shadda+vowel to vowel+shadda -- to match MediaWiki's automatic NFC normalization. This reverses the -- internal shadda+vowel format used during processing. word = rsub(word, SH .. "(" .. DIACRITIC_ANY_BUT_SH .. ")", "%1" .. SH) return word end ------------------------------------------------------------------------------- -- Basic functions to inflect tenses -- ------------------------------------------------------------------------------- local function skip_slot(base, slot, allow_overrides) if base.slot_explicitly_missing[slot] then return true end if not allow_overrides and base.slot_overrides[slot] and not base.slot_override_uses_default[slot] then -- Skip any slots for which there are overrides, except those that request the default value using +, ++, etc. return true end if base.passive == "nopass" and (slot == "pp" or slot:find("_pass")) then return true elseif base.passive == "onlypass" and slot ~= "pp" and slot ~= "vn" and not slot:find("_pass") then return true elseif base.passive == "ipass" and slot:find("_pass") and not slot:find("3ms") then return true elseif base.passive == "onlypass-impers" and slot ~= "pp" and slot ~= "vn" and (not slot:find("_pass") or slot:find("_pass") and not slot:find("3ms")) then return true end if base.nopast and slot:find("^past_") then return true end if base.noimp and slot:find("^imp_") then return true end if base.no_nonpast and (slot:find("^ind_") or slot:find("^sub_") or slot:find("^juss")) then return true end return false end local function basic_combine_stem_ending(stem, ending) return stem .. ending end local function basic_combine_stem_ending_tr(stem, ending) return stem .. ending end -- Concatenate `prefixes`, `stems` and `endings` (any of which may be an abbreviate form list, i.e. strings, form -- objects or lists of strings or form objects) and store into `slot`. If a user-supplied override exists for the slot, -- nothing will happen unless `allow_overrides` is provided. local function add3(base, slot, prefixes, stems, endings, allow_overrides) if skip_slot(base, slot, allow_overrides) then return end -- Optimization since the prefixes are almost always single strings. if type(prefixes) == "string" then local function do_combine_stem_ending(stem, ending) return prefixes .. stem .. ending end local function do_combine_stem_ending_tr(stem, ending) return transliterate(prefixes) .. stem .. ending end iut.add_forms(base.forms, slot, stems, endings, do_combine_stem_ending, transliterate, do_combine_stem_ending_tr, base.form_footnotes) else iut.add_multiple_forms(base.forms, slot, {prefixes, stems, endings}, basic_combine_stem_ending, transliterate, basic_combine_stem_ending_tr, base.form_footnotes) end end -- Insert one or more forms in `form_or_forms` into `slot`. `form_or_forms` is an abbreviated form list (see comment at -- top of [[Module:inflection utilities]]). If a user-supplied override exists for the slot, nothing will happen unless -- `allow_overrides` is provided. BEWARE: One form object should never occur in two different slots, or twice in a given -- slot; if taking a form object from an existing slot, make sure to shallowCopy() it. local function insert_form_or_forms(base, slot, form_or_forms, allow_overrides, uncertain) if not skip_slot(base, slot, allow_overrides) then -- Some optimizations of the most common case of inserting a single string. if type(form_or_forms) == "string" and not base.form_footnotes then form_or_forms = {form = form_or_forms, uncertain = uncertain} iut.insert_form(base.forms, slot, form_or_forms) else local list = iut.convert_to_general_list_form(form_or_forms, base.form_footnotes) if uncertain then for _, formobj in ipairs(list) do formobj.uncertain = true end end iut.insert_forms(base.forms, slot, list) end end end -- Insert `string_or_form` into both the ap2 and pp2 slots, shallowCopying a form object to make sure no form objects -- occur in two slots. local function insert_ap2_pp2(base, string_or_form) insert_form_or_forms(base, "ap2", string_or_form) if type(string_or_form) == "table" then string_or_form = m_table.shallowCopy(string_or_form) end insert_form_or_forms(base, "pp2", string_or_form) end -- Convert `stemforms` (a string, a form object, or a list of strings and/or form objects) into "general form" (a list -- of form objects) and map `fn` over the list of objects. `fn` is passed two arguments (form value and translit) and -- should likewise return the new form value and translit. Footnotes will be preserved. FIXME: Preserve other metadata. local function map_general(stemforms, fn) return iut.map_forms(iut.convert_to_general_list_form(stemforms), fn) end -- Similar to map_general() except that `fn` should return a single value (one or more strings or form objects), instead -- of two values (form value and translit), and the resulting value(s) from all calls to `fn` will be flattened to -- construct the overall return value. Footnotes will be preserved. FIXME: Preserve other metadata. local function flatmap_general(stemforms, fn) return iut.flatmap_forms(iut.convert_to_general_list_form(stemforms), fn) end -- Given user-supplied stem overrides in `base`, construct any derived stem overrides (e.g. vowel-specific or -- consonant-specific variants), and truncate initial y-/ي- in any non-past overrides. local function construct_stems(base) local stems = base.stem_overrides stems.past_v = stems.past_v or stems.past stems.past_c = stems.past_c or stems.past stems.past_pass_v = stems.past_pass_v or stems.past_pass stems.past_pass_c = stems.past_pass_c or stems.past_pass stems.nonpast_v = stems.nonpast_v or stems.nonpast stems.nonpast_c = stems.nonpast_c or stems.nonpast stems.nonpast_pass_v = stems.nonpast_pass_v or stems.nonpast_pass stems.nonpast_pass_c = stems.nonpast_pass_c or stems.nonpast_pass stems.imp_v = stems.imp_v or stems.imp stems.imp_c = stems.imp_c or stems.imp local function truncate_nonpast_initial_cons(stem_type, form, translit) if form == "+" then return form, translit end if not form:find("^" .. Y) then error(("Form value %s for stem type '%s' should begin with ي"):format(form, stem_type)) end form = form:gsub("^" .. Y, "") if translit then if not translit:find("^y") then error(("Translit value %s for stem type '%s' should begin with y"):format(translit, stem_type)) end translit = translit:gsub("^y", "") end return form, translit end for _, nonpast_stem_type in ipairs { "nonpast_v", "nonpast_c", "nonpast_pass_v", "nonpast_pass_c" } do if stems[nonpast_stem_type] then stems[nonpast_stem_type] = map_general(stems[nonpast_stem_type], function(form, translit) return truncate_nonpast_initial_cons(nonpast_stem_type, form, translit) end) end end end -- Given user-specified overrides for stem `stemname`, return overrides with occurrences of + replaced by -- `default_stem`. If no overrides, return `default_stem`, or {} if no default. local function override_stem_if_needed(base, stemname, default_stem) local overrides = base.stem_overrides[stemname] if not overrides then return default_stem or {} end return map_general(overrides, function(form, translit) if form ~= "+" and default_indicator_to_active_participle_slot[form] then error(("Stem overrides cannot use secondary default indicators but saw %s in stem override '%s'"):format( form, stemname)) end if form == "+" then if translit then error(("Cannot supply manual translit along with + for stem override '%s'"):format(stemname)) end if not default_stem then error(("Cannot use + for stem override '%s' because no default is available"):format(stemname)) end if type(default_stem) ~= "string" then error(("Internal error: Default stem for '%s' is not a string: %s"):format(stemname, dump(default_stem))) end return default_stem end return form, translit end) end ------------------------------------------------------------------------------- -- Properties of different verbal forms -- ------------------------------------------------------------------------------- local allowed_vforms = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "Iq", "IIq", "IIIq", "IVq"} local allowed_vforms_set = m_table.listToSet(allowed_vforms) local allowed_vforms_with_weakness = m_table.shallowCopy(allowed_vforms) -- The user needs to be able to explicitly specify that a form-I verb (specifically one whose initial radical is و) is -- sound. Cf. wajiʕa yawjaʕu (not #yajaʕu) "to ache, to hurt". In general, i~a and u~u verbs whose initial radical is و -- seem to not assimilate the first radical; cf. وقح "to be shameless", variously waqaḥa~yaqiḥu, waquḥa~yawquḥu and -- waqiḥa~yawqaḥu, whereas a~i verbs (wafaḍa~yafiḍu "to rush"), i~i verbs (wafiqa~yafiqu "to be proper, to be suitable") -- and a~a verbs (waḍaʕa~yaḍaʕu "to set down, to place") do assimilate. But there are naturally exceptions, e.g. -- waṭiʔa~yaṭaʔu "to tread, to trample"; wasiʕa~yasaʕu "to be spacious; to be well-off"; waṯiʔa~yaṯaʔu "to get bruised, -- to be sprained". Also beware of waniya~yawnā "to be faint; to languish", which is sound in the first radical and -- final-weak in the last radical. Nonetheless, the regularity of the patterns mentioned above suggest we should provide -- them as defaults. -- Note that there are other cases of unexpectedly sound verbs, e.g. izdawaja~yazdawiju "to be in pairs", layisa~yalyasu -- "to be valiant, to be brave", ʔaḥwaja~yuḥwiju "to need", istahwana~yastahwinu "to consider easy", sawisa~yaswasu "to -- be or become moth-eaten or worm-eaten" (vs. sāsa~yasūsu "to govern, to rule" from the same radicals), ʕawira~yaʕwaru -- "to be one-eyed", istajwaba~yastajwibu "to interrogate", etc. But in these cases there is no need for explicit user -- specification as the lemma itself specifies the unexpected soundness. for _, form_with_weakness in ipairs { "I-sound", "I-assimilated", "none-sound", "none-hollow", "none-geminate", "none-final-weak" } do table.insert(allowed_vforms_with_weakness, form_with_weakness) end local allowed_vforms_with_weakness_set = m_table.listToSet(allowed_vforms_with_weakness) local function vform_supports_final_weak(vform) return vform ~= "XI" and vform ~= "XV" and vform ~= "IVq" end local function vform_supports_geminate(vform) return vform == "I" or vform == "III" or vform == "IV" or vform == "VI" or vform == "VII" or vform == "VIII" or vform == "X" end local function vform_supports_hollow(vform) return vform == "I" or vform == "IV" or vform == "VII" or vform == "VIII" or vform == "X" end local function vform_probably_impersonal_passive(vform, weakness, past_vowel, nonpast_vowel) return vform == "I" and req(past_vowel, I) or vform == "V" or vform == "VI" or vform == "X" or vform == "IIq" end local function vform_probably_full_passive(vform) return vform == "II" or vform == "III" or vform == "IV" or vform == "Iq" end local function vform_probably_no_passive(vform, weakness, past_vowel, nonpast_vowel) return vform == "I" and req(past_vowel, U) or vform == "VII" or vform == "IX" or vform == "XI" or vform == "XII" or vform == "XIII" or vform == "XIV" or vform == "XV" or vform == "IIIq" or vform == "IVq" end -- Active vforms II, III, IV, Iq use non-past prefixes in -u- instead of -a-. local function prefix_vowel_from_vform(vform) if vform == "II" or vform == "III" or vform == "IV" or vform == "Iq" then return "u" else return "a" end end -- True if the active non-past takes a-vocalization rather than i-vocalization in its last syllable. local function vform_nonpast_a_vowel(vform) return vform == "V" or vform == "VI" or vform == "XV" or vform == "IIq" end -- True if the `passive` spec indicates a passive-only verb. local function is_passive_only(passive) return passive == "onlypass" or passive == "onlypass-impers" end export.is_passive_only = is_passive_only -- for use in [[Module:ar-headword]] ------------------------------------------------------------------------------- -- Properties of specific sounds -- ------------------------------------------------------------------------------- -- Is radical wāw (و) or yāʾ (ي)? local function is_waw_ya(rad) return req(rad, W) or req(rad, Y) end -- Check that radical is wāw (و) or yāʾ (ي), error if not local function check_waw_ya(rad) if not is_waw_ya(rad) then error("Expecting weak radical: '" .. rget(rad) .. "' should be " .. W .. " or " .. Y) end end -- Form-I verb حيّ or حيي and form-X verb استحيا or استحى local function hayy_radicals(rad1, rad2, rad3) return req(rad1, "ح") and req(rad2, Y) and is_waw_ya(rad3) end -- FUCK ME HARD. "Lua error at line 1514: main function has more than 200 local variables". local function create_conjugations() ------------------------------------------------------------------------------- -- Radicals associated with various irregular verbs -- ------------------------------------------------------------------------------- -- Form-I verb أخذ or form-VIII verb اتخذ local function axadh_radicals(rad1, rad2, rad3) return req(rad1, HAMZA) and req(rad2, "خ") and req(rad3, "ذ") end -- Form-I verb whose imperative has a reduced form: أكل and أخذ and أمر. Return "shortonly" if only -- short-form imperatives exist (أكل and أخذ) or "shortlong" if long-form imperatives also exist (أمر); -- they are used after a clitic like فَ and وَ. local function reduced_imperative_verb(rad1, rad2, rad3) return axadh_radicals(rad1, rad2, rad3) and "shortonly" or req(rad1, HAMZA) and req(rad2, "ك") and req(rad3, "ل") and "shortonly" or req(rad1, HAMZA) and req(rad2, "م") and req(rad3, "ر") and "shortlong" end -- Form-I verb رأى and form-IV verb أرى local function raa_radicals(rad1, rad2, rad3) return req(rad1, "ر") and req(rad2, HAMZA) and is_waw_ya(rad3) end -- Form-I verb سأل local function saal_radicals(rad1, rad2, rad3) return req(rad1, "س") and req(rad2, HAMZA) and req(rad3, "ل") end -- Form-I verb كان local function kaan_radicals(rad1, rad2, rad3) return req(rad1, "ك") and req(rad2, W) and req(rad3, N) end ------------------------------------------------------------------------------- -- Sets of past endings -- ------------------------------------------------------------------------------- -- The 13 endings of the sound/hollow/geminate past tense. local past_endings = { -- singular SK .. TU, SK .. TA, SK .. "تِ", A, A .. "تْ", --dual SK .. "تُمَا", AA, A .. "تَا", -- plural SK .. "نَا", SK .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this SK .. "تُن" .. SH .. A, UU .. ALIF, SK .. "نَ" } -- Make endings for final-weak past in -aytu or -awtu. AYAW is AY or AW as appropriate. Note that AA and AW are -- global variables. local function make_past_endings_ay_aw(ayaw, third_sg_masc) return { -- singular ayaw .. SK .. TU, ayaw .. SK .. TA, ayaw .. SK .. "تِ", third_sg_masc, A .. "تْ", --dual ayaw .. SK .. "تُمَا", ayaw .. AA, A .. "تَا", -- plural ayaw .. SK .. "نَا", ayaw .. SK .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this ayaw .. SK .. "تُن" .. SH .. A, AW .. SK .. ALIF, ayaw .. SK .. "نَ" } end -- past final-weak -aytu endings local past_endings_ay = make_past_endings_ay_aw(AY, AAMAQ) -- past final-weak -awtu endings local past_endings_aw = make_past_endings_ay_aw(AW, AA) -- used for alternative endings for form-X geminate verbs like اِسْتَمَرَّ local past_endings_ay_12_person_only = { -- singular AY .. SK .. TU, AY .. SK .. TA, AY .. SK .. "تِ", {}, {}, --dual AY .. SK .. "تُمَا", {}, {}, -- plural AY .. SK .. "نَا", AY .. SK .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this AY .. SK .. "تُن" .. SH .. A, {}, {}, } -- Make endings for final-weak past in -ītu or -ūtu. IIUU is ī or ū as appropriate. Note that AA and UU are global -- variables. local function make_past_endings_ii_uu(iiuu) return { -- singular iiuu .. TU, iiuu .. TA, iiuu .. "تِ", iiuu .. A, iiuu .. A .. "تْ", --dual iiuu .. "تُمَا", iiuu .. AA, iiuu .. A .. "تَا", -- plural iiuu .. "نَا", iiuu .. "تُمْ", -- shadda + vowel diacritic ends up in the wrong order due to Unicode -- bug, so keep them separate to avoid this iiuu .. "تُن" .. SH .. A, UU .. ALIF, iiuu .. "نَ" } end -- past final-weak -ītu endings local past_endings_ii = make_past_endings_ii_uu(II) -- past final-weak -ūtu endings local past_endings_uu = make_past_endings_ii_uu(UU) ------------------------------------------------------------------------------- -- Sets of non-past prefixes and endings -- ------------------------------------------------------------------------------- local nonpast_prefix_consonants = { -- singular HAMZA, T, T, Y, T, -- dual T, Y, T, -- plural N, T, T, Y, Y } -- There are only five distinct endings in all non-past verbs. Make any set of non-past endings given these five -- distinct endings. local function make_nonpast_endings(null, fem, dual, pl, fempl) return { -- singular null, null, fem, null, null, -- dual dual, dual, dual, -- plural null, pl, fempl, pl, fempl } end -- endings for non-past indicative local ind_endings = make_nonpast_endings( U, II .. NA, AANI, UU .. NA, SK .. NA ) -- Make the endings for non-past subjunctive/jussive, given the vowel diacritic used in "null" endings -- (1s/2ms/3ms/3fs/1p). local function make_sub_juss_endings(dia_null) return make_nonpast_endings( dia_null, II, AA, UU .. ALIF, SK .. NA ) end -- endings for non-past subjunctive local sub_endings = make_sub_juss_endings(A) -- endings for non-past jussive local juss_endings = make_sub_juss_endings(SK) -- endings for alternative geminate non-past jussive in -a; same as subjunctive local juss_endings_alt_a = sub_endings -- endings for alternative geminate non-past jussive in -i local juss_endings_alt_i = make_sub_juss_endings(I) -- Endings for final-weak non-past indicative in -ā. Note that AY, AW and AAMAQ are global variables. local ind_endings_aa = make_nonpast_endings( AAMAQ, AYSK .. NA, AY .. AANI, AWSK .. NA, AYSK .. NA ) -- Make endings for final-weak non-past indicative in -ī or -ū; IIUU is ī or ū as appropriate. Note that II and UU -- are global variables. local function make_ind_endings_ii_uu(iiuu) return make_nonpast_endings( iiuu, II .. NA, iiuu .. AANI, UU .. NA, iiuu .. NA ) end -- endings for final-weak non-past indicative in -ī local ind_endings_ii = make_ind_endings_ii_uu(II) -- endings for final-weak non-past indicative in -ū local ind_endings_uu = make_ind_endings_ii_uu(UU) -- Endings for final-weak non-past subjunctive in -ā. Note that AY, AW, ALIF, AAMAQ are global variables. local sub_endings_aa = make_nonpast_endings( AAMAQ, AYSK, AY .. AA, AWSK .. ALIF, AYSK .. NA ) -- Make endings for final-weak non-past subjunctive in -ī or -ū. IIUU is ī or ū as appropriate. Note that AA, II, -- UU, ALIF are global variables. local function make_sub_endings_ii_uu(iiuu) return make_nonpast_endings( iiuu .. A, II, iiuu .. AA, UU .. ALIF, iiuu .. NA ) end -- endings for final-weak non-past subjunctive in -ī local sub_endings_ii = make_sub_endings_ii_uu(II) -- endings for final-weak non-past subjunctive in -ū local sub_endings_uu = make_sub_endings_ii_uu(UU) -- endings for final-weak non-past jussive in -ā local juss_endings_aa = make_nonpast_endings( A, AYSK, AY .. AA, AWSK .. ALIF, AYSK .. NA ) -- Make endings for final-weak non-past jussive in -ī or -ū. IU is short i or u, IIUU is long ī or ū as appropriate. -- Note that AA, II, UU, ALIF are global variables. local function make_juss_endings_ii_uu(iu, iiuu) return make_nonpast_endings( iu, II, iiuu .. AA, UU .. ALIF, iiuu .. NA ) end -- endings for final-weak non-past jussive in -ī local juss_endings_ii = make_juss_endings_ii_uu(I, II) -- endings for final-weak non-past jussive in -ū local juss_endings_uu = make_juss_endings_ii_uu(U, UU) ------------------------------------------------------------------------------- -- Sets of imperative endings -- ------------------------------------------------------------------------------- -- Extract the second person jussive endings to get corresponding imperative endings. local function imperative_endings_from_jussive(endings) return {endings[2], endings[3], endings[6], endings[10], endings[11]} end -- normal imperative endings local imp_endings = imperative_endings_from_jussive(juss_endings) -- alternative geminate imperative endings in -a local imp_endings_alt_a = imperative_endings_from_jussive(juss_endings_alt_a) -- alternative geminate imperative endings in -i local imp_endings_alt_i = imperative_endings_from_jussive(juss_endings_alt_i) -- final-weak imperative endings in -ā local imp_endings_aa = imperative_endings_from_jussive(juss_endings_aa) -- final-weak imperative endings in -ī local imp_endings_ii = imperative_endings_from_jussive(juss_endings_ii) -- final-weak imperative endings in -ū local imp_endings_uu = imperative_endings_from_jussive(juss_endings_uu) ------------------------------------------------------------------------------- -- Basic functions to inflect tenses -- ------------------------------------------------------------------------------- -- Add to `base` the inflections for the tense indicated by `tense` (the prefix in the slot names, e.g. 'past' -- or 'juss_pass'), formed by combining the `prefixes`, `stems` and `endings`. Each of `prefixes`, `stems` and -- `endings` is either a sequence of 5 (for the imperative) or 13 (for other tenses) abbreviated form lists (each of -- which is either a string, a form object, or a list of strings and/or form objects; see -- [[Module:inflection utilities]] for more info). Alternatively, any of `prefixes`, `stems` or `endings` can be a -- single-element list containing an abbreviated form list, with an additional key `all_same` set to true, or (as a -- special case) a single string; in the latter cases, the same value is used for all 5 or 13 slots. If existing -- inflections already exist, they will be added to, not overridden. `pnums` is the list of person/number slot name -- suffixes, which must match up with the elements in `prefixes`, `stems` and `endings` (i.e. 5 for imperative, 13 -- otherwise). local function inflect_tense_1(base, tense, prefixes, stems, endings, pnums) if not prefixes or not stems or not endings then return end local function verify_affixes(affixname, affixes) local function interr(msg) error(("Internal error: For tense '%s', '%s' %s: %s"):format(tense, affixname, msg, dump(affixes))) end if type(affixes) == "string" then -- do nothing elseif type(affixes) ~= "table" then interr("is not a table or string") elseif affixes.all_same then if #affixes ~= 1 then interr(("with all_same = true should have length 1 but has length %s"):format(#affixes)) end else if #affixes ~= #pnums then interr(("should have length %s but has length %s"):format(#pnums, #affixes)) end end end verify_affixes("prefixes", prefixes) verify_affixes("stems", stems) verify_affixes("endings", endings) local function get_affix(affixes, i) if type(affixes) == "string" then return affixes elseif affixes.all_same then return affixes[1] else return affixes[i] end end for i, pnum in ipairs(pnums) do local prefix = get_affix(prefixes, i) local stem = get_affix(stems, i) local ending = get_affix(endings, i) local slot = tense .. "_" .. pnum add3(base, slot, prefix, stem, ending) end end -- Add to `base` the inflections for the tense indicated by `tense` (the prefix in the slot names, e.g. 'past' -- or 'juss_pass'), formed by combining the `prefixes`, `stems` and `endings`. This is a simple wrapper around -- inflect_tense_1() that applies to all tenses other than the imperative; see inflect_tense_1() for more -- information about the parameters. local function inflect_tense(base, tense, prefixes, stems, endings) inflect_tense_1(base, tense, prefixes, stems, endings, all_person_number_list) end -- Like inflect_tense() but for the imperative, which has only five parts instead of 13 and no prefixes. local function inflect_tense_imp(base, stems, endings) inflect_tense_1(base, "imp", "", stems, endings, imp_person_number_list) end ------------------------------------------------------------------------------- -- Functions to inflect the past tense -- ------------------------------------------------------------------------------- -- Generate past verbs using specified vowel and consonant stems; works for sound, assimilated, hollow, and geminate -- verbs, active and passive. local function past_2stem_conj(base, tense, v_stem, c_stem, footnote_12) local passive = tense:find("_pass") and "_pass" or "" -- Override stems with user-specified stems if available. v_stem = override_stem_if_needed(base, "past" .. passive .. "_v", v_stem) local c_stem_12 = c_stem if footnote_12 then c_stem_12 = iut.combine_form_and_footnotes(c_stem_12, footnote_12) end c_stem_12 = override_stem_if_needed(base, "past" .. passive .. "_c", c_stem_12) local c_stem_3 = override_stem_if_needed(base, "past" .. passive .. "_c", c_stem) inflect_tense(base, tense, "", { -- singular c_stem_12, c_stem_12, c_stem_12, v_stem, v_stem, --dual c_stem_12, v_stem, v_stem, -- plural c_stem_12, c_stem_12, c_stem_12, v_stem, c_stem_3 }, past_endings) end -- Generate past verbs using single specified stem; works for sound and assimilated verbs, active and passive. local function past_1stem_conj(base, tense, stem) past_2stem_conj(base, tense, stem, stem) end ------------------------------------------------------------------------------- -- Functions to inflect non-past tenses -- ------------------------------------------------------------------------------- -- Generate non-past conjugation, with two stems, for vowel-initial and consonant-initial endings, respectively. -- Useful for active and passive; for all forms; for all weaknesses (sound, assimilated, hollow, final-weak and -- geminate) and for all types of non-past (indicative, subjunctive, jussive) except for the imperative. (There is a -- separate wrapper function below for geminate jussives because they have three alternants.) Both stems may be the -- same, e.g. for sound verbs. -- `prefix_vowel` will be either "a" or "u". `endings` should be an array of 13 items. If `endings` is nil or -- omitted, infer the endings from the tense. If `jussive` is true, or `endings` is nil and `tense` indicatives -- jussive, use the jussive pattern of vowel/consonant stems (different from the normal ones). local function nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, endings, jussive) local passive = tense:find("_pass") and "_pass" or "" -- Override stems with user-specified stems if available. v_stem = override_stem_if_needed(base, "nonpast" .. passive .. "_v", v_stem and q(dia[prefix_vowel], v_stem) or nil) c_stem = override_stem_if_needed(base, "nonpast" .. passive .. "_c", c_stem and q(dia[prefix_vowel], c_stem) or nil) if not endings then if tense:find("^ind") then endings = ind_endings elseif tense:find("^sub") then endings = sub_endings elseif tense:find("^juss") then jussive = true endings = juss_endings else error("Internal error: Unrecognized tense '" .. tense .."'") end end if not jussive then inflect_tense(base, tense, nonpast_prefix_consonants, { -- singular v_stem, v_stem, v_stem, v_stem, v_stem, --dual v_stem, v_stem, v_stem, -- plural v_stem, v_stem, c_stem, v_stem, c_stem }, endings) else inflect_tense(base, tense, nonpast_prefix_consonants, { -- singular -- 'adlul, tadlul, tadullī, yadlul, tadlul c_stem, c_stem, v_stem, c_stem, c_stem, --dual -- tadullā, yadullā, tadullā v_stem, v_stem, v_stem, -- plural -- nadlul, tadullū, tadlulna, yadullū, yadlulna c_stem, v_stem, c_stem, v_stem, c_stem }, endings) end end -- Generate non-past conjugation with one stem (no distinct stems for vowel-initial and consonant-initial endings). -- See nonpast_2stem_conj(). local function nonpast_1stem_conj(base, tense, prefix_vowel, stem, endings, jussive) nonpast_2stem_conj(base, tense, prefix_vowel, stem, stem, endings, jussive) end -- Generate active/passive jussive geminative. There are three alternants, two with terminations -a and -i and one -- in a null termination with a distinct pattern of vowel/consonant stem usage. See nonpast_2stem_conj() for a -- description of the arguments. local function jussive_gem_conj(base, tense, prefix_vowel, v_stem, c_stem) -- alternative in -a nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings_alt_a) -- alternative in -i nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings_alt_i) -- alternative in -null; requires different combination of v_stem and -- c_stem since the null endings require the c_stem (e.g. "tadlul" here) -- whereas the corresponding endings above in -a or -i require the v_stem -- (e.g. "tadulla, tadulli" above) nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings, "jussive") end ------------------------------------------------------------------------------- -- Functions to inflect the imperative -- ------------------------------------------------------------------------------- -- Generate imperative conjugation, with two stems, for vowel-initial and consonant-initial endings, respectively. -- Useful for all forms, and for all weaknesses other than final-weak. Note that the two stems may be the same -- (specifically for sound and assimilated verbs). If `endings` is nil or omitted, use `imp_endings`. If `alt_gem` -- is specified, use the pattern of vowel and consonant stems appropriate for the alternative geminate imperatives -- that use a null ending of -a or -i instead of an empty ending. local function make_2stem_imperative(base, v_stem, c_stem, endings, alt_gem) endings = endings or imp_endings -- Override stems with user-specified stems if available. v_stem = override_stem_if_needed(base, "imp_v", v_stem) c_stem = override_stem_if_needed(base, "imp_c", c_stem) if alt_gem then inflect_tense_imp(base, {v_stem, v_stem, v_stem, v_stem, c_stem}, endings) else inflect_tense_imp(base, {c_stem, v_stem, v_stem, v_stem, c_stem}, endings) end end -- Generate imperative parts for sound or assimilated verbs. local function make_1stem_imperative(base, stem) make_2stem_imperative(base, stem, stem) end -- Generate imperative parts for geminate verbs form I (also IV, VII, VIII, X). local function make_gem_imperative(base, v_stem, c_stem) make_2stem_imperative(base, v_stem, c_stem, imp_endings_alt_a, "alt gem") make_2stem_imperative(base, v_stem, c_stem, imp_endings_alt_i, "alt gem") make_2stem_imperative(base, v_stem, c_stem) end ------------------------------------------------------------------------------- -- Functions to inflect entire verbs -- ------------------------------------------------------------------------------- -- Generate finite parts of a sound verb (also works for assimilated verbs) from five stems (past and non-past, -- active and passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u"). local function make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel) past_1stem_conj(base, "past", past_stem) past_1stem_conj(base, "past_pass", past_pass_stem) nonpast_1stem_conj(base, "ind", prefix_vowel, nonpast_stem) nonpast_1stem_conj(base, "sub", prefix_vowel, nonpast_stem) nonpast_1stem_conj(base, "juss", prefix_vowel, nonpast_stem) nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem) nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem) nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem) make_1stem_imperative(base, imp_stem) end local function past_final_weak_endings_from_vowel(vowel) if vowel == "ay" then return past_endings_ay elseif vowel == "aw" then return past_endings_aw elseif vowel == "ī" then return past_endings_ii elseif vowel == "ū" then return past_endings_uu elseif not vowel then return nil else error(("Internal error: Unrecognized past final-weak vowel spec '%s'"):format(vowel)) end end local function nonpast_final_weak_endings_from_vowel(vowel) if vowel == "ā" then return ind_endings_aa, sub_endings_aa, juss_endings_aa, imp_endings_aa elseif vowel == "ī" then return ind_endings_ii, sub_endings_ii, juss_endings_ii, imp_endings_ii elseif vowel == "ū" then return ind_endings_uu, sub_endings_uu, juss_endings_uu, imp_endings_uu elseif not vowel then return nil else error(("Internal error: Unrecognized non-past final-weak vowel spec '%s'"):format(vowel)) end end -- Generate finite parts of a final-weak verb from five stems (past and non-past, active and passive, plus -- imperative), the past active ending vowel (ay, aw, ī or ū), the non-past active ending vowel (ā, ī or ū) and the -- prefix vowel in the active non-past (a or u). local function make_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, past_ending_vowel, nonpast_ending_vowel, prefix_vowel) past_stem = override_stem_if_needed(base, "past", past_stem) past_pass_stem = override_stem_if_needed(base, "past_pass", past_pass_stem) -- Don't call override_stem_if_needed() here for non-past stems; it's called in nonpast_2stem_conj(). imp_stem = override_stem_if_needed(base, "imp", imp_stem) -- + not supported for ending vowel overrides past_ending_vowel = base.stem_overrides.past_final_weak_vowel or past_ending_vowel local past_pass_ending_vowel = base.stem_overrides.past_pass_final_weak_vowel or "ī" nonpast_ending_vowel = base.stem_overrides.nonpast_final_weak_vowel or nonpast_ending_vowel local nonpast_pass_ending_vowel = base.stem_overrides.nonpast_pass_final_weak_vowel or "ā" local past_endings = past_final_weak_endings_from_vowel(past_ending_vowel) local past_pass_endings = past_final_weak_endings_from_vowel(past_pass_ending_vowel) local ind_endings, sub_endings, juss_endings, imp_endings = nonpast_final_weak_endings_from_vowel(nonpast_ending_vowel) local ind_pass_endings, sub_pass_endings, juss_pass_endings = nonpast_final_weak_endings_from_vowel(nonpast_pass_ending_vowel) inflect_tense(base, "past", "", {past_stem, all_same = 1}, past_endings) inflect_tense(base, "past_pass", "", {past_pass_stem, all_same = 1}, past_pass_endings) nonpast_1stem_conj(base, "ind", prefix_vowel, nonpast_stem, ind_endings) nonpast_1stem_conj(base, "sub", prefix_vowel, nonpast_stem, sub_endings) nonpast_1stem_conj(base, "juss", prefix_vowel, nonpast_stem, juss_endings) nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem, ind_pass_endings) nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem, sub_pass_endings) nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem, juss_pass_endings) inflect_tense_imp(base, {imp_stem, all_same = 1}, imp_endings) end -- Generate finite parts of an augmented (form II+) final-weak verb from five stems (past and non-past, active and -- passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u") and a flag indicating if it -- behaves like a form V/VI verb in taking non-past endings in -ā instead of -ī. local function make_augmented_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel, form56) make_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, "ay", form56 and "ā" or "ī", prefix_vowel) end -- Generate finite parts of an augmented (form II+) sound or final-weak verb, given: -- * `base` (conjugation data structure); -- * `vowel_spec` (radicals, weakness); -- * `past_stem_base` (active past stem minus last syllable (= -al or -ā)); -- * `nonpast_stem_base` (non-past stem minus last syllable (= -al/-il or -ā/-ī); -- * `past_pass_stem_base` (passive past stem minus last syllable (= -il or -ī)); -- * `vn` (verbal noun). local function make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) insert_form_or_forms(base, "vn", vn) local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3 local final_weak = is_final_weak(base, vowel_spec) local prefix_vowel = prefix_vowel_from_vform(base.verb_form) local form56 = vform_nonpast_a_vowel(base.verb_form) local a_base_suffix = final_weak and "" or q(A, lastrad) local i_base_suffix = final_weak and "" or q(I, lastrad) -- past and non-past stems, active and passive local past_stem = q(past_stem_base, a_base_suffix) -- In forms 5 and 6, non-past has /a/ as last stem vowel in the non-past -- in both active and passive, but /i/ in the active participle and /a/ -- in the passive participle. Elsewhere, consistent /i/ in active non-past -- and participle, consistent /a/ in passive non-past and participle. -- Hence, forms 5 and 6 differ only in the non-past active (but not -- active participle), so we have to split the finite non-past stem and -- active participle stem. local nonpast_stem = q(nonpast_stem_base, form56 and a_base_suffix or i_base_suffix) local ap_stem = q(nonpast_stem_base, i_base_suffix) local past_pass_stem = q(past_pass_stem_base, i_base_suffix) local nonpast_pass_stem = q(nonpast_stem_base, a_base_suffix) -- imperative stem local imp_stem = q(past_stem_base, form56 and a_base_suffix or i_base_suffix) -- make parts if final_weak then make_augmented_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel, form56) else make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel) end -- active and passive participle if final_weak then insert_form_or_forms(base, "ap", q(MU, ap_stem, IN)) insert_form_or_forms(base, "pp", q(MU, nonpast_pass_stem, AN, AMAQ)) else insert_form_or_forms(base, "ap", q(MU, ap_stem)) insert_form_or_forms(base, "pp", q(MU, nonpast_pass_stem)) end end -- Generate finite parts of a hollow or geminate verb from ten stems (vowel and consonant stems for each of past and -- non-past, active and passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u"), plus a -- flag indicating if we are a geminate verb. local function make_hollow_geminate_verb(base, geminate, past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel, altgem_note) past_2stem_conj(base, "past", past_v_stem, past_c_stem, altgem_note) past_2stem_conj(base, "past_pass", past_pass_v_stem, past_pass_c_stem) nonpast_2stem_conj(base, "ind", prefix_vowel, nonpast_v_stem, nonpast_c_stem) nonpast_2stem_conj(base, "sub", prefix_vowel, nonpast_v_stem, nonpast_c_stem) nonpast_2stem_conj(base, "ind_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) nonpast_2stem_conj(base, "sub_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) if geminate then jussive_gem_conj(base, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem) jussive_gem_conj(base, "juss_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) make_gem_imperative(base, imp_v_stem, imp_c_stem) else nonpast_2stem_conj(base, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem) nonpast_2stem_conj(base, "juss_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem) make_2stem_imperative(base, imp_v_stem, imp_c_stem) end end -- Generate finite parts of an augmented (form II+) hollow verb, given: -- * `base` (conjugation data structure); -- * `vowel_spec` (radicals, weakness); -- * `past_stem_base` (invariable part of active past stem); -- * `nonpast_stem_base` (invariable part of nonpast stem); -- * `past_pass_stem_base` (invariable part of passive past stem); -- * `vn` (verbal noun). local function make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) insert_form_or_forms(base, "vn", vn) local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3 local form410 = base.verb_form == "IV" or base.verb_form == "X" local prefix_vowel = prefix_vowel_from_vform(base.verb_form) local a_base_suffix_v, a_base_suffix_c local i_base_suffix_v, i_base_suffix_c a_base_suffix_v = q(AA, lastrad) -- 'af-āl-a, inf-āl-a a_base_suffix_c = q(A, lastrad) -- 'af-al-tu, inf-al-tu i_base_suffix_v = q(II, lastrad) -- 'uf-īl-a, unf-īl-a i_base_suffix_c = q(I, lastrad) -- 'uf-il-tu, unf-il-tu -- past and non-past stems, active and passive, for vowel-initial and -- consonant-initial endings local past_v_stem = q(past_stem_base, a_base_suffix_v) local past_c_stem = q(past_stem_base, a_base_suffix_c) -- yu-f-īl-u, ya-staf-īl-u but yanf-āl-u, yaft-āl-u local nonpast_v_stem = q(nonpast_stem_base, form410 and i_base_suffix_v or a_base_suffix_v) local nonpast_c_stem = q(nonpast_stem_base, form410 and i_base_suffix_c or a_base_suffix_c) local past_pass_v_stem = q(past_pass_stem_base, i_base_suffix_v) local past_pass_c_stem = q(past_pass_stem_base, i_base_suffix_c) local nonpast_pass_v_stem = q(nonpast_stem_base, a_base_suffix_v) local nonpast_pass_c_stem = q(nonpast_stem_base, a_base_suffix_c) -- imperative stem local imp_v_stem = q(past_stem_base, form410 and i_base_suffix_v or a_base_suffix_v) local imp_c_stem = q(past_stem_base, form410 and i_base_suffix_c or a_base_suffix_c) -- make parts make_hollow_geminate_verb(base, false, past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel) -- active participle insert_form_or_forms(base, "ap", q(MU, nonpast_v_stem)) -- passive participle insert_form_or_forms(base, "pp", q(MU, nonpast_pass_v_stem)) end -- Generate finite parts of an augmented (form II+) geminate verb, given: -- * `base` (conjugation data structure); -- * `vowel_spec` (radicals, weakness); -- * `past_stem_base` (invariable part of active past stem; this and the stem bases below will end with a consonant -- for forms IV, X, IVq, and a short vowel for the others); -- * `nonpast_stem_base` (invariable part of nonpast stem); -- * `past_pass_stem_base` (invariable part of passive past stem); -- * `vn` (verbal noun); -- * `altgem_note` (footnote to add to active past 1/2-person forms, when alternative forms are supplied [form X]). local function make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn, altgem_note) insert_form_or_forms(base, "vn", vn) local vform = base.verb_form local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3 local prefix_vowel = prefix_vowel_from_vform(vform) local a_base_suffix_v, a_base_suffix_c local i_base_suffix_v, i_base_suffix_c if vform == "IV" or vform == "X" or vform == "IVq" then a_base_suffix_v = q(A, lastrad, SH) -- 'af-all a_base_suffix_c = q(SK, lastrad, A, lastrad) -- 'af-lal i_base_suffix_v = q(I, lastrad, SH) -- yuf-ill i_base_suffix_c = q(SK, lastrad, I, lastrad) -- yuf-lil else a_base_suffix_v = q(lastrad, SH) -- fā-ll, infa-ll a_base_suffix_c = q(lastrad, A, lastrad) -- fā-lal, infa-lal i_base_suffix_v = q(lastrad, SH) -- yufā-ll, yanfa-ll i_base_suffix_c = q(lastrad, I, lastrad) -- yufā-lil, yanfa-lil end -- past and non-past stems, active and passive, for vowel-initial and -- consonant-initial endings local past_v_stem = q(past_stem_base, a_base_suffix_v) local past_c_stem = q(past_stem_base, a_base_suffix_c) local nonpast_v_stem = q(nonpast_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_v or i_base_suffix_v) local nonpast_c_stem = q(nonpast_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_c or i_base_suffix_c) -- NOTE: Formerly had a comment that "vform III and VI passive past do not have contracted parts, only -- uncontracted parts, which are added separately by those functions". This is based on Mace -- "Arabic Verbs and Essential Grammar" (1999) entry 63 (continued), which shows passive ḥūjija but no ḥūjja; -- but that is apparently a mistake, as (1) verb tables in other books do show contracted passive parts for -- these forms; (2) there is no mention of such an exception on p. 99, which explains how geminate ("doubled") -- verbs work (on the contrary, it says "The contracted and uncontracted pairs (see above) are found all -- over Forms III and VI of the doubled verbs"). local past_pass_v_stem = q(past_pass_stem_base, i_base_suffix_v) local past_pass_c_stem = q(past_pass_stem_base, i_base_suffix_c) local nonpast_pass_v_stem = q(nonpast_stem_base, a_base_suffix_v) local nonpast_pass_c_stem = q(nonpast_stem_base, a_base_suffix_c) -- imperative stem local imp_v_stem = q(past_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_v or i_base_suffix_v) local imp_c_stem = q(past_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_c or i_base_suffix_c) -- make parts make_hollow_geminate_verb(base, "geminate", past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel, altgem_note) -- active participle insert_form_or_forms(base, "ap", q(MU, nonpast_v_stem)) -- passive participle insert_form_or_forms(base, "pp", q(MU, nonpast_pass_v_stem)) end ------------------------------------------------------------------------------- -- Conjugation functions for specific conjugation types -- ------------------------------------------------------------------------------- local function form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1) local imp_vowel = map_vowel(nonpast_vowel, function(vow) if vow == A or vow == I then return I elseif vow == U then return U elseif not skip_slot(base, "imp_2ms") then error(("Internal error: Non-past vowel %s isn't a, i, or u, should have been caught earlier"):format( dump(nonpast_vowel))) else -- Passive-only; imperative won't ever be displayed so it doesn't matter. return I end end) -- Mace ("Arabic Verbs and Essentials of Grammar" p. 63: [https://archive.org/details/arabicverbsessen00john/page/62/mode/2up]) -- claims that initial hamza is assimilated/elided into a long vowel in the form-I imperative, but apparently -- this isn't corrrect. local vowel_on_alif = map_vowel(imp_vowel, function(vow) return ALIF .. vow end) return q(vowel_on_alif, rad1, SK) end -- Implement form-I sound or assimilated verb. ASSIMILATED is true for assimilated verbs. local function make_form_i_sound_assimilated_verb(base, vowel_spec, assimilated) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied -- past and non-past stems, active and passive local past_stem = q(rad1, A, rad2, past_vowel, rad3) local nonpast_stem = assimilated and q(rad2, nonpast_vowel, rad3) or q(rad1, SK, rad2, nonpast_vowel, rad3) local past_pass_stem = q(rad1, U, rad2, I, rad3) local nonpast_pass_stem = q(rad1, SK, rad2, A, rad3) -- imperative stem -- check for irregular verb with reduced imperative (أَخَذَ or أَكَلَ or أَمَرَ) local reducedimp = reduced_imperative_verb(rad1, rad2, rad3) if reducedimp then base.irregular = true end local imp_stem_suffix = q(rad2, nonpast_vowel, rad3) local long_imp_stem_base = form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1) local short_imp_stem_base = "" local imp_stem = q((assimilated or reducedimp) and "" or long_imp_stem_base, imp_stem_suffix) -- make parts make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, "a") if reducedimp == "shortlong" then make_1stem_imperative(base, iut.combine_form_and_footnotes(q(long_imp_stem_base, imp_stem_suffix), mw.getCurrentFrame():preprocess("[used especially with a clitic such as {{m|ar|فَ}} or {{m|ar|وَ}}]"))) end -- Check for irregular verb سَأَلَ with alternative jussive and imperative. Calling this after make_sound_verb() -- adds additional entries to the paradigm parts. if saal_radicals(rad1, rad2, rad3) then base.irregular = true nonpast_1stem_conj(base, "juss", "a", "سَل") nonpast_1stem_conj(base, "juss_pass", "u", "سَل") make_1stem_imperative(base, "سَل") end -- Active participle. insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, I, rad3)) -- Insert alternative active participle (stative type I) فَعِيل. Since not all verbs have this, we require that -- verbs that do have it specify it explicitly; a shortcut ++ is provided to make this easier (e.g. <ap:++> to -- indicate that the alternative form should be used for the active participle, <ap:+,++> to indicate that both -- forms can be used, and <ap:-> to indicate that there is no active participle). The same form is used for -- secondary default passive participle. insert_ap2_pp2(base, q(rad1, A, rad2, II, rad3)) -- Active participle, stative type II فَعِل (+++). insert_form_or_forms(base, "ap3", q(rad1, A, rad2, I, rad3)) -- Active participle, color/defect أَفْعَل (+cd). insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, A, rad3)) -- Active participle, -ān فَعْلَان (+an). insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, UU, rad3)) end conjugations["I-sound"] = function(base, vowel_spec) make_form_i_sound_assimilated_verb(base, vowel_spec, false) end conjugations["none-sound"] = function(base, vowel_spec) -- All default stems are nil. make_sound_verb(base) end conjugations["none-hollow"] = function(base, vowel_spec) -- All default stems are nil. make_hollow_geminate_verb(base, false) end conjugations["none-geminate"] = function(base, vowel_spec) -- All default stems are nil. make_hollow_geminate_verb(base, "geminate") end conjugations["none-final-weak"] = function(base, vowel_spec) -- All default stems are nil. make_final_weak_verb(base) end conjugations["I-assimilated"] = function(base, vowel_spec) make_form_i_sound_assimilated_verb(base, vowel_spec, "assimilated") end local function make_form_i_hayy_verb(base, vowel_spec) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied base.irregular = true -- past and non-past stems, active and passive, and imperative stem local past_c_stem = "حَيِي" local past_v_stem_long = past_c_stem local past_v_stem_short = "حَيّ" local past_pass_c_stem = "حُيِي" local past_pass_v_stem_long = past_pass_c_stem local past_pass_v_stem_short = "حُيّ" local nonpast_stem = "حْي" local nonpast_pass_stem = nonpast_stem local imp_stem = _I .. nonpast_stem -- make parts past_2stem_conj(base, "past", {}, past_c_stem) past_2stem_conj(base, "past_pass", {}, past_pass_c_stem) local variant = vowel_spec.variant or "both" if variant == "short" or variant == "both" then past_2stem_conj(base, "past", past_v_stem_short, {}) past_2stem_conj(base, "past_pass", past_pass_v_stem_short, {}) end function inflect_long_variant(tense, long_stem, short_stem) inflect_tense_1(base, tense, "", {long_stem, long_stem, long_stem, long_stem, short_stem}, {past_endings[4], past_endings[5], past_endings[7], past_endings[8], past_endings[12]}, {"3ms", "3fs", "3md", "3fd", "3mp"}) end if variant == "long" or variant == "both" then inflect_long_variant("past", past_v_stem_long, past_v_stem_short) inflect_long_variant("past_pass", past_pass_v_stem_long, past_pass_v_stem_short) end nonpast_1stem_conj(base, "ind", "a", nonpast_stem, ind_endings_aa) nonpast_1stem_conj(base, "sub", "a", nonpast_stem, sub_endings_aa) nonpast_1stem_conj(base, "juss", "a", nonpast_stem, juss_endings_aa) nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem, ind_endings_aa) nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem, sub_endings_aa) nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem, juss_endings_aa) inflect_tense_imp(base, {imp_stem, all_same = 1}, imp_endings_aa) -- active and passive participles apparently do not exist for this verb end -- Implement form-I final-weak assimilated+final-weak verb. ASSIMILATED is true for assimilated verbs. local function make_form_i_final_weak_verb(base, vowel_spec, assimilated) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- حَيَّ or حَيِيَ is weird enough that we handle it as a separate function. if hayy_radicals(rad1, rad2, rad3) then make_form_i_hayy_verb(base, vowel_spec) return end -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied. -- Past and non-past stems, active and passive, and imperative stem. local past_stem = q(rad1, A, rad2) local past_pass_stem = q(rad1, U, rad2) local nonpast_stem, nonpast_pass_stem, imp_stem if raa_radicals(rad1, rad2, rad3) then base.irregular = true nonpast_stem = rad1 nonpast_pass_stem = rad1 imp_stem = rad1 else nonpast_pass_stem = q(rad1, SK, rad2) if assimilated then nonpast_stem = rad2 imp_stem = rad2 else nonpast_stem = nonpast_pass_stem imp_stem = q(form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1), rad2) end end -- Make parts. local past_ending_vowel = req(rad3, Y) and req(past_vowel, A) and "ay" or req(rad3, W) and req(past_vowel, A) and "aw" or req(past_vowel, I) and "ī" or "ū" -- Try to preserve footnotes attached to the third radical and/or past and/or non-past vowels. local past_footnotes = iut.combine_footnotes(rget_footnotes(rad3), rget_footnotes(past_vowel)) local nonpast_ending_vowel = req(nonpast_vowel, A) and "ā" or req(nonpast_vowel, I) and "ī" or "ū" local nonpast_footnotes = iut.combine_footnotes(rget_footnotes(rad3), rget_footnotes(nonpast_vowel)) make_final_weak_verb(base, iut.combine_form_and_footnotes(past_stem, past_footnotes), iut.combine_form_and_footnotes(past_pass_stem, past_footnotes), iut.combine_form_and_footnotes(nonpast_stem, nonpast_footnotes), iut.combine_form_and_footnotes(nonpast_pass_stem, nonpast_footnotes), iut.combine_form_and_footnotes(imp_stem, nonpast_footnotes), past_ending_vowel, nonpast_ending_vowel, "a") -- Active participle. insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, IN)) -- Active participle, stative type I فَعِيّ (++). FIXME: Is this correct when rad3 is W? insert_ap2_pp2(base, q(rad1, A, rad2, II, SH)) -- Active participle, stative type II فَعٍ (+++). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "ap3", q(rad1, A, rad2, IN)) -- Active participle, color/defect أَفْعَى (+cd). insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, AAMAQ)) -- Active participle, -ān فَعْيَان or فَعْوَان (+an). -- FIXME: Any examples of this for both rad3 = W and y to verify it's correct? insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, req(rad3, Y) and II or UU, SH)) end conjugations["I-final-weak"] = function(base, vowel_spec) make_form_i_final_weak_verb(base, vowel_spec, false) end conjugations["I-assimilated+final-weak"] = function(base, vowel_spec) make_form_i_final_weak_verb(base, vowel_spec, "assimilated") end conjugations["I-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- In some sense, hollow vowels i~i and u~u are more "correct" than a~i and a~u, but the latter follow the -- pattern of other form-I verbs, so we map i~i to a~i and u~u to a~u in infer_radicals(). Now however we have -- to undo this to get the actual past vowel based on the non-past vowel. if req(past_vowel, A) then past_vowel = map_vowel(past_vowel, function(vow) return req(nonpast_vowel, A) and I or rget(nonpast_vowel) end) end local lengthened_nonpast = map_vowel(nonpast_vowel, function(vow) return vow == U and UU or vow == I and II or AA end) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied. -- active past stems - vowel (v) and consonant (c) local past_v_stem = q(rad1, AA, rad3) local past_c_stem = q(rad1, past_vowel, rad3) -- active non-past stems - vowel (v) and consonant (c) local nonpast_v_stem = q(rad1, lengthened_nonpast, rad3) local nonpast_c_stem = q(rad1, nonpast_vowel, rad3) -- passive past stems - vowel (v) and consonant (c) -- 'ufīla, 'ufiltu local past_pass_v_stem = q(rad1, II, rad3) local past_pass_c_stem = q(rad1, I, rad3) -- passive non-past stems - vowel (v) and consonant (c) -- yufāla/yufalna -- stem is built differently but conjugation is identical to sound verbs local nonpast_pass_v_stem = q(rad1, AA, rad3) local nonpast_pass_c_stem = q(rad1, A, rad3) -- imperative stem local imp_v_stem = nonpast_v_stem local imp_c_stem = nonpast_c_stem -- make parts make_hollow_geminate_verb(base, false, past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, "a") if kaan_radicals(rad1, rad2, rad3) then local endings = make_nonpast_endings(U, {}, {}, {}, {}) inflect_tense(base, "juss", nonpast_prefix_consonants, q(A, rad1), endings) base.irregular = true end -- Active participle. insert_form_or_forms(base, "ap1", req(rad3, HAMZA) and q(rad1, AA, HAMZA, IN) or q(rad1, AA, HAMZA, I, rad3)) -- Active participle, stative type I فَيِّد (++). FIXME: Any examples of this to verify it's correct? insert_ap2_pp2(base, q(rad1, A, Y, SH, I, rad3)) -- Active participle, stative type II فَيِد (+++). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "ap3", q(rad1, A, Y, I, rad3)) -- Active participle, color/defect أَفّيَد or أَفّوَد (+cd). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, A, rad3)) -- Active participle, -ān فَيْدَان or فَوْدَان (+an). Example: جَاعَ "to be hungry", act part جَوْعَان insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, req(rad2, Y) and II or UU, rad3)) end conjugations["I-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec) -- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied. -- active past stems - vowel (v) and consonant (c) local past_v_stem = q(rad1, A, rad2, SH) local past_c_stem = q(rad1, A, rad2, past_vowel, rad2) -- active non-past stems - vowel (v) and consonant (c) local nonpast_v_stem = q(rad1, nonpast_vowel, rad2, SH) local nonpast_c_stem = q(rad1, SK, rad2, nonpast_vowel, rad2) -- passive past stems - vowel (v) and consonant (c) -- dulla/dulilta local past_pass_v_stem = q(rad1, U, rad2, SH) local past_pass_c_stem = q(rad1, U, rad2, I, rad2) -- passive non-past stems - vowel (v) and consonant (c) --yudallu/yudlalna -- stem is built differently but conjugation is identical to sound verbs local nonpast_pass_v_stem = q(rad1, A, rad2, SH) local nonpast_pass_c_stem = q(rad1, SK, rad2, A, rad2) -- imperative stem local imp_v_stem = q(rad1, nonpast_vowel, rad2, SH) local imp_c_stem = q(form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1), rad2, nonpast_vowel, rad2) -- make parts make_hollow_geminate_verb(base, "geminate", past_v_stem, past_c_stem, past_pass_v_stem, past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem, imp_c_stem, "a") -- Active participle. insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, SH)) -- Active participle, stative type I فَعِيع (++). FIXME: Any examples of this to verify it's correct? insert_ap2_pp2(base, q(rad1, A, rad2, II, rad2)) -- Active participle, stative type II فَعّ (+++). Example: بَرَّ "to be pious", active participle بَرّ insert_form_or_forms(base, "ap3", q(rad1, A, rad2, SH)) -- Active participle, color/defect أَفَعّ (+cd). -- Example: لَصَّ "to be thievish, to steal repeatedly", active participle أَلَصّ. insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, A, rad2, SH)) -- Active participle, -ān فَعَّان (+an). FIXME: Any examples of this to verify it's correct? insert_form_or_forms(base, "apan", q(rad1, A, rad2, SH, AAN)) -- Passive participle. insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, UU, rad2)) end -- Return the ta- (active, past and non-past) and tu- (passive past) prefixes for a form II/III/V/VI verb. -- Form V and VI verbs normally use ta- and tu-, but reduced (base.reduced) verbs use different prefixes. Form II -- and III verbs have no prefix. local function form_ii_iii_v_vi_ta_tu_prefix(base, rad1) local vform = base.verb_form if vform == "V" or vform == "VI" then if base.reduced then -- To simplify the code, we generate two rad1's with a sukūn between them, which is cleaned up in -- postprocessing. return q(_I, rad1, SK), q(rad1, SK), q(_U, rad1, SK) else return TA, TA, TU end else return "", "", "" end end -- Make form II or V sound or final-weak verb. local function make_form_ii_v_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local vform = base.verb_form local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1) local vn = vform == "V" and q(ta_past_prefix, rad1, A, rad2, SH, final_weak and IN or q(U, rad3)) or q(TA, rad1, SK, rad2, II, final_weak and AH or rad3) -- various stem bases local past_stem_base = q(ta_past_prefix, rad1, A, rad2, SH) local nonpast_stem_base = q(ta_nonpast_prefix, rad1, A, rad2, SH) local past_pass_stem_base = q(tu_past_prefix, rad1, U, rad2, SH) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["II-sound"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end conjugations["II-final-weak"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end local function make_form_iii_alt_vn(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) -- Insert alternative verbal noun فِعَال. Since not all verbs have this, we require that verbs that do have it -- specify it explicitly; a shortcut ++ is provided to make this easier (e.g. <vn:+,++> to indicate that -- both the normal verbal noun مُفَاعَلَة and secondary verbal noun فِعَال are available). insert_form_or_forms(base, "vn2", q(rad1, I, rad2, AA, final_weak and HAMZA or rad3)) end -- Make form III or VI sound or final-weak verb. local function make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local vform = base.verb_form local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1) local vn = vform == "VI" and q(ta_past_prefix, rad1, AA, rad2, final_weak and IN or q(U, rad3)) or q(MU, rad1, AA, rad2, final_weak and AAH or q(A, rad3, AH)) -- various stem bases local past_stem_base = q(ta_past_prefix, rad1, AA, rad2) local nonpast_stem_base = q(ta_nonpast_prefix, rad1, AA, rad2) local past_pass_stem_base = q(tu_past_prefix, rad1, UU, rad2) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) if vform == "III" then make_form_iii_alt_vn(base, vowel_spec) end end conjugations["III-sound"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end conjugations["III-final-weak"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end -- Make form III or VI geminate verb. local function make_form_iii_vi_geminate_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vform = base.verb_form local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1) -- Alternative verbal noun فِعَال will be inserted when we add sound parts below. local vn = vform == "VI" and q(ta_past_prefix, rad1, AA, rad2, SH) or q(MU, rad1, AA, rad2, SH, AH) -- Various stem bases. local past_stem_base = q(ta_past_prefix, rad1, AA) local nonpast_stem_base = q(ta_nonpast_prefix, rad1, AA) local past_pass_stem_base = q(tu_past_prefix, rad1, UU) -- Make parts. local variant = vowel_spec.variant or "short" if variant == "short" or variant == "both" then make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Also add alternative sound (non-compressed) parts. This will lead to some duplicate entries, but they are -- removed during addition. if variant == "long" or variant == "both" then make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) elseif vform == "III" then -- Still need to add the alternative form-III verbal noun. make_form_iii_alt_vn(base, vowel_spec) end end conjugations["III-geminate"] = function(base, vowel_spec) make_form_iii_vi_geminate_verb(base, vowel_spec) end -- Make form IV sound or final-weak verb. local function make_form_iv_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) -- core of stem base, minus stem prefixes local stem_core -- check for irregular verb أَرَى local is_raa = raa_radicals(rad1, rad2, rad3) if is_raa then base.irregular = true stem_core = rad1 else stem_core = q(rad1, SK, rad2) end -- verbal noun local vn = is_raa and q(HAMZA, I, stem_core, AA, HAMZA, AH) or q(HAMZA, I, stem_core, AA, final_weak and HAMZA or rad3) -- various stem bases local past_stem_base = q(HAMZA, A, stem_core) local nonpast_stem_base = stem_core local past_pass_stem_base = q(HAMZA, U, stem_core) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IV-sound"] = function(base, vowel_spec) make_form_iv_sound_final_weak_verb(base, vowel_spec) end conjugations["IV-final-weak"] = function(base, vowel_spec) make_form_iv_sound_final_weak_verb(base, vowel_spec) end conjugations["IV-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) -- verbal noun local vn = q(HAMZA, I, rad1, AA, rad3, AH) -- various stem bases local past_stem_base = q(HAMZA, A, rad1) local nonpast_stem_base = rad1 local past_pass_stem_base = q(HAMZA, U, rad1) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IV-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(HAMZA, I, rad1, SK, rad2, AA, rad2) -- various stem bases local past_stem_base = q(HAMZA, A, rad1) local nonpast_stem_base = rad1 local past_pass_stem_base = q(HAMZA, U, rad1) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["V-sound"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end conjugations["V-final-weak"] = function(base, vowel_spec) make_form_ii_v_sound_final_weak_verb(base, vowel_spec) end conjugations["VI-sound"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end conjugations["VI-final-weak"] = function(base, vowel_spec) make_form_iii_vi_sound_final_weak_verb(base, vowel_spec) end conjugations["VI-geminate"] = function(base, vowel_spec) make_form_iii_vi_geminate_verb(base, vowel_spec) end -- Make a verbal noun of the general form that applies to forms VII and above. RAD12 is the first consonant cluster -- (after initial اِ) and RAD34 is the second consonant cluster. RAD5 is the final consonant. local function high_form_verbal_noun(rad12, rad34, rad5) return q(_I, rad12, I, rad34, AA, rad5) end -- Populate a sound or final-weak verb for any of the various high-numbered augmented forms (form VII and up) that -- have up to 5 consonants in two clusters in the stem and the same pattern of vowels between. Some of these -- consonants in certain verb parts are w's, which leads to apparent anomalies in certain stems of these parts, but -- these anomalies are handled automatically in postprocessing, where we resolve sequences of iwC -> īC, uwC -> ūC, -- w + sukūn + w -> w + shadda. -- RAD12 is the first consonant cluster (after initial اِ) and RAD34 is the second consonant cluster. RAD5 is the -- final consonant. local function make_high_form_sound_final_weak_verb(base, vowel_spec, rad12, rad34, rad5) local final_weak = is_final_weak(base, vowel_spec) local vn = high_form_verbal_noun(rad12, rad34, final_weak and HAMZA or rad5) -- various stem bases local nonpast_stem_base = q(rad12, A, rad34) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, rad12, U, rad34) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end local function form_vii_nrad1(base, rad1) if base.reduced then if not req(rad1, M) then error(("Internal error: Form VII first radical %s is not م but .reduced specified; should have been caught earlier"): format(rget(rad1))) end return M .. SH else return q("نْ", rad1) end end -- Make form VII sound or final-weak verb. local function make_form_vii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high_form_sound_final_weak_verb(base, vowel_spec, form_vii_nrad1(base, rad1), rad2, rad3) end conjugations["VII-sound"] = function(base, vowel_spec) make_form_vii_sound_final_weak_verb(base, vowel_spec) end conjugations["VII-final-weak"] = function(base, vowel_spec) make_form_vii_sound_final_weak_verb(base, vowel_spec) end conjugations["VII-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local nrad1 = form_vii_nrad1(base, rad1) local vn = high_form_verbal_noun(nrad1, Y, rad3) -- various stem bases local nonpast_stem_base = nrad1 local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, nrad1) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["VII-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local nrad1 = form_vii_nrad1(base, rad1) local vn = high_form_verbal_noun(nrad1, rad2, rad2) -- various stem bases local nonpast_stem_base = q(nrad1, A) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, nrad1, U) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Return Form VIII verbal noun. local function form_viii_verbal_noun(base, vowel_spec, rad1, rad2, rad3) local final_weak = is_final_weak(base, vowel_spec) rad3 = final_weak and HAMZA or rad3 return {high_form_verbal_noun(vowel_spec.form_viii_assim, rad2, rad3)} end -- Make form VIII sound or final-weak verb. local function make_form_viii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) -- check for irregular verb اِتَّخَذَ if axadh_radicals(rad1, rad2, rad3) then base.irregular = true rad1 = T end make_high_form_sound_final_weak_verb(base, vowel_spec, vowel_spec.form_viii_assim, rad2, rad3) end conjugations["VIII-sound"] = function(base, vowel_spec) make_form_viii_sound_final_weak_verb(base, vowel_spec) end conjugations["VIII-final-weak"] = function(base, vowel_spec) make_form_viii_sound_final_weak_verb(base, vowel_spec) end conjugations["VIII-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = form_viii_verbal_noun(base, vowel_spec, rad1, Y, rad3) -- various stem bases local nonpast_stem_base = vowel_spec.form_viii_assim local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, nonpast_stem_base) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["VIII-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = form_viii_verbal_noun(base, vowel_spec, rad1, rad2, rad2) -- various stem bases local nonpast_stem_base = q(vowel_spec.form_viii_assim, A) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, vowel_spec.form_viii_assim, U) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IX-sound"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(_I, rad1, SK, rad2, I, rad3, AA, rad3) -- various stem bases local nonpast_stem_base = q(rad1, SK, rad2, A) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, rad1, SK, rad2, U) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["IX-final-weak"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high_form_sound_final_weak_verb(base, vowel_spec, q(rad1, SK, rad2), rad3, rad3) end -- Populate a sound or final-weak verb for any of the various high-numbered -- augmented forms that have 5 consonants in the stem and the same pattern of -- vowels. Some of these consonants in certain verb parts are w's, which leads to -- apparent anomalies in certain stems of these parts, but these anomalies -- are handled automatically in postprocessing, where we resolve sequences of -- iwC -> īC, uwC -> ūC, w + sukūn + w -> w + shadda. local function make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, rad3, rad4, rad5) make_high_form_sound_final_weak_verb(base, vowel_spec, q(rad1, SK, rad2), q(rad3, SK, rad4), rad5) end -- Make form X sound or final-weak verb. local function make_form_x_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) -- check for irregular verb اِسْتَحْيَا (also اِسْتَحَى) local is_hayy = hayy_radicals(rad1, rad2, rad3) local variant = vowel_spec.variant or "both" if not is_hayy or variant == "long" or variant == "both" then make_high5_form_sound_final_weak_verb(base, vowel_spec, S, T, rad1, rad2, rad3) end if is_hayy and (variant == "short" or variant == "both") then base.irregular = true -- Add alternative entries to the verbal paradigms. Any duplicates are removed during addition. make_high_form_sound_final_weak_verb(base, vowel_spec, S .. SK .. T, rad1, rad3) end end conjugations["X-sound"] = function(base, vowel_spec) make_form_x_sound_final_weak_verb(base, vowel_spec) end conjugations["X-final-weak"] = function(base, vowel_spec) make_form_x_sound_final_weak_verb(base, vowel_spec) end conjugations["X-hollow"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(base.reduced and "اِسْ" or "اِسْتِ", rad1, AA, rad3, AH) -- various stem bases local past_stem_base = q(base.reduced and "اِسْ" or "اِسْتَ", rad1) local nonpast_stem_base = q(base.reduced and "سْ" or "سْتَ", rad1) local past_pass_stem_base = q(base.reduced and "اُسْ" or "اُسْتُ", rad1) -- make parts make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["X-geminate"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q("اِسْتِ", rad1, SK, rad2, AA, rad2) -- various stem bases local past_stem_base = q("اِسْتَ", rad1) local nonpast_stem_base = q("سْتَ", rad1) local past_pass_stem_base = q("اُسْتُ", rad1) -- make parts if base.altgem then inflect_tense(base, "past", "", {q(past_stem_base, A, rad2, SH), all_same = 1}, past_endings_ay_12_person_only) end make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn, base.altgem and "[uncommon]" or nil) end conjugations["XI-sound"] = function(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local vn = q(_I, rad1, SK, rad2, II, rad3, AA, rad3) -- various stem bases local nonpast_stem_base = q(rad1, SK, rad2, AA) local past_stem_base = q(_I, nonpast_stem_base) local past_pass_stem_base = q(_U, rad1, SK, rad2, UU) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Probably no form XI final-weak, since already geminate in form; would behave as XI-sound. -- Make form XII sound or final-weak verb. local function make_form_xii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, W, rad2, rad3) end conjugations["XII-sound"] = function(base, vowel_spec) make_form_xii_sound_final_weak_verb(base, vowel_spec) end conjugations["XII-final-weak"] = function(base, vowel_spec) make_form_xii_sound_final_weak_verb(base, vowel_spec) end -- Make form XIII sound or final-weak verb. local function make_form_xiii_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, W, W, rad3) end conjugations["XIII-sound"] = function(base, vowel_spec) make_form_xiii_sound_final_weak_verb(base, vowel_spec) end conjugations["XIII-final-weak"] = function(base, vowel_spec) make_form_xiii_sound_final_weak_verb(base, vowel_spec) end -- Make a form XIV or XV sound or final-weak verb. Last radical appears twice (if`anlala / yaf`anlilu) so if it were -- w or y you'd get if`anwā / yaf`anwī or if`anyā / yaf`anyī, i.e. unlike for most augmented verbs, the identity of -- the radical matters. local function make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3 = get_radicals_3(vowel_spec) local lastrad = base.verb_form == "XV" and Y or rad3 make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, N, rad3, lastrad) end conjugations["XIV-sound"] = function(base, vowel_spec) make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) end conjugations["XIV-final-weak"] = function(base, vowel_spec) make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) end conjugations["XV-sound"] = function(base, vowel_spec) make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec) end -- Probably no form XV final-weak, since already final-weak in form; would behave as XV-sound. -- Make form Iq or IIq sound or final-weak verb. local function make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local vform = base.verb_form local vn = vform == "IIq" and q(TA, rad1, A, rad2, SK, rad3, (final_weak and IN or q(U, rad4))) or q(rad1, A, rad2, SK, rad3, (final_weak and AAH or q(A, rad4, AH))) local ta_pref = vform == "IIq" and TA or "" local tu_pref = vform == "IIq" and TU or "" -- various stem bases local past_stem_base = q(ta_pref, rad1, A, rad2, SK, rad3) local nonpast_stem_base = past_stem_base local past_pass_stem_base = q(tu_pref, rad1, U, rad2, SK, rad3) -- make parts make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end conjugations["Iq-sound"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end conjugations["Iq-final-weak"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IIq-sound"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IIq-final-weak"] = function(base, vowel_spec) make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec) end -- Make form IIIq sound or final-weak verb. local function make_form_iiiq_sound_final_weak_verb(base, vowel_spec) local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, N, rad3, rad4) end conjugations["IIIq-sound"] = function(base, vowel_spec) make_form_iiiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IIIq-final-weak"] = function(base, vowel_spec) make_form_iiiq_sound_final_weak_verb(base, vowel_spec) end conjugations["IVq-sound"] = function(base, vowel_spec) local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) local vn = q(_I, rad1, SK, rad2, I, rad3, SK, rad4, AA, rad4) -- various stem bases local past_stem_base = q(_I, rad1, SK, rad2, A, rad3) local nonpast_stem_base = q(rad1, SK, rad2, A, rad3) local past_pass_stem_base = q(_U, rad1, SK, rad2, U, rad3) -- make parts make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn) end -- Probably no form IVq final-weak, since already geminate in form; would behave as IVq-sound. end create_conjugations() ------------------------------------------------------------------------------- -- Guts of main conjugation function -- ------------------------------------------------------------------------------- -- Given form, weakness and radicals, check to make sure the radicals present are allowable for the weakness. Hamzas on -- alif/wāw/yāʾ seats are never allowed (should always appear as hamza-on-the-line), and various weaknesses have various -- strictures on allowable consonants. local function check_radicals(form, weakness, rad1, rad2, rad3, rad4) local function hamza_check(index, rad) if rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or rad == HAMZA_ON_W or rad == HAMZA_ON_Y then error("Radical " .. index .. " is " .. rad .. " but should be ء (hamza on the line)") end end local function check_waw_ya(index, rad) if not is_waw_ya(rad) then error("Radical " .. index .. " is " .. rad .. " but should be و or ي") end end local function check_not_waw_ya(index, rad) if is_waw_ya(rad) then error("In a sound verb, radical " .. index .. " should not be و or ي") end end hamza_check(rad1) hamza_check(rad2) hamza_check(rad3) hamza_check(rad4) if weakness == "assimilated" or weakness == "assimilated+final-weak" then if rad1 ~= W then error("Radical 1 is " .. rad1 .. " but should be و") end -- don't check that non-assimilated form I verbs don't have wāw as their -- first radical because some form-I verbs exist where a first-radical wāw -- behaves as sound, e.g. wajuha yawjuhu "to be distinguished". end if weakness == "final-weak" or weakness == "assimilated+final-weak" then if rad4 then check_waw_ya(4, rad4) else check_waw_ya(3, rad3) end elseif vform_supports_final_weak(form) then -- non-final-weak verbs cannot have weak final radical if there's a corresponding -- final-weak verb category. I think this is safe. We may have problems with -- ḥayya/ḥayiya yaḥyā if we treat it as a geminate verb. if rad4 then check_not_waw_ya(4, rad4) else check_not_waw_ya(3, rad3) end end if weakness == "hollow" then check_waw_ya(2, rad2) -- don't check that non-hollow verbs in forms that support hollow verbs -- don't have wāw or yāʾ as their second radical because some verbs exist -- where a middle-radical wāw/yāʾ behaves as sound, e.g. form-VIII izdawaja -- "to be in pairs". end if weakness == "geminate" then if rad4 then error("Internal error: No geminate quadrilaterals, should not be seen") end if rad2 ~= rad3 then error("Weakness is geminate; radical 3 is " .. rad3 .. " but should be same as radical 2 " .. rad2) end elseif vform_supports_geminate(form) then -- non-geminate verbs cannot have second and third radical same if there's -- a corresponding geminate verb category. I think this is safe. We -- don't fuss over double wāw or double yāʾ because this could legitimately -- be a final-weak verb with middle wāw/yāʾ, treated as sound. if rad4 then error("Internal error: No quadrilaterals should support geminate verbs") end if rad2 == rad3 and not is_waw_ya(rad2) then error("Weakness is '" .. weakness .. "'; radical 2 and 3 are same at " .. rad2 .. " but should not be; consider making weakness 'geminate'") end end end -- array of substitutions; each element is a 2-entry array FROM, TO; do it -- this way so the concatenations only get evaluated once local postprocess_subs = { -- reorder short-vowel + shadda -> shadda + short-vowel for easier processing {"(" .. AIU .. ")" .. SH, SH .. "%1"}, ----------same letter separated by sukūn should instead use shadda--------- ------------happens e.g. in kun-nā "we were".----------------- {"(.)" .. SK .. "%1", "%1" .. SH}, ---------------------------- assimilated verbs ---------------------------- -- iw, iy -> ī (assimilated verbs) {I .. W .. SK, II}, {I .. Y .. SK, II}, -- uw, uy -> ū (assimilated verbs) {U .. W .. SK, UU}, {U .. Y .. SK, UU}, -------------- final -yā uses tall alif not alif maqṣūra ------------------ {"(" .. Y .. SH .. "?" .. A .. ")" .. AMAQ, "%1" .. ALIF}, ----------------------- handle hamza assimilation ------------------------- -- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel {HAMZA .. A .. HAMZA .. SK, HAMZA .. A .. ALIF}, {HAMZA .. I .. HAMZA .. SK, HAMZA .. I .. Y}, {HAMZA .. U .. HAMZA .. SK, HAMZA .. U .. W} } local postprocess_tr_subs = { {"ī([" .. vowels .. "y*])", "iy%1"}, {"ū([" .. vowels .. "w*])", "uw%1"}, {"(.)%*", "%1%1"}, -- implement shadda ---------------------------- assimilated verbs ---------------------------- -- iw, iy -> ī (assimilated verbs) {"iw([^" .. vowels .. "w])", "ī%1"}, {"iy([^" .. vowels .. "y])", "ī%1"}, -- uw, uy -> ū (assimilated verbs) {"uw([^" .. vowels .. "w])", "ū%1"}, {"uy([^" .. vowels .. "y])", "ū%1"}, ----------------------- handle hamza assimilation ------------------------- -- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel {"ʔaʔ(" .. NV .. ")", "ʔā%1"}, {"ʔiʔ(" .. NV .. ")", "ʔī%1"}, {"ʔuʔ(" .. NV .. ")", "ʔū%1"}, } -- Post-process verb parts to eliminate phonological anomalies. Many of the changes, particularly the tricky ones, -- involve converting hamza to have the proper seat. The rules for this are complicated and are documented on the -- [[w:Hamza]] Wikipedia page. In some cases there are alternatives allowed, and we handle them below by returning -- multiple possibilities. local function postprocess_term(term) if term == "?" then return "?" end -- Add BORDER at text boundaries. term = BORDER .. term .. BORDER -- Do the main post-processing, based on the pattern substitutions in postprocess_subs. for _, sub in ipairs(postprocess_subs) do term = rsub(term, sub[1], sub[2]) end term = term:gsub(BORDER, "") if not rfind(term, HAMZA) then return term end term = term:gsub(HAMZA, HAMZA_PH) term = ar_utilities.process_hamza(term) if #term == 1 then term = term[1] end return term end local function postprocess_translit(translit) if translit == "?" then return "?" end -- Add BORDER at text boundaries. translit = BORDER .. translit .. BORDER -- Do the main post-processing, based on the pattern substitutions in postprocess_tr_subs. for _, sub in ipairs(postprocess_tr_subs) do translit = rsub(translit, sub[1], sub[2]) end translit = translit:gsub(BORDER, "") return translit end local function postprocess_forms(base) local converted_values = {} for slot, forms in pairs(base.forms) do local need_dedup = false for i, form in ipairs(forms) do local term = postprocess_term(form.form) local translit = form.translit and postprocess_translit(form.translit) or nil if term ~= form.form or translit ~= form.translit then need_dedup = true end converted_values[i] = {term, translit} end if need_dedup then local temp_dedup = {} for i = 1, #forms do local new_term, new_translit = unpack(converted_values[i]) if type(new_term) == "table" then for _, nt in ipairs(new_term) do local new_formobj = { form = nt, translit = new_translit, footnotes = forms[i].footnotes, } iut.insert_form(temp_dedup, "temp", new_formobj) end else local new_formobj = { form = new_term, translit = new_translit, footnotes = forms[i].footnotes, } iut.insert_form(temp_dedup, "temp", new_formobj) end end base.forms[slot] = temp_dedup.temp end end end local function process_slot_overrides(base) for slot, forms in pairs(base.slot_overrides) do local existing_values = base.forms[slot] base.forms[slot] = nil for _, form in ipairs(forms) do -- + in active participle for form I requests slot ap1 if form.form == "+" and (base.verb_form ~= "I" or slot ~= "ap") then if not existing_values then error(("Slot '%s' requested the default value but no such value available"):format(slot)) end -- We maintain an invariant that no two slots share a form object (although they may share the footnote -- lists inside the form objects). However, there is no need to copy the form objects here because there -- is a one-to-one correspondence between slots and slot overrides, i.e. you can't have a default value -- go into two slots. insert_form_or_forms(base, slot, existing_values, "allow overrides", form.uncertain) elseif default_indicator_to_active_participle_slot[form.form] then if form.form == "++" then if slot ~= "vn" and slot ~= "ap" and slot ~= "pp" then error(("Secondary default value request '++' only applicable to verbal nouns and pariciples, but found in slot '%s'"): format(slot)) end else if slot ~= "ap" then error(("Secondary default value request '%s' only applicable to active pariciples, but found in slot '%s'"): format(form.form, slot)) end end local secondary_default_slot = slot == "vn" and "vn2" or slot == "pp" and "pp2" or default_indicator_to_active_participle_slot[form.form] local existing_values = base.forms[secondary_default_slot] if not existing_values then error(("Slot '%s' requested a secondary default value using '%s' but no such value available"): format(slot, form.form)) end -- See comment above about the lack of need to copy the form objects. insert_form_or_forms(base, slot, existing_values, "allow overrides", form.uncertain) -- To make sure there aren't shared form objects. base.forms[secondary_default_slot] = nil else insert_form_or_forms(base, slot, form, "allow overrides", form.uncertain) end end end -- Now, for non-stative form-I verbs, fill the active participle slot from ap1 unless it should be missing (e.g. -- passive-only or user specified 'ap:-'). if base.verb_form == "I" and not base.forms.ap and base.forms.ap1 and not skip_slot(base, "ap") then local saw_non_stative = false for _, vowel_spec in ipairs(base.conj_vowels) do if req(vowel_spec.past, A) then saw_non_stative = true break end end if saw_non_stative then base.forms.ap = base.forms.ap1 -- To make sure there aren't shared form objects. base.forms.ap1 = nil end end end local function handle_lemma_linked(base) -- Compute linked versions of potential lemma slots, for use in {{ar-verb}}. We substitute the original lemma -- (before removing links) for forms that are the same as the lemma, if the original lemma has links. for _, slot in ipairs(export.potential_lemma_slots) do if base.forms[slot] then insert_form_or_forms(base, slot .. "_linked", iut.map_forms(base.forms[slot], function(form) if form == base.lemma and rfind(base.linked_lemma, "%[%[") then return base.linked_lemma else return form end end)) end end end -- Process specs given by the user using 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'. local function process_addnote_specs(base) for _, spec in ipairs(base.addnote_specs) do for _, slot_spec in ipairs(spec.slot_specs) do slot_spec = "^" .. slot_spec .. "$" for slot, forms in pairs(base.forms) do if rfind(slot, slot_spec) then -- To save on memory, side-effect the existing forms. for _, form in ipairs(forms) do form.footnotes = iut.combine_footnotes(form.footnotes, spec.footnotes) end end end end end end local function add_missing_links_to_forms(base) -- Any forms without links should get them now. Redundant ones will be stripped later. for slot, forms in pairs(base.forms) do for _, form in ipairs(forms) do if not form.form:find("%[%[") then form.form = "[[" .. form.form .. "]]" end end end end local function conjugate_verb(base) construct_stems(base) for _, vowel_spec in ipairs(base.conj_vowels) do -- Reconstruct conjugation type from verb form and (possibly inferred) weakness. conj_type = base.verb_form .. "-" .. vowel_spec.weakness -- Check that the conjugation type is recognized. if not conjugations[conj_type] then error("Unknown conjugation type '" .. conj_type .. "'") end -- The way the conjugation functions work is they always add entries to the appropriate parts of the paradigm -- (each of which is an array), rather than setting the values. This makes it possible to call more than one -- conjugation function and essentially get a paradigm of the "either A or B" kind. Doing this may insert -- duplicate entries into a particular paradigm part, but this is not a problem because we check for duplicate -- entries when adding them, and don't insert in that case. conjugations[conj_type](base, vowel_spec) end postprocess_forms(base) process_slot_overrides(base) -- This should happen before add_missing_links_to_forms() so that the comparison `form == base.lemma` in -- handle_lemma_linked() works correctly and compares unlinked forms to unlinked forms. handle_lemma_linked(base) process_addnote_specs(base) if not base.alternant_multiword_spec.args.noautolinkverb then add_missing_links_to_forms(base) end end local function parse_indicator_spec(angle_bracket_spec) -- Store the original angle bracket spec so we can reconstruct the overall conj spec with the lemma(s) in them. local base = { angle_bracket_spec = angle_bracket_spec, conj_vowels = {}, root_consonants = {}, user_stem_overrides = {}, user_slot_overrides = {}, slot_explicitly_missing = {}, slot_uncertain = {}, slot_override_uses_default = {}, addnote_specs = {}, } local function parse_err(msg) error(msg .. ": " .. angle_bracket_spec) end local function fetch_footnotes(separated_group) local footnotes for j = 2, #separated_group - 1, 2 do if separated_group[j + 1] ~= "" then parse_err("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'") end if not footnotes then footnotes = {} end table.insert(footnotes, separated_group[j]) end return footnotes end local inside = angle_bracket_spec:match("^<(.*)>$") assert(inside) local segments = put.parse_multi_delimiter_balanced_segment_run(inside, {{"[", "]"}, {"<", ">"}}) local dot_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "%.") -- The first dot-separated element must specify the verb form, e.g. IV or IIq. If the form is I, it needs to include -- the the past and non-past vowels, e.g. I/a~u for kataba ~ yaktubu. More than one vowel can be given, -- comma-separated, and more than one past~non-past pair can be given, slash-separated, e.g. I/a,u~u/i~a for form I -- كمل, which can be conjugated as kamala/kamula ~ yakmulu or kamila ~ yakmalu. An individual vowel spec must be one -- of a, i or u and in general (a) at least one past~non-past pair most be given, and (b) both past and non-past -- vowels must be given even though sometimes the vowel can be determined from the unvocalized form. An exception is -- passive-only verbs, where the vowels can't in general be determined (except indirectly in some cases by looking -- at an associated non-passive verb); in that case, the vowel~vowel spec can left out. local slash_separated_groups = put.split_alternating_runs_and_strip_spaces(dot_separated_groups[1], "/") local form_spec = slash_separated_groups[1] base.form_footnotes = fetch_footnotes(form_spec) if form_spec[1] == "" then parse_err("Missing verb form") end if not allowed_vforms_with_weakness_set[form_spec[1]] then parse_err(("Unrecognized verb form '%s', should be one of %s"):format( form_spec[1], list_to_text(allowed_vforms, nil, " or "))) end if form_spec[1]:find("%-") then base.verb_form, base.explicit_weakness = form_spec[1]:match("^(.-)%-(.*)$") else base.verb_form = form_spec[1] end if #slash_separated_groups > 1 then if base.verb_form ~= "I" then parse_err(("Past~non-past vowels can only be specified when verb form is I, but saw form '%s'"):format( base.verb_form)) end for i = 2, #slash_separated_groups do local slash_separated_group = slash_separated_groups[i] local tilde_separated_groups = put.split_alternating_runs_and_strip_spaces(slash_separated_group, "~") if #tilde_separated_groups ~= 2 then parse_err(("Expected two tilde-separated vowel specs: %s"):format(table.concat(slash_separated_group))) end local function parse_conj_vowels(tilde_separated_group, vtype) local conj_vowel_objects = {} local comma_separated_groups = put.split_alternating_runs_and_strip_spaces(tilde_separated_group, ",") for _, comma_separated_group in ipairs(comma_separated_groups) do local conj_vowel = comma_separated_group[1] if conj_vowel ~= "a" and conj_vowel ~= "i" and conj_vowel ~= "u" then parse_err(("Expected %s conjugation vowel '%s' to be one of a, i or u in %s"):format( vtype, conj_vowel, table.concat(slash_separated_group))) end conj_vowel = dia[conj_vowel] local conj_vowel_footnotes = fetch_footnotes(comma_separated_group) -- Try to use strings when possible as it makes q() significantly more efficient. if conj_vowel_footnotes then table.insert(conj_vowel_objects, {form = conj_vowel, footnotes = conj_vowel_footnotes}) else table.insert(conj_vowel_objects, conj_vowel) end end return conj_vowel_objects end local conj_vowel_spec = { past = parse_conj_vowels(tilde_separated_groups[1], "past"), nonpast = parse_conj_vowels(tilde_separated_groups[2], "non-past"), } table.insert(base.conj_vowels, conj_vowel_spec) end end for i = 2, #dot_separated_groups do local dot_separated_group = dot_separated_groups[i] local first_element = dot_separated_group[1] if first_element == "addnote" then local spec_and_footnotes = fetch_footnotes(dot_separated_group) if #spec_and_footnotes < 2 then parse_err("Spec with 'addnote' should be of the form 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'") end local slot_spec = table.remove(spec_and_footnotes, 1) local slot_spec_inside = rmatch(slot_spec, "^%[(.*)%]$") if not slot_spec_inside then parse_err("Internal error: slot_spec " .. slot_spec .. " should be surrounded with brackets") end local slot_specs = rsplit(slot_spec_inside, ",") -- FIXME: Here, [[Module:it-verb]] called strip_spaces(). Generally we don't do this. Should we? table.insert(base.addnote_specs, {slot_specs = slot_specs, footnotes = spec_and_footnotes}) elseif first_element:find("^var:") then if #dot_separated_group > 1 then parse_err(("Can't attach footnotes to 'var:' spec '%s'"):format(first_element)) end base.variant = first_element:match("^var:(.*)$") elseif first_element:find("^I+V?:") then local root_cons, root_cons_value = first_element:match("^(I+V?):(.*)$") local root_index if root_cons == "I" then root_index = 1 elseif root_cons == "II" then root_index = 2 elseif root_cons == "III" then root_index = 3 elseif root_cons == "IV" then root_index = 4 if not base.verb_form:find("q$") then parse_err(("Can't specify root consonant IV for non-quadriliteral verb form '%s': %s"):format( base.verb_form, first_element)) end end local cons, translit = root_cons_value:match("^(.*)//(.*)$") if not cons then cons = root_cons_value end local root_footnotes = fetch_footnotes(dot_separated_group) if not translit and not root_footnotes then base.root_consonants[root_index] = cons else base.root_consonants[root_index] = {form = cons, translit = translit, footnotes = root_footnotes} end elseif first_element:find("^[a-z][a-z0-9_]*:") then local slot_or_stem, remainder = first_element:match("^(.-):(.*)$") dot_separated_group[1] = remainder local comma_separated_groups = put.split_alternating_runs_and_strip_spaces(dot_separated_group, "[,،]") if overridable_stems[slot_or_stem] then if base.user_stem_overrides[slot_or_stem] then parse_err("Overridable stem '" .. slot_or_stem .. "' specified twice") end base.user_stem_overrides[slot_or_stem] = overridable_stems[slot_or_stem](comma_separated_groups, {prefix = slot_or_stem, base = base, parse_err = parse_err, fetch_footnotes = fetch_footnotes}) else -- assume a form override; we validate further later when the possible slots are available if base.user_slot_overrides[slot_or_stem] then parse_err("Form override '" .. slot_or_stem .. "' specified twice") end base.user_slot_overrides[slot_or_stem] = allow_multiple_values_for_override(comma_separated_groups, {prefix = slot_or_stem, base = base, parse_err = parse_err, fetch_footnotes = fetch_footnotes}, "is form override") end elseif indicator_flags[first_element] then if #dot_separated_group > 1 then parse_err("No footnotes allowed with '" .. first_element .. "' spec") end if base[first_element] then parse_err("Spec '" .. first_element .. "' specified twice") end base[first_element] = true else local passive, uncertain = first_element:match("^(.*)(%?)$") passive = passive or first_element uncertain = not not uncertain if passive_types[passive] then if #dot_separated_group > 1 then parse_err("No footnotes allowed with '" .. passive .. "' spec") end if base.passive then parse_err("Value for passive type specified twice") end base.passive = passive base.passive_uncertain = uncertain else parse_err("Unrecognized spec '" .. first_element .. "'") end end end return base end -- Normalize all lemmas, substituting the pagename for blank lemmas and adding links to multiword lemmas. local function normalize_all_lemmas(alternant_multiword_spec, head) -- (1) Add links to all before and after text. Remember the original text so we can reconstruct the verb spec later. if not alternant_multiword_spec.args.noautolinktext then iut.add_links_to_before_and_after_text(alternant_multiword_spec, "remember original") end -- (2) Remove any links from the lemma, but remember the original form so we can use it below in the 'lemma_linked' -- form. iut.map_word_specs(alternant_multiword_spec, function(base) if base.lemma == "" then base.lemma = head end base.user_specified_lemma = base.lemma base.lemma = m_links.remove_links(base.lemma) base.user_specified_verb = base.lemma base.verb = base.user_specified_verb local linked_lemma if alternant_multiword_spec.args.noautolinkverb or base.user_specified_lemma:find("%[%[") then linked_lemma = base.user_specified_lemma else -- Add links to the lemma so the user doesn't specifically need to, since we preserve -- links in multiword lemmas and include links in non-lemma forms rather than allowing -- the entire form to be a link. linked_lemma = iut.add_links(base.user_specified_lemma) end base.linked_lemma = linked_lemma end) end --W: Lexical exceptions — yāʾ-middle roots whose Form IV (أفعل) keeps the yāʾ SOUND -- instead of undergoing the regular إعلال to a hollow verb (أَفْيَلَ, not أَفَالَ). -- These are attested verbs (not the صفة مشبهة like أَخْيَف/خَيْفاء); each is sourced below. -- Only Form IV is affected; forms VII/VIII/X of these roots remain hollow. -- Keyed by rad1..rad2..rad3. local SOUND_FORM_IV_ROOTS = { ["خيل"] = true, -- أَخْيَلَ: أخيلت السماءُ تهيأت للمطر — المعجم الوسيط ج1 ص266 (وبه أَخالت أيضًا) ["غيل"] = true, -- أَغْيَلَ: أغيلته فهي مُغْيِل، سقته الغَيْل — لسان العرب ج11 ص111 (وبه أغالت أيضًا) ["حيج"] = true, -- أَحْيَجَ: أحيجت الأرضُ، على خلاف القياس — تاج العروس ج5 ص500 ["حين"] = true, -- أَحْيَنَ: أحين القومُ آن لهم أن يبلغوا ما أملوه — المعجم الوسيط ج1 ص212 ["خيف"] = true, -- أَخْيَفَ: أخيف الحاجُّ نزلوا خيف منى — كتاب الأفعال لابن القوطية ص35 ["ريف"] = true, -- أَرْيَفَ: أريفت الأرضُ إريافًا (خصبت) — المحيط في اللغة ج10 ص256 ["زين"] = true, -- أَزْيَنَ: أزينت الأرضُ، حسّنها عشبها — مقاييس اللغة ج3 ص41 } -- Determine weakness from radicals. Used when root given in place of lemma (e.g. for {{ar-verb forms}}). local function weakness_from_radicals(form, rad1, rad2, rad3, rad4) local weakness = nil local quadlit = form:find("q$") -- If weakness unspecified, derive from radicals. if not quadlit then if is_waw_ya(rad3) and rad1 == W and form == "I" then weakness = "assimilated+final-weak" elseif is_waw_ya(rad3) and vform_supports_final_weak(form) then weakness = "final-weak" elseif rad2 == rad3 and vform_supports_geminate(form) then weakness = "geminate" elseif is_waw_ya(rad2) and vform_supports_hollow(form) then --W: Both wāw (و) and yāʾ (ي) in the middle position create hollow verbs, in form I -- (قَالَ from ق-و-ل, بَاعَ from ب-ي-ع) and in the augmented forms IV, VII, VIII, X -- (أَتَاسَ from ت-ي-س, أَبَانَ from ب-ي-ن, أَطَاحَ from ط-ي-ح — معجم متن اللغة). if form == "IV" and rad2 == Y and SOUND_FORM_IV_ROOTS[rad1 .. rad2 .. rad3] then -- Lexical exceptions whose Form IV keeps the yāʾ sound (أَفْيَلَ); see SOUND_FORM_IV_ROOTS. weakness = "sound" else weakness = "hollow" end elseif rad1 == W and form == "I" then weakness = "assimilated" else weakness = "sound" end else if is_waw_ya(rad4) then weakness = "final-weak" else weakness = "sound" end end return weakness end -- Join the infixed tāʔ (ت) to the first radical in form VIII verbs. This may cause assimilation of the tāʔ to the -- radical or in some cases the radical to the tāʔ. Used when a root is supplied instead of a lemma (which already has -- the appropriate assimilation in it). local function form_viii_join_ta(rad) if rad == W or rad == Y or rad == "ت" then return "تّ" elseif rad == "د" then return "دّ" elseif rad == "ث" then return "ثّ" elseif rad == "ذ" then return "ذّ" elseif rad == "ز" then return "زْد" elseif rad == "ص" then return "صْط" elseif rad == "ض" then return "ضْط" elseif rad == "ط" then return "طّ" elseif rad == "ظ" then return "ظّ" else return rad .. SK .. "ت" end end local function detect_indicator_spec(base) base.forms = {} base.stem_overrides = {} base.slot_overrides = {} if not base.conj_vowels[1] then -- These may be converted to inferred vowels. If not, we throw an error if form I and not passive-only. base.conj_vowels = {{ past = "-", nonpast = "-", }} else -- If multiple vowels specified for a given vowel type (e.g. a,u~u), expand so that each spec in local expansion = {} for _, spec in ipairs(base.conj_vowels) do for _, past in ipairs(spec.past) do for _, nonpast in ipairs(spec.nonpast) do table.insert(expansion, {past = past, nonpast = nonpast}) end end end base.conj_vowels = expansion end local vform = base.verb_form -- check for quadriliteral form (Iq, IIq, IIIq, IVq) base.quadlit = not not vform:find("q$") -- Infer radicals as necessary. We infer a separate set of radicals for each past~non-past vowel combination because -- they may be different (particularly with form-I hollow verbs). for _, vowel_spec in ipairs(base.conj_vowels) do -- NOTE: rad1, rad2, etc. refer to user-specified radicals, which are formobj tables that optionally specify an -- explicit manual translit, whereas ir1, ir2, etc. refer to inferred radicals, which are either strings or -- lists of possible radicals. local rads = base.root_consonants local rad1, rad2, rad3, rad4 = rads[1], rads[2], rads[3], rads[4] -- Default any unspecified radicals to radicals determined from the headword. The returned radicals may be -- lists of possible radicals, where the first radical should be chosen if the user didn't explicitly specify a -- radical but all are allowed. If `ambig = true` is set in the table, the radical is considered ambiguous and -- categories won't be created for weak radicals. local weakness, ir1, ir2, ir3, ir4 if vform ~= "none" then ir1, ir2, ir3 = rmatch(base.lemma, "^([^_])_([^_])_([^_])$") if not ir1 then ir1, ir2, ir3, ir4 = rmatch(base.lemma, "^([^_])_([^_])_([^_])_([^_])$") end if ir1 then -- root given instead of lemma weakness = weakness_from_radicals(vform, ir1, ir2, ir3, ir4) if vform == "VIII" then vowel_spec.form_viii_assim = form_viii_join_ta(ir1) end else local ret = export.infer_radicals { headword = base.lemma, vform = vform, passive = base.passive, past_vowel = vowel_spec.past, nonpast_vowel = vowel_spec.nonpast, is_reduced = base.reduced, } weakness, ir1, ir2, ir3, ir4 = ret.weakness, ret.rad1, ret.rad2, ret.rad3, ret.rad4 vowel_spec.form_viii_assim = ret.form_viii_assim vowel_spec.past = ret.past_vowel vowel_spec.nonpast = ret.nonpast_vowel vowel_spec.variant = base.variant or ret.variant end end -- For most ambiguous radicals, the choice of radical doesn't matter because it doesn't affect the conjugation -- one way or another. For form I hollow verbs, however, it definitely does. In fact, the choice of radical is -- critical even beyond the past and non-past vowels because it affects the form of the passive participle. So, -- check for this and signal an error if the radical could not be inferred and is not given explicitly. if vform == "I" and type(ir2) == "table" and ir2.need_radical and not rad2 then error("Unable to guess middle radical of hollow form I verb; need to specify radical explicitly") end if vform == "I" and not is_passive_only(base.passive) and ( rget(vowel_spec.past) == "-" or rget(vowel_spec.nonpast) == "-") then error("Form I verb that isn't passive-only or final-weak must have past~non-past vowels specified") end -- Convert ambiguous radicals. local function regularize_inferred_radical(rad) if type(rad) == "table" then if rad.ambig then return {form = rad[1], ambig = true} else return rad[1] end else return rad end end -- Return the appropriate radical at index `index` (1 through 4), based either on the user-specified radical -- `user_radical` or (if unspecified) `inferred_radical`, inferred from the unvocalized lemma. Two values are -- returned, the "regularized" version of the radical (where ambiguous inferred radicals are converted to their -- most likely actual radical) and the non-regularized version. The returned values are form objects rather than -- strings. local function fetch_radical(user_radical, inferred_radical, index) if not user_radical then return regularize_inferred_radical(inferred_radical), inferred_radical else local rad_formval = rget(user_radical) if type(inferred_radical) == "table" then local allowed_radical_set = m_table.listToSet(inferred_radical) if not allowed_radical_set[rad_formval] then error(("For lemma %s, radical %s ambiguously inferred as %s but user radical incompatibly given as %s"): format(base.lemma, index, list_to_text(inferred_radical, nil, " or "), rad_formval)) end elseif rad_formval ~= inferred_radical then error(("For lemma %s, radical %s inferred as %s but user radical incompatibly given as %s"): format(base.lemma, index, inferred_radical, rad_formval)) end return user_radical, user_radical end end if vform ~= "none" then vowel_spec.rad1, vowel_spec.unreg_rad1 = fetch_radical(rad1, ir1, 1) vowel_spec.rad2, vowel_spec.unreg_rad2 = fetch_radical(rad2, ir2, 2) vowel_spec.rad3, vowel_spec.unreg_rad3 = fetch_radical(rad3, ir3, 3) if base.quadlit then vowel_spec.rad4, vowel_spec.unreg_rad4 = fetch_radical(rad4, ir4, 4) end end if vform == "I" then -- If explicit weakness given using 'I-sound' or 'I-assimilated', we may need to adjust the inferred weakness. if base.explicit_weakness == "sound" then if weakness == "assimilated" then weakness = "sound" elseif weakness == "assimilated+final-weak" then -- Verbs like waniya~yawnā "to be faint; to languish" (although the defaults should handle this -- correctly) weakness = "final-weak" else error(("Can't specify form 'I-sound' when inferred weakness is '%s' for lemma %s"):format( weakness, base.lemma)) end elseif base.explicit_weakness == "assimilated" then if weakness == "sound" then -- i~a verbs like waṭiʔa~yaṭaʔu "to tread, to trample"; wasiʕa~yasaʕu "to be spacious; to be well-off"; -- waṯiʔa~yaṯaʔu "to get bruised, to be sprained", which would default to sound. weakness = "assimilated" elseif weakness == "final-weak" then -- For completeness; not clear if any verbs occur where this is needed. (There are plenty of -- assimilated+final-weak verbs but the defaults should take care of them.) weakness = "assimilated+final-weak" else error(("Can't specify form 'I-assimilated' when inferred weakness is '%s' for lemma %s"):format( weakness, base.lemma)) end elseif base.explicit_weakness then error(("Internal error: Unrecognized value '%s' for base.explicit_weakness"):format(base.explicit_weakness)) end elseif vform == "none" then weakness = base.explicit_weakness elseif base.explicit_weakness then error(("Internal error: Explicit weakness should not be specifiable except with forms I and none, but saw explicit weakness '%s' with verb form '%s'"): format(base.explicit_weakness, vform)) end vowel_spec.weakness = weakness if vform ~= "none" then -- Error if radicals are wrong given the weakness. More likely to happen if the weakness is explicitly given -- rather than inferred. Will also happen if certain incorrect letters are included as radicals e.g. hamza on -- top of various letters, alif maqṣūra, tā' marbūṭa. check_radicals(vform, weakness, rget(vowel_spec.rad1), rget(vowel_spec.rad2), rget(vowel_spec.rad3), base.quadlit and rget(vowel_spec.rad4) or nil) end -- Check the variant value. local form_iii_vi_geminate = (vform == "III" or vform == "VI") and rget(vowel_spec.rad2) == rget(vowel_spec.rad3) and not req(vowel_spec.rad2, Y) local hayy_i_x = hayy_radicals(vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3) and (vform == "I" or vform == "X") if form_iii_vi_geminate or hayy_i_x then if vowel_spec.variant and vowel_spec.variant ~= "long" and vowel_spec.variant ~= "short" and vowel_spec.variant ~= "both" then error(("For form-III/VI geminate verb or form-I/X verb with ح-ي-ي radicals, saw unrecognized 'var:%s' value; should be 'var:long', 'var:short' or 'var:both'"):format( vowel_spec.variant)) end elseif vowel_spec.variant then error(("Variant value 'var:%s' not allowed in this context"):format(vowel_spec.variant)) end end -- If form I, regroup expanded vowels for display purposes. if vform == "I" then local group_by_past = {} for _, vowel_spec in ipairs(base.conj_vowels) do m_table.insertIfNot(group_by_past, { past = undia[rget(vowel_spec.past)], nonpasts = {undia[rget(vowel_spec.nonpast)]}, }, { key = function(obj) return obj.past end, combine = function(obj1, obj2) for _, nonpast in ipairs(obj2.nonpasts) do m_table.insertIfNot(obj1.nonpasts, nonpast) end end, }) end local group_by_nonpast = {} for _, vowel_spec in ipairs(group_by_past) do m_table.insertIfNot(group_by_nonpast, { pasts = {vowel_spec.past}, nonpasts = vowel_spec.nonpasts, }, { key = function(obj) return obj.nonpasts end, combine = function(obj1, obj2) for _, past in ipairs(obj2.pasts) do m_table.insertIfNot(obj1.pasts, past) end end, }) end base.grouped_conj_vowels = group_by_nonpast end -- Set value of passive. If not specified, default is yes for forms II, III, IV and Iq; no but uncertainly for -- forms VII, IX, XI - XV and IIIq - IVq, as well as form I with past vowel u; impersonal but uncertainly for form -- V, VI, X and IIq, as well as form I with past vowel i; and yes but uncertainly for the remainder (form I with -- past vowel only a and form VIII). if not base.passive then base.passive_defaulted = true -- Temporary tracking for defaulted passives by verb form, weakness and (for form I) past/non-past vowels. track_if_ar_conj(base, "passive-defaulted/" .. vform) for _, vowel_spec in ipairs(base.conj_vowels) do track_if_ar_conj(base, "passive-defaulted/" .. vform.. "/" .. vowel_spec.weakness) if vform == "I" then --W: use rget(); the vowel is a form object when the user attached a footnote -- (e.g. <I/a[نادر]~u>), and undia[<table>] is nil, which crashes format() on Lua 5.1. local past_nonpast = ("%s~%s"):format(undia[rget(vowel_spec.past)], undia[rget(vowel_spec.nonpast)]) track_if_ar_conj(base, "passive-defaulted/I/" .. past_nonpast) track_if_ar_conj(base, "passive-defaulted/I/" .. vowel_spec.weakness .. "/" .. past_nonpast) end end if vform_probably_full_passive(vform) then base.passive = "pass" else base.passive_uncertain = true for _, vowel_spec in ipairs(base.conj_vowels) do if vform_probably_no_passive(vform, vowel_spec.weakness, vowel_spec.past, vowel_spec.nonpast) then base.passive = "nopass" break elseif vform_probably_impersonal_passive(vform, vowel_spec.weakness, vowel_spec.past, vowel_spec.nonpast) then base.passive = "ipass" break end end base.passive = base.passive or "pass" end end -- NOTE: Currently there are no built-in stems or form overrides for Arabic; this code is inherited from -- [[Module:ca-verb]], where such things do exist, and is kept for generality in case we decide in the future to -- implement such things. -- Override built-in verb stems and overrides with user-specified ones. for stem, values in pairs(base.user_stem_overrides) do base.stem_overrides[stem] = values end for slot, values in pairs(base.user_slot_overrides) do if not base.alternant_multiword_spec.verb_slots_map[slot] then error("Unrecognized override slot '" .. slot .. "': " .. base.angle_bracket_spec) end if export.unsettable_slots_set[slot] then error("Slot '" .. slot .. "' cannot be set using an override: " .. base.angle_bracket_spec) end if skip_slot(base, slot, "allow overrides") then error("Override slot '" .. slot .. "' would be skipped based on the passive, 'noimp' and/or 'no_nonpast' settings: " .. base.angle_bracket_spec) end base.slot_overrides[slot] = values end if base.verb_form == "none-final-weak" then for _, stem_type in ipairs { "past", "past_pass", "nonpast", "nonpast_pass" } do if base.stem_overrides[stem_type .. "_c"] or base.stem_overrides[stem_type .. "_v"] then error(("Specify past stem for verb type 'none-final-weak' using '%s:...' not '%s_c:...' or '%s_v:...'"): format(stem_type, stem_type, stem_type)) end end for _, stem_type in ipairs { "past", "nonpast" } do if base.stem_overrides[stem_type] or not base.stem_overrides[stem_type .. "_final_weak_vowel"] then error(("For verb type 'none-final-weak', if '%s:...' specified, so must '%s_final_weak_vowel:...'"): format(stem_type, stem_type)) end end end end local function detect_all_indicator_specs(alternant_multiword_spec) add_slots(alternant_multiword_spec) alternant_multiword_spec.verb_forms = {} -- This means at least one individual base had the slot marked as explicitly missing. Another base (e.g. when -- there are multiple alternants) might have a value for the slot. In practice, we only respect this when there are -- no overall values in the slot and `slot_uncertain` isn't set; in this case, we display "no ..." for the slot -- instead of simply not displaying anything for the slot. alternant_multiword_spec.slot_explicitly_missing = {} -- This means at least one individual base had no values for the slot and the slot marked as explicitly uncertain. -- Note that this is different from a value being present but marked as uncertain (e.g. if an override was given -- with a ? after it); this causes the form object for the value to have `uncertain = true` set. If there are no -- overall values in the slot and `slot_uncertain` is set, we display this in the headword. alternant_multiword_spec.slot_uncertain = {} iut.map_word_specs(alternant_multiword_spec, function(base) -- So arguments, etc. can be accessed. WARNING: Creates circular reference. base.alternant_multiword_spec = alternant_multiword_spec detect_indicator_spec(base) if not base.nocat then m_table.insertIfNot(alternant_multiword_spec.verb_forms, base.verb_form) end if base.passive then alternant_multiword_spec.passive = base.passive end if base.passive_uncertain then alternant_multiword_spec.passive_uncertain = true end for slot, _ in pairs(base.slot_explicitly_missing) do alternant_multiword_spec.slot_explicitly_missing[slot] = true end end) end local function determine_slot_uncertainty_from_forms(alternant_multiword_spec) iut.map_word_specs(alternant_multiword_spec, function(base) -- If no verbal noun and verb form is not 'none' (manually-specified stems) — which currently only happens for -- form I — and the verbal noun wasn't explicitly indicated as missing using <vn:->, we assume it's just -- unknown/unspecified rather than missing. Same with active participles. for uncertain_slot, _ in pairs(slots_that_may_be_uncertain) do if not base.forms[uncertain_slot] and vform ~= "none" and not skip_slot(base, uncertain_slot) then base.slot_uncertain[uncertain_slot] = true end end -- Propagate slot uncertainty up. Currently only the verbal noun can have this set but we write the code -- generally. for slot, _ in pairs(base.slot_uncertain) do alternant_multiword_spec.slot_uncertain[slot] = true end end) -- If slot is uncertain and has no value, explicitly set its value to "?". for uncertain_slot, _ in pairs(slots_that_may_be_uncertain) do if not alternant_multiword_spec.forms[uncertain_slot] and alternant_multiword_spec.slot_uncertain[uncertain_slot] then alternant_multiword_spec.forms[uncertain_slot] = {{form = "?"}} end end end -- Determine certain properties of the verb from the overall forms, such as whether the verb is active-only or -- passive-only, is impersonal, lacks an imperative, etc. local function determine_verb_properties_from_forms(alternant_multiword_spec) alternant_multiword_spec.has_active = false alternant_multiword_spec.has_passive = false alternant_multiword_spec.has_non_impers_active = false alternant_multiword_spec.has_non_impers_passive = false alternant_multiword_spec.has_imp = false alternant_multiword_spec.has_past = false alternant_multiword_spec.has_nonpast = false for slot, _ in pairs(alternant_multiword_spec.forms) do if slot == "ap" or slot:find("[123]") and not slot:find("_pass") then alternant_multiword_spec.has_active = true end if slot == "pp" or slot:find("[123]") and slot:find("_pass") then alternant_multiword_spec.has_passive = true end if slot:find("[123]") and not slot:find("pass_[123]") and not slot:find("3ms") then alternant_multiword_spec.has_non_impers_active = true end if slot:find("pass_[123]") and not slot:find("3ms") then alternant_multiword_spec.has_non_impers_passive = true end if slot:find("^imp_") then alternant_multiword_spec.has_imp = true end if slot:find("^past_") then alternant_multiword_spec.has_past = true end if slot:find("^ind_") or slot:find("^sub_") or slot:find("^juss_") then alternant_multiword_spec.has_nonpast = true end end end local function add_categories_and_annotation(alternant_multiword_spec, base, multiword_lemma, insert_ann, insert_cat) -- Useful e.g. in constructing suppletive verbs out of parts. For a verb like جاء or أتى whose imperative comes -- from the unrelated verb تعالى, we don't want the latter verb showing up in categories or annotations. if base.nocat then return end local vform = base.verb_form if vform ~= "none" then insert_ann("form", vform) insert_cat("form-" .. vform .. " verbs") --W: Add morphological pattern corresponding to the form local morph_pattern = FORM_TO_MORPHOLOGICAL_PATTERN[vform] if morph_pattern then insert_ann("morph_pattern", morph_pattern) end end if base.reduced then insert_ann("reduced", "reduced") if vform ~= "none" then insert_cat("form-" .. vform .. " reduced verbs") end end if base.quadlit then insert_cat("verbs with quadriliteral roots") end if base.passive_defaulted then insert_cat("verbs with defaulted passive") end for _, vowel_spec in ipairs(base.conj_vowels) do local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec) local final_weak = is_final_weak(base, vowel_spec) local weakness = vowel_spec.weakness -- We have to distinguish weakness by form and weakness by conjugation. Weakness by form merely indicates the -- presence of weak letters in certain positions in the radicals. Weakness by conjugation is related to how the -- verbs are conjugated. For example, form-II verbs that are "hollow by form" (middle radical is wāw or yāʾ) are -- conjugated as sound verbs. Another example: form-I verbs with initial wāw are "assimilated by form" and most -- are assimilated by conjugation as well, but a few are sound by conjugation, e.g. wajuha yawjuhu "to be -- distinguished" (rather than wajuha yajuhu); similarly for some hollow-by-form verbs in various forms, e.g. -- form VIII izdawaja yazdawiju "to be in pairs" (rather than izdāja yazdāju). Categories referring to weakness -- always refer to weakness by conjugation; weakness by form is distinguished only by categories such as -- [[:Category:Arabic form-III verbs with و as second radical]]. insert_ann("weakness", weakness) if vform ~= "none" then insert_cat(("%s form-%s verbs"):format(weakness, vform)) end local function radical_is_ambiguous(rad) return type(rad) == "table" and rad.ambig end local function radical_is_unambiguous_weak(rad) return not radical_is_ambiguous(rad) and (is_waw_ya(rad) or req(rad, HAMZA)) end if vform ~= "none" then local ur1, ur2, ur3, ur4 = vowel_spec.unreg_rad1, vowel_spec.unreg_rad2, vowel_spec.unreg_rad3, vowel_spec.unreg_rad4 -- Create headword categories based on the radicals. Do the following before -- converting the Latin radicals into Arabic ones so we distinguish -- between ambiguous and non-ambiguous radicals. if radical_is_ambiguous(ur1) or radical_is_ambiguous(ur2) or radical_is_ambiguous(ur3) or ur4 and radical_is_ambiguous(ur4) then insert_cat("verbs with ambiguous radicals") end if radical_is_unambiguous_weak(ur1) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur1) .. " as first radical") end if radical_is_unambiguous_weak(ur2) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur2) .. " as second radical") end if radical_is_unambiguous_weak(ur3) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur3) .. " as third radical") end if ur4 and radical_is_unambiguous_weak(ur4) then insert_cat("form-" .. vform .. " verbs with " .. rget(ur4) .. " as fourth radical") end end end if vform == "I" and not is_passive_only(base.passive) then for _, vowel_spec in ipairs(base.grouped_conj_vowels) do insert_ann("vowels", ("%s ~ %s"):format(table.concat(vowel_spec.pasts, "/"), table.concat(vowel_spec.nonpasts, "/"))) for _, past in ipairs(vowel_spec.pasts) do for _, nonpast in ipairs(vowel_spec.nonpasts) do if past == "-" or nonpast == "-" then error("Internal error: Saw form I past vowel %s and non-past vowel %s but - in place of vowel should have triggered an error earlier") end insert_cat(("form-I verbs with past vowel %s and non-past vowel %s"):format(past, nonpast)) --W: Add morphological pattern for form I based on vowels local form_i_with_vowels = ("I/%s~%s"):format(past, nonpast) local morph_pattern = FORM_TO_MORPHOLOGICAL_PATTERN[form_i_with_vowels] if morph_pattern then insert_ann("morph_pattern", morph_pattern) end end end end end for slot, name in pairs(slots_that_may_be_uncertain) do if base.slot_uncertain[slot] then -- An unspecified and non-defaulted verbal noun (form I) is considered uncertain rather than explicitly -- missing. Use <vn:-> to explicitly indicate the lack of verbal noun. Same for form-I stative active -- participles. insert_cat(("verbs with unknown or uncertain %ss"):format(name)) end end if base.irregular then insert_ann("irreg", "irregular") insert_cat("irregular verbs") end end -- Compute the categories to add the verb to, as well as the annotation to display in the conjugation title bar. We -- combine the code to do these functions as both categories and title bar contain similar information. local function compute_categories_and_annotation(alternant_multiword_spec) alternant_multiword_spec.categories = {} local ann = {} alternant_multiword_spec.annotation = ann ann.form = {} ann.morph_pattern = {} --W: arabic morphological pattern corresponding to the form ann.weakness = {} ann.vowels = {} ann.passive = nil ann.reduced = {} ann.irreg = {} ann.defective = {} local multiword_lemma = false for _, slot in ipairs(export.potential_lemma_slots) do if alternant_multiword_spec.forms[slot] then for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do if formobj.form:find(" ") then multiword_lemma = true break end end break end end local function insert_ann(anntype, value) m_table.insertIfNot(alternant_multiword_spec.annotation[anntype], value) end local function insert_cat(cat, also_when_multiword) -- Don't place multiword terms in categories like 'Arabic form-II verbs' to avoid spamming the categories with -- such terms. if also_when_multiword or not multiword_lemma then m_table.insertIfNot(alternant_multiword_spec.categories, "Arabic " .. cat) end end iut.map_word_specs(alternant_multiword_spec, function(base) add_categories_and_annotation(alternant_multiword_spec, base, multiword_lemma, insert_ann, insert_cat) end) for slot, name in pairs(slots_that_may_be_uncertain) do if alternant_multiword_spec.forms[slot] then for _, form in ipairs(alternant_multiword_spec.forms[slot]) do if form.uncertain then if form.form == "?" then insert_cat(("verbs with explicitly unknown %ss"):format(name)) else insert_cat(("verbs needing %s checked"):format(name)) end break end end end end if alternant_multiword_spec.has_active then if alternant_multiword_spec.has_passive and alternant_multiword_spec.has_non_impers_passive then insert_cat("verbs with full passive") ann.passive = "full passive" elseif alternant_multiword_spec.has_passive then insert_cat("verbs with impersonal passive") ann.passive = "impersonal passive" else insert_cat("verbs lacking passive forms") ann.passive = "no passive" end else if alternant_multiword_spec.has_non_impers_passive then insert_cat("passive verbs") insert_cat("verbs with full passive") ann.passive = "passive-only" else insert_cat("passive verbs") insert_cat("impersonal verbs") insert_cat("verbs with impersonal passive") ann.passive = "impersonal (passive-only)" end end if alternant_multiword_spec.passive_uncertain then insert_cat("verbs needing passive checked") ann.passive = ann.passive .. ' <abbr title="passive status uncertain">(?)</abbr>' end if alternant_multiword_spec.has_active and not alternant_multiword_spec.has_imp then insert_ann("defective", "no imperative") insert_cat("verbs lacking imperative forms") end if not alternant_multiword_spec.has_past then insert_ann("defective", "no past") insert_cat("verbs lacking past forms") end if not alternant_multiword_spec.has_nonpast then insert_ann("defective", "no non-past") insert_cat("verbs lacking non-past forms") end local ann_parts = {} local function insert_ann_part(part, conj) local val = table.concat(ann[part], conj or " or ") if val ~= "" and val ~= "regular" then table.insert(ann_parts, val) end end insert_ann_part("form") insert_ann_part("weakness") insert_ann_part("reduced") insert_ann_part("vowels") if ann.passive then table.insert(ann_parts, ann.passive) end insert_ann_part("irreg") insert_ann_part("defective", "، ") --W: Save morph_pattern before overwriting annotation with string alternant_multiword_spec.morph_pattern = ann.morph_pattern alternant_multiword_spec.annotation = table.concat(ann_parts, "، ") end local function show_forms(alternant_multiword_spec) local lemmas = {} for _, slot in ipairs(export.potential_lemma_slots) do if alternant_multiword_spec.forms[slot] then for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do table.insert(lemmas, formobj) end break end end alternant_multiword_spec.lemmas = lemmas -- save for later use in make_table() alternant_multiword_spec.vn = alternant_multiword_spec.forms.vn -- save for later use in make_table() -- Reconstruct the original verb spec without overrides for verbal nouns and participles, since those specific slots -- are ignored by {{ar-verb form}}. Compute this once beforehand; `transform_accel_obj` is called repeatedly on each -- form and we don't want to compute this repeatedly. local reconstructed_verb_spec = iut.reconstruct_original_spec(alternant_multiword_spec, { preprocess_angle_bracket_spec = function(spec) spec = spec:match("^<(.*)>$") assert(spec) local segments = put.parse_multi_delimiter_balanced_segment_run(spec, {{"[", "]"}, {"<", ">"}}) local dot_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "%.") -- Rejoin each dot-separated group into a single string, since we aren't actually going to do any parsing -- of bracket-bounded textual runs; then filter out overrides for verbal nouns and participles. local filtered_indicators = {} for _, dot_separated_group in ipairs(dot_separated_groups) do local indicator = table.concat(dot_separated_group) -- FIXME: Do we want to filter out any other indicators? if not (indicator:find("^vn:") or indicator:find("^[ap]p:")) then table.insert(filtered_indicators, indicator) end end return ("<%s>"):format(table.concat(filtered_indicators, ".")) end, }) -- If we're dealing with a single word, no alternants and a single verb form, use the auto-conjugation-fetching -- variant. local reconstructed_lemma, inside = reconstructed_verb_spec:match("^([^ <>()]+)(%b<>)$") if inside and alternant_multiword_spec.verb_forms[1] and not alternant_multiword_spec.verb_forms[2] then reconstructed_verb_spec = ("+%s<%s>"):format(reconstructed_lemma, alternant_multiword_spec.verb_forms[1]) end local function transform_accel_obj(slot, formobj, accel_obj) if not accel_obj then return accel_obj end if slot == "ap" or slot == "pp" or slot == "vn" then -- FIXME: [[Module:accel]] can't correctly handle more than one verb form for participles and verbal nouns accel_obj.form = slot .. "-" .. table.concat(alternant_multiword_spec.verb_forms, "،") else accel_obj.form = "verb-form-" .. reconstructed_verb_spec end return accel_obj end local function generate_link(data) local form = data.form local term = form.formval_for_link local alt = form.alt if term == "?" then term = nil alt = "?" end --W: Create simple link for verb forms without #Arabic anchor local display = alt or term local link if display then link = link_term_simple(display) else link = "" end link = link .. iut.get_footnote_text(form.footnotes, data.footnote_obj) --W: qualifier/label rendering removed along with the q/qq/l/ll modifiers; -- it required [[Module:pron qualifier]], absent on ar.wiktionary. return link end local props = { lang = lang, lemmas = lemmas, transform_accel_obj = transform_accel_obj, generate_link = generate_link, slot_list = alternant_multiword_spec.verb_slots, include_translit = false, } iut.show_forms(alternant_multiword_spec.forms, props) end ------------------------------------------------------------------------------- -- Functions to create inflection tables -- ------------------------------------------------------------------------------- -- Make the conjugation table. Called from export.show(). local function make_table(alternant_multiword_spec) local text = mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-top', args = { title = 'تصريف {title}', tall = 'yes', palette = 'green', category = 'conjugation', class = 'tr-alongside', -- temp hack to prevent extra line break } } text = text .. [=[ ! colspan="6" | <<الْمَصْدَر>> | colspan="7" | {vn} ]=] if alternant_multiword_spec.has_active then text = text .. [=[ |- ! colspan="6" | <<اِسْم الْفَاعِل>> | colspan="7" | {ap} ]=] end if alternant_multiword_spec.has_passive then text = text .. [=[ |- ! colspan="6" | <<اِسْم الْمَفْعُول>> | colspan="7" | {pp} ]=] end text = text .. [=[ |- ! colspan="999" class="separator" | ]=] if alternant_multiword_spec.has_active then text = text .. [=[ |- ! colspan="12" class="outer" | <<الْفِعْل الْمَعْلُوم>> |- ! colspan="2" | ! colspan="3" | <<الْمُفْرَد>> ! rowspan="12" class="separator" | ! colspan="2" | <<الْمُثَنَّى>> ! rowspan="12" class="separator" | ! colspan="3"| <<الْجَمْع>> |- ! colspan="2"| ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> |- ! rowspan="2" | <<الْمَاضِي>> ! class="secondary" | المذكر | rowspan="2" | {past_1s} | {past_2ms} | {past_3ms} | rowspan="2" | {past_2d} | {past_3md} | rowspan="2" | {past_1p} | {past_2mp} | {past_3mp} |- ! class="secondary" | المؤنث | {past_2fs} | {past_3fs} | {past_3fd} | {past_2fp} | {past_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَرْفُوع>> ! class="secondary" | المذكر | rowspan="2" | {ind_1s} | {ind_2ms} | {ind_3ms} | rowspan="2" | {ind_2d} | {ind_3md} | rowspan="2" | {ind_1p} | {ind_2mp} | {ind_3mp} |- ! class="secondary" | المؤنث | {ind_2fs} | {ind_3fs} | {ind_3fd} | {ind_2fp} | {ind_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَنْصُوب>> ! class="secondary" | المذكر | rowspan="2" | {sub_1s} | {sub_2ms} | {sub_3ms} | rowspan="2" | {sub_2d} | {sub_3md} | rowspan="2" | {sub_1p} | {sub_2mp} | {sub_3mp} |- ! class="secondary" | المؤنث | {sub_2fs} | {sub_3fs} | {sub_3fd} | {sub_2fp} | {sub_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَجْزُوم>> ! class="secondary" | المذكر | rowspan="2" | {juss_1s} | {juss_2ms} | {juss_3ms} | rowspan="2" | {juss_2d} | {juss_3md} | rowspan="2" | {juss_1p} | {juss_2mp} | {juss_3mp} |- ! class="secondary" | المؤنث | {juss_2fs} | {juss_3fs} | {juss_3fd} | {juss_2fp} | {juss_3fp} |- ! rowspan="2" | <<الْأَمْر>> ! class="secondary" | المذكر | rowspan="2" | | {imp_2ms} | rowspan="2" | | rowspan="2" | {imp_2d} | rowspan="2" | | rowspan="2" | | {imp_2mp} | rowspan="2" | |- ! class="secondary" | المؤنث | {imp_2fs} | {imp_2fp} ]=] end if alternant_multiword_spec.has_passive then text = text .. [=[ |- ! colspan="999" class="separator" | |- ! colspan="12" class="outer" | <<الْفِعْل الْمَجْهُول>> |- ! colspan="2" | ! colspan="3" | <<الْمُفْرَد>> ! rowspan="10" class="separator" | ! colspan="2" | <<الْمُثَنَّى>> ! rowspan="10" class="separator" | ! colspan="3" | <<الْجَمْع>> |- ! colspan="2" | ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> ! <<الْمُتَكَلِّم>> ! <<الْمُخَاطَب>> ! <<الْغَائِب>> |- ! rowspan="2" | <<الْمَاضِي>> ! class="secondary" | المذكر | rowspan="2" | {past_pass_1s} | {past_pass_2ms} | {past_pass_3ms} | rowspan="2" | {past_pass_2d} | {past_pass_3md} | rowspan="2" | {past_pass_1p} | {past_pass_2mp} | {past_pass_3mp} |- ! class="secondary" | المؤنث | {past_pass_2fs} | {past_pass_3fs} | {past_pass_3fd} | {past_pass_2fp} | {past_pass_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَرْفُوع>> ! class="secondary" | المذكر | rowspan="2" | {ind_pass_1s} | {ind_pass_2ms} | {ind_pass_3ms} | rowspan="2" | {ind_pass_2d} | {ind_pass_3md} | rowspan="2" | {ind_pass_1p} | {ind_pass_2mp} | {ind_pass_3mp} |- ! class="secondary" | المؤنث | {ind_pass_2fs} | {ind_pass_3fs} | {ind_pass_3fd} | {ind_pass_2fp} | {ind_pass_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَنْصُوب>> ! class="secondary" | المذكر | rowspan="2" | {sub_pass_1s} | {sub_pass_2ms} | {sub_pass_3ms} | rowspan="2" | {sub_pass_2d} | {sub_pass_3md} | rowspan="2" | {sub_pass_1p} | {sub_pass_2mp} | {sub_pass_3mp} |- ! class="secondary" | المؤنث | {sub_pass_2fs} | {sub_pass_3fs} | {sub_pass_3fd} | {sub_pass_2fp} | {sub_pass_3fp} |- ! rowspan="2" | <<الْمُضَارِع الْمَجْزُوم>> ! class="secondary" | المذكر | rowspan="2" | {juss_pass_1s} | {juss_pass_2ms} | {juss_pass_3ms} | rowspan="2" | {juss_pass_2d} | {juss_pass_3md} | rowspan="2" | {juss_pass_1p} | {juss_pass_2mp} | {juss_pass_3mp} |- ! class="secondary" | المؤنث | {juss_pass_2fs} | {juss_pass_3fs} | {juss_pass_3fd} | {juss_pass_2fp} | {juss_pass_3fp} ]=] end text = text .. mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-bottom', args = { notes = '{footnote}', } } local forms = alternant_multiword_spec.forms if not alternant_multiword_spec.lemmas then forms.title = "—" else local linked_lemmas = {} for _, form in ipairs(alternant_multiword_spec.lemmas) do --W: Create simple link for lemmas without #Arabic anchor -- table.insert(linked_lemmas, link_term(form.form, "term")) table.insert(linked_lemmas, link_term_simple(form.form)) end forms.title = table.concat(linked_lemmas, "، ") end local ann_parts = {} if alternant_multiword_spec.annotation ~= "" then table.insert(ann_parts, alternant_multiword_spec.annotation) end if alternant_multiword_spec.vn then local linked_vns = {} for _, form in ipairs(alternant_multiword_spec.vn) do table.insert(linked_vns, link_term(form.form, "term")) end table.insert(ann_parts, (#linked_vns > 1 and "verbal nouns" or "verbal noun") .. " " .. table.concat(linked_vns, "، ")) end local annotation = table.concat(ann_parts, "، ") --W: use only arabic morphological pattern in title annotation local morph_patterns = alternant_multiword_spec.morph_pattern if morph_patterns and #morph_patterns > 0 then annotation = table.concat(morph_patterns, "، ") forms.title = forms.title .. " (" .. annotation .. ")" end -- Format the table. local tagged_table = rsub(text, "<<(.-)>>", tag_text) return m_string_utilities.format(tagged_table, forms) end -- Helper function to get root letters and verb classification for simplified table header -- Returns: root_display (e.g., "ح س ب"), verb_type (e.g., "فعل ثلاثي صحيح سالم") local function get_verb_info(alternant_multiword_spec) local root_display = "" local verb_type = "فعل" local rad1, rad2, rad3, rad4 -- Extract radicals from the base word spec using iut.map_word_specs -- This accesses the radicals directly from the parsed verb specification iut.map_word_specs(alternant_multiword_spec, function(base) -- Get radicals from the first conjugation vowel spec if base.conj_vowels and #base.conj_vowels > 0 then local vowel_spec = base.conj_vowels[1] -- Extract radicals, handling both table and string types local function extract_radical(rad) if type(rad) == "table" then -- If it's a table, get the first element or the string representation return rad[1] or rget(rad) else return rad end end rad1 = extract_radical(vowel_spec.rad1) rad2 = extract_radical(vowel_spec.rad2) rad3 = extract_radical(vowel_spec.rad3) rad4 = vowel_spec.rad4 and extract_radical(vowel_spec.rad4) end end) -- Get basic/derived classification from morphological pattern local basic_deriv if alternant_multiword_spec.morph_pattern and #alternant_multiword_spec.morph_pattern > 0 then local morph_pattern = alternant_multiword_spec.morph_pattern[1] basic_deriv = MORPHOLOGICAL_PATTERN_TO_BASIC_DERIV[morph_pattern] end -- Format root display with spaces between letters if rad1 and rad2 and rad3 then if rad4 then -- Quadrilateral root_display = rad1 .. " " .. rad2 .. " " .. rad3 .. " " .. rad4 verb_type = "فعل رباعي" if basic_deriv then verb_type = verb_type .. " " .. basic_deriv end else -- triliteral root_display = rad1 .. " " .. rad2 .. " " .. rad3 verb_type = "فعل ثلاثي" if basic_deriv then verb_type = verb_type .. " " .. basic_deriv if basic_deriv == "مُجرَّد" then --W: the classification concerns only triliteral basic verbs (الثلاثي المُجرَّد) local classification = export.classify_triliteral_verb(rad1, rad2, rad3) verb_type = verb_type .. " " .. classification end end end end return root_display, verb_type end -- Make simplified conjugation table for ar.wiktionary. Called from export.show(). -- This table shows only past, present indicative, and imperative forms. -- No passive voice, no participles, no verbal nouns. local function make_table_ar(alternant_multiword_spec) local text = mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-top', args = { title = 'تصريف {title}', tall = 'yes', palette = 'green', category = 'conjugation', class = 'tr-alongside', -- temp hack to prevent extra line break } } -- Get verb root and classification local root_display, verb_type = get_verb_info(alternant_multiword_spec) -- First row: Root and verb type spanning all columns text = text .. "! colspan=\"2\" | <<الجذر>>: " .. root_display .. "\n" text = text .. "| colspan=\"3\" | <<'''" .. verb_type .. "'''>>\n" text = text .. [=[ |- ! colspan="2" | <<الضمائر>> ! <<الماضي>> ! <<المضارع>> ! <<الأمر>> |- ! rowspan="2" | <<المتكلّم>> | <<أنا>> | {past_1s} | {ind_1s} | |- | <<نحن>> | {past_1p} | {ind_1p} | |- ! rowspan="5" | <<المخاطب>> | <<أنتَ>> | {past_2ms} | {ind_2ms} | {imp_2ms} |- | <<أنتِ>> | {past_2fs} | {ind_2fs} | {imp_2fs} |- | <<أنتما>> | {past_2d} | {ind_2d} | {imp_2d} |- | <<أنتم>> | {past_2mp} | {ind_2mp} | {imp_2mp} |- | <<أنتنّ>> | {past_2fp} | {ind_2fp} | {imp_2fp} |- ! rowspan="6" | <<الغائب>> | <<هو>> | {past_3ms} | {ind_3ms} | |- | <<هي>> | {past_3fs} | {ind_3fs} | |- | <<هما>> | {past_3md} | {ind_3md} | |- | <<هما>> | {past_3fd} | {ind_3fd} | |- | <<هم>> | {past_3mp} | {ind_3mp} | |- | <<هنّ>> | {past_3fp} | {ind_3fp} | ]=] text = text .. mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-bottom', args = { notes = '{footnote}', } } local forms = alternant_multiword_spec.forms if not alternant_multiword_spec.lemmas then forms.title = "—" else local linked_lemmas = {} for _, form in ipairs(alternant_multiword_spec.lemmas) do --W: Create simple link for lemmas without #Arabic anchor -- table.insert(linked_lemmas, link_term(form.form, "term")) table.insert(linked_lemmas, link_term_simple(form.form)) end forms.title = table.concat(linked_lemmas, "، ") end local ann_parts = {} if alternant_multiword_spec.annotation ~= "" then table.insert(ann_parts, alternant_multiword_spec.annotation) end local annotation = table.concat(ann_parts, "، ") --W: use only arabic morphological pattern in title annotation local morph_patterns = alternant_multiword_spec.morph_pattern if morph_patterns and #morph_patterns > 0 then annotation = table.concat(morph_patterns, "، ") forms.title = forms.title .. " (" .. annotation .. ")" end -- Format the table. local tagged_table = rsub(text, "<<(.-)>>", tag_text) return m_string_utilities.format(tagged_table, forms) end ------------------------------------------------------------------------------- -- Verb classification functions -- ------------------------------------------------------------------------------- --[=[ تصنيف الفعل الثلاثي المُجرَّد حسب نوعه Classify an Arabic triliteral basic verb root based on its three letters. @param r1 الحرف الأول (first root letter - الفاء) @param r2 الحرف الثاني (second root letter - العين) @param r3 الحرف الثالث (third root letter - اللام) @return str: نوع الفعل (verb type classification in Arabic) Examples: classify_triliteral_verb('ك', 'ت', 'ب') --> 'صحيح سالم' classify_triliteral_verb('أ', 'ت', 'ي') --> 'معتل ناقص يائي مهموز الفاء' classify_triliteral_verb('ق', 'ل', 'و') --> 'معتل ناقص واوي' ]=] function export.classify_triliteral_verb(r1, r2, r3) -- تعريف أحرف العلة والهمزة -- الحروف الضعيفة (أحرف العلة): واو وياء فقط local weak_letters = {[W] = true, [Y] = true} -- الهمزة: جميع أشكال الهمزة (ء، أ، إ، ؤ، ئ) local hamza_letters = { [HAMZA] = true, -- ء [HAMZA_ON_ALIF] = true, -- أ [HAMZA_UNDER_ALIF] = true, -- إ [HAMZA_ON_W] = true, -- ؤ [HAMZA_ON_Y] = true -- ئ } -- فحص كل حرف local is_hamza = {hamza_letters[r1] or false, hamza_letters[r2] or false, hamza_letters[r3] or false} local is_weak = {weak_letters[r1] or false, weak_letters[r2] or false, weak_letters[r3] or false} -- عدد أحرف العلة local weak_count = (is_weak[1] and 1 or 0) + (is_weak[2] and 1 or 0) + (is_weak[3] and 1 or 0) -- فحص التضعيف (الحرف الثاني = الثالث) local is_doubled = (r2 == r3) -- ═══════════════════════════════════════════════════════════ -- المُعتل: الفعل الذي يحتوي على حرف أو أكثر من أحرف العلة -- ═══════════════════════════════════════════════════════════ if weak_count > 0 then -- ───────────────────────────────────────────────────── -- لفيف: يحتوي على حرفين أو أكثر من أحرف العلة -- ───────────────────────────────────────────────────── if weak_count >= 2 then -- مقرون: حرفا العلة متجاوران if is_weak[2] and is_weak[3] then -- الثاني والثالث if is_hamza[1] then return "معتل لفيف مقرون مهموز الفاء" end return "معتل لفيف مقرون" elseif is_weak[1] and is_weak[2] then -- الأول والثاني return "معتل لفيف مقرون" -- مفروق: الأول والثالث حرفا علة elseif is_weak[1] and is_weak[3] then if is_hamza[2] then return "معتل لفيف مفروق مهموز العين" end return "معتل لفيف مفروق" end -- ───────────────────────────────────────────────────── -- مثال: الحرف الأول حرف علة -- ───────────────────────────────────────────────────── elseif is_weak[1] then if r1 == W then -- واوي if is_hamza[3] then return "معتل مثال واوي مهموز اللام" elseif is_doubled then return "معتل مثال واوي مُضعَّف" elseif is_hamza[2] then return "معتل مثال واوي مهموز العين" end return "معتل مثال واوي" else -- يائي (ي) if is_doubled then return "معتل مثال يائي مُضعَّف" elseif is_hamza[2] then return "معتل مثال يائي مهموز العين" end return "معتل مثال يائي" end -- ───────────────────────────────────────────────────── -- أجوف: الحرف الثاني حرف علة -- ───────────────────────────────────────────────────── elseif is_weak[2] then if r2 == W then -- واوي if is_hamza[3] then return "معتل أجوف واوي مهموز اللام" elseif is_hamza[1] then return "معتل أجوف واوي مهموز الفاء" end return "معتل أجوف واوي" else -- يائي (ي) if is_hamza[3] then return "معتل أجوف يائي مهموز اللام" elseif is_hamza[1] then return "معتل أجوف يائي مهموز الفاء" end return "معتل أجوف يائي" end -- ───────────────────────────────────────────────────── -- ناقص: الحرف الثالث حرف علة -- ───────────────────────────────────────────────────── elseif is_weak[3] then if r3 == W then -- واوي if is_hamza[1] then return "معتل ناقص واوي مهموز الفاء" elseif is_hamza[2] then return "معتل ناقص واوي مهموز العين" end return "معتل ناقص واوي" else -- يائي (ي) if is_hamza[1] then return "معتل ناقص يائي مهموز الفاء" elseif is_hamza[2] then return "معتل ناقص يائي مهموز العين" end return "معتل ناقص يائي" end end -- ═══════════════════════════════════════════════════════════ -- الصحيح: الفعل الذي لا يحتوي على أحرف العلة -- ═══════════════════════════════════════════════════════════ else -- ───────────────────────────────────────────────────── -- مهموز: يحتوي على همزة -- ───────────────────────────────────────────────────── if is_hamza[1] or is_hamza[2] or is_hamza[3] then -- مهموز الفاء واللام: الأول والثالث همزة if is_hamza[1] and is_hamza[3] then return "صحيح مهموز الفاء واللام" -- مهموز الفاء elseif is_hamza[1] then if is_doubled then return "صحيح مُضعَّف مهموز الفاء" end return "صحيح مهموز الفاء" -- مهموز العين elseif is_hamza[2] then return "صحيح مهموز العين" -- مهموز اللام elseif is_hamza[3] then return "صحيح مهموز اللام" end -- ───────────────────────────────────────────────────── -- مُضعَّف: الحرف الثاني = الثالث -- ───────────────────────────────────────────────────── elseif is_doubled then return "صحيح مُضعَّف" -- ───────────────────────────────────────────────────── -- سالم: لا همزة، لا حرف علة، لا تضعيف -- ───────────────────────────────────────────────────── else return "صحيح سالم" end end return "غير معروف" end ------------------------------------------------------------------------------- -- External entry points -- ------------------------------------------------------------------------------- --W: Normalize root to use underscores as separators -- Accepts: "كتب", "ك_ت_ب", or "ك ت ب" -- Returns: "ك_ت_ب" local function normalize_root(root) if not root then return nil end -- Helper function to normalize a single character/radical local function normalize_char(char) -- Convert all hamza variants to canonical bare hamza if char == HAMZA_ON_ALIF or char == HAMZA_UNDER_ALIF or char == HAMZA_ON_W or char == HAMZA_ON_Y then return HAMZA -- Convert alif maqṣūra to yaa (ى → ي) elseif char == AMAQ then return Y -- Reject alif - it's not a root radical elseif char == ALIF then error("Root contains alif (ا) which is not a valid root radical. For weak verbs, use و (waw) or ي (yaa) instead.") end return char end -- Convert alif madda to hamza + alif for processing (will be caught as error below) root = root:gsub(AMAD, HAMZA .. ALIF) -- Replace spaces with underscores root = root:gsub(" ", "_") -- If no underscores yet, insert them between each character if not root:find("_") then -- Extract only Arabic letters (excluding diacritics) -- Arabic letter range: U+0621 to U+064A (basic letters) local chars = {} for char in mw.ustring.gmatch(root, "[ء-ي]") do table.insert(chars, normalize_char(char)) end root = table.concat(chars, "_") else -- Root already has underscores (e.g., "أ_م_ن"), normalize each radical local parts = mw.text.split(root, "_") for i, part in ipairs(parts) do parts[i] = normalize_char(part) end root = table.concat(parts, "_") end return root end --W: Construct the [1] parameter from root (جذر) and morphological pattern (وزن) -- Example: -- input: root="كتب", pattern="فعَل يفعُل" -- output: "ك_ت_ب<I/a~u>" local function construct_verb_spec(root, pattern) if not root or not pattern then return nil end local normalized_root = normalize_root(root) local form_code = MORPHOLOGICAL_PATTERN_TO_FORM[pattern] if not form_code then error("Unknown morphological pattern (وزن صرفي): " .. pattern) end return normalized_root .. "<" .. form_code .. ">" end -- Wrapper function that accepts جذر (root) and وزن (pattern) as parameters -- Returns: verb_type (e.g., "فعل ثلاثي مُجرَّد صحيح سالم") -- Example: export.get_verb_type({["جذر"] = "كتب", ["وزن"] = "فعَل يفعُل"}) function export.get_verb_type(args) local verb_spec if args["جذر"] and args["وزن"] then verb_spec = construct_verb_spec(args["جذر"], args["وزن"]) elseif args[1] then verb_spec = args[1] else error("Must provide either جذر and وزن, or a verb specification in args[1]") end local alternant_multiword_spec = export.do_generate_forms({[1] = verb_spec}) local _, verb_type = get_verb_info(alternant_multiword_spec) return verb_type end -- Append two lists `l1` and `l2`, removing duplicates. If either is {nil}, just return the other. local function combine_lists(l1, l2) -- combine_footnotes() does exactly what we want. return iut.combine_footnotes(l1, l2) end local function combine_metadata(data) local src1 = data.form1 local src2 = data.form2 local dest = data.dest_form dest.uncertain = src1.uncertain or src2.uncertain if src1.genders and src2.genders and not m_table.deepEquals(src1.genders, src2.genders) then -- do nothing else dest.genders = src1.genders or src2.genders end if src1.pos and src2.pos and src1.pos ~= src2.pos then -- do nothing else dest.pos = src1.pos or src2.pos end -- Don't copy .alt, .gloss, .lit, .id, which describe a single term and don't extend to multiword terms. dest.q = combine_lists(src1.q, src2.q) dest.qq = combine_lists(src1.qq, src2.qq) dest.l = combine_lists(src1.l, src2.l) dest.ll = combine_lists(src1.ll, src2.ll) end -- Externally callable function to parse and conjugate a verb given user-specified arguments. -- Return value is WORD_SPEC, an object where the conjugated forms are in `WORD_SPEC.forms` -- for each slot. If there are no values for a slot, the slot key will be missing. The value -- for a given slot is a list of objects {form=FORM, footnotes=FOOTNOTES}. function export.do_generate_forms(args, source_template, headword_head) local PAGENAME = mw.loadData("Module:headword/data").pagename local function in_template_space() local nsText = mw.title.getCurrentTitle().nsText return nsText == "Template" or nsText == "قالب" end -- Determine the verb spec we're being asked to generate the conjugation of. This may be taken from the current page -- title or the value of |pagename=; but not when called from {{ar-verb form}}, where the page title is a -- non-lemma form. Note that the verb spec may omit the lemma; e.g. it may be "<II>". For this reason, we use the -- value of `pagename` computed here down below, when calling normalize_all_lemmas(). local pagename = source_template ~= "ar-verb form" and args.pagename or PAGENAME local head = headword_head or pagename --W: If both root (جذر) and morphological pattern (وزن) are provided, construct [1] if args["جذر"] and args["جذر"] ~= "" and args["وزن"] and args["وزن"] ~= "" then local constructed = construct_verb_spec(args["جذر"], args["وزن"]) if constructed and constructed ~= "" then args[1] = constructed end end local arg1 = args[1] if not arg1 then if (pagename == "ar-conj" or pagename == "ar-verb" or pagename == "ar-verb form" or pagename == "تصريف") and in_template_space() then arg1 = "كتب<I/a~u.pass>" else arg1 = "<>" end end -- When called from {{ar-verb form}}, determine the non-lemma form whose inflections we're being asked to -- determine. This normally comes from the page title or the value of |pagename=. local verb_form_of_form if source_template == "ar-verb form" then verb_form_of_form = args.pagename if not verb_form_of_form then if PAGENAME == "ar-verb form" and in_template_space() then verb_form_of_form = "كتبت" else verb_form_of_form = PAGENAME end end end local incorporated_headword_head_into_lemma = false if arg1:find("^<.*>$") then -- missing lemma if head:find(" ") then -- If multiword lemma, try to add arg spec after the first word. -- Try to preserve the brackets in the part after the verb, but don't do it -- if there aren't the same number of left and right brackets in the verb -- (which means the verb was linked as part of a larger expression). local first_word, post = rmatch(head, "^(.-)( .*)$") local left_brackets = rsub(first_word, "[^%[]", "") local right_brackets = rsub(first_word, "[^%]]", "") if #left_brackets == #right_brackets then arg1 = iut.remove_redundant_links(first_word) .. arg1 .. post incorporated_headword_head_into_lemma = true else -- Try again using the form without links. local linkless_head = m_links.remove_links(head) if linkless_head:find(" ") then first_word, post = rmatch(linkless_head, "^(.-)( .*)$") arg1 = first_word .. arg1 .. post else error("Unable to incorporate <...> spec into explicit head due to a multiword linked verb or " .. "unbalanced brackets; please include <> explicitly: " .. arg1) end end else -- Will be incorporated through `head` below in the call to normalize_all_lemmas(). incorporated_headword_head_into_lemma = true end end local parse_props = { parse_indicator_spec = parse_indicator_spec, angle_brackets_omittable = true, allow_blank_lemma = true, } local alternant_multiword_spec = iut.parse_inflected_text(arg1, parse_props) alternant_multiword_spec.pos = pos or "verbs" alternant_multiword_spec.args = args alternant_multiword_spec.source_template = source_template alternant_multiword_spec.verb_form_of_form = verb_form_of_form alternant_multiword_spec.incorporated_headword_head_into_lemma = incorporated_headword_head_into_lemma normalize_all_lemmas(alternant_multiword_spec, head) detect_all_indicator_specs(alternant_multiword_spec) local inflect_props = { lang = lang, slot_list = alternant_multiword_spec.verb_slots, inflect_word_spec = conjugate_verb, combine_metadata = combine_metadata, -- We add links around the generated verbal forms rather than allow the entire multiword -- expression to be a link, so ensure that user-specified links get included as well. include_user_specified_links = true, } iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props) if debug_translit then for slot, forms in pairs(alternant_multiword_spec.forms) do for _, form in ipairs(forms) do if form.translit then local full_form_translit = (lang:transliterate(m_links.remove_links(form.form))) if full_form_translit ~= form.translit then error(("Internal error: For slot '%s', form '%s' incremental translit '%s' not same as full translit '%s'"): format(slot, form.form, form.translit, full_form_translit)) end end form.form = iut.remove_redundant_links(form.form) end end end -- Remove redundant brackets around entire forms. for slot, forms in pairs(alternant_multiword_spec.forms) do for _, form in ipairs(forms) do form.form = iut.remove_redundant_links(form.form) end end -- Apply NFC-style normalization: reorder shadda+vowel to vowel+shadda -- to match MediaWiki's automatic NFC normalization (which is applied on enwiktionary -- but not in local Lua environments). This reverses the postprocessing step. for slot, forms in pairs(alternant_multiword_spec.forms) do for _, form in ipairs(forms) do form.form = apply_nfc_shadda(form.form) end end determine_slot_uncertainty_from_forms(alternant_multiword_spec) determine_verb_properties_from_forms(alternant_multiword_spec) compute_categories_and_annotation(alternant_multiword_spec) if args.json and source_template == "ar-conj" then -- There is a circular reference in `base.alternant_multiword_spec`, which points back to top level. iut.map_word_specs(alternant_multiword_spec, function(base) base.alternant_multiword_spec = nil end) return require("Module:JSON").toJSON(alternant_multiword_spec) end return alternant_multiword_spec end -- Entry point for {{ar-conj}}. Template-callable function to parse and conjugate a verb given -- user-specified arguments and generate a displayable table of the conjugated forms. function export.show(frame) local parent_args = frame:getParent() and frame:getParent().args or frame.args --W local params = { [1] = {}, ["noautolinktext"] = {type = "boolean"}, ["noautolinkverb"] = {type = "boolean"}, ["t"] = {}, -- for use by {{ar-verb form}}; otherwise ignored ["id"] = {}, -- for use by {{ar-verb form}}; otherwise ignored ["pagename"] = {}, -- for testing/documentation pages ["json"] = {type = "boolean"}, -- for bot use --W: additional parameters for ar.wiktionary.org ["جذر"] = {type = "string"}, --W: الجذر اللغوي, root ["وزن"] = {type = "string"}, --W: الوزن الصرفي, morphological pattern ["كامل"] = {type = "boolean"}, --W: show full table (default is simplified table) } local args = require("Module:parameters").process(parent_args, params) --W: If both root (جذر) and morphological pattern (وزن) are provided, construct [1] if args["جذر"] and args["جذر"] ~= "" and args["وزن"] and args["وزن"] ~= "" then local constructed = construct_verb_spec(args["جذر"], args["وزن"]) if constructed and constructed ~= "" then args[1] = constructed end end local alternant_multiword_spec = export.do_generate_forms(args, "ar-conj") if type(alternant_multiword_spec) == "string" then -- JSON return value return alternant_multiword_spec end show_forms(alternant_multiword_spec) --W: Choose table type: simplified (default) or full (comprehensive) local table_html if args["كامل"] then table_html = make_table(alternant_multiword_spec) else table_html = make_table_ar(alternant_multiword_spec) end --W: don't add categories --W: TODO: all lines with insert_cat() are to be removed. -- return table_html .. -- require("Module:utilities").format_categories(alternant_multiword_spec.categories, lang, nil, nil, force_cat) return table_html end function export.verb_forms(frame) local parargs = frame:getParent().args local params = { [1] = {}, [2] = {}, [3] = {}, [4] = {}, [5] = {}, pagename = {}, } for _, form in ipairs(allowed_vforms) do -- FIXME: We go up to 5 here. The code supports unlimited variants but it's unlikely we will ever see more than -- 2. for index = 1, 5 do local prefix = index == 1 and form or form .. index params[prefix .. "-pv"] = {} for _, extn in ipairs { "", "-vn", "-ap", "-pp" } do params[prefix .. extn] = {} params[prefix .. extn .. "-head"] = {} -- FIXME: No -tr? params[prefix .. extn .. "-gloss"] = {} end end end local args = require("Module:parameters").process(parargs, params) local i = 1 local past_vowel_re = "^[aui,]*$" local combined_root = nil if not args[i] or rfind(args[i], past_vowel_re) then combined_root = args.pagename or mw.loadData("Module:headword/data").pagename if not rfind(combined_root, "^([^ ]) ([^ ]) ([^ ])$") and not rfind(combined_root, "^([^ ]) ([^ ]) ([^ ]) ([^ ])$") then error("When inferring roots from page title, need three or four space-separated radicals: " .. combined_root) end elseif rfind(args[i], " ") then combined_root = args[i] i = i + 1 else local separate_roots = {} while args[i] and not rfind(args[i], past_vowel_re) do table.insert(separate_roots, args[i]) i = i + 1 end combined_root = table.concat(separate_roots, " ") end local past_vowel = args[i] i = i + 1 if past_vowel and not rfind(past_vowel, past_vowel_re) then error("Unrecognized past vowel, should be 'a', 'i', 'u', 'a,u', etc. or empty: " .. past_vowel) end -- Spaces interfere with parsing as a unit in [[Module:inflection utilities]], so replace with underscore. combined_root = combined_root:gsub(" ", "_") local split_root = rsplit(combined_root, "_") -- Map from verb forms (I, II, etc.) to a table of verb properties, -- which has entries e.g. for "verb" (either true to autogenerate the verb -- head, or an explicitly specified verb head using e.g. argument "I-head"), -- and for "verb-gloss" (which comes from e.g. the argument "I" or "I-gloss"), -- and for "vn" and "vn-gloss", "ap" and "ap-gloss", "pp" and "pp-gloss". local verb_properties = {} for _, form in ipairs(allowed_vforms) do local formpropslist = {} local derivs = {{"verb", ""}, {"vn", "-vn"}, {"ap", "-ap"}, {"pp", "-pp"}} local index = 1 while true do local formprops = {} local prefix = index == 1 and form or form .. index if prefix == "I" then formprops.pv = past_vowel end if args[prefix .. "-pv"] then formprops.pv = args[prefix .. "-pv"] end for _, deriv in ipairs(derivs) do local prop = deriv[1] local extn = deriv[2] if args[prefix .. extn] == "+" then formprops[prop] = true elseif args[prefix .. extn] == "-" then formprops[prop] = false elseif args[prefix .. extn] then formprops[prop] = true formprops[prop .. "-gloss"] = args[prefix .. extn] end if args[prefix .. extn .. "-head"] then if formprops[prop] == nil then formprops[prop] = true end formprops[prop] = args[prefix .. extn .. "-head"] end if args[prefix .. extn .. "-gloss"] then if formprops[prop] == nil then formprops[prop] = true end formprops[prop .. "-gloss"] = args[prefix .. extn .. "-gloss"] end end if formprops.verb then -- If a verb form specified, also turn on vn (unless form I, with -- unpredictable vn) and ap, and maybe pp, according to form, -- weakness and past vowel. But don't turn these on if there's -- an explicit on/off specification for them (e.g. I-pp=-). if form ~= "I" and formprops.vn == nil then formprops.vn = true end if formprops.ap == nil then formprops.ap = true end local weakness = weakness_from_radicals(form, split_root[1], split_root[2], split_root[3], split_root[4]) if formprops.pp == nil and not vform_probably_no_passive(form, weakness, rsplit(formprops.pv or "", ","), {}) then formprops.pp = true end if formprops.verb == true or formprops.vn == true or formprops.ap == true or formprops.pp == true then formprops.need_autogen = true end table.insert(formpropslist, formprops) index = index + 1 else break end end table.insert(verb_properties, {form, formpropslist}) end -- Go through and create the verb form derivations as necessary, when they haven't been explicitly given. for _, vplist in ipairs(verb_properties) do local vform = vplist[1] for _, props in ipairs(vplist[2]) do if props.need_autogen then local form_with_vowels if vform == "I" then local pv = props.pv if not pv then -- Make up likely past vowels based on weakness and actual radical. if split_root[3] == W then -- final-weak form_with_vowels = "I/a~u" elseif split_root[3] == Y then form_with_vowels = "I/a~i" elseif split_root[2] == W then --hollow form_with_vowels = "I/u~u" elseif split_root[2] == Y then form_with_vowels = "I/i~i" else -- most common; doesn't matter so much since we're not displaying the non-past form_with_vowels = "I/a~u" end else local pvs = rsplit(pv, ",") local vowel_sufs = {} for _, pv in ipairs(pvs) do local vowel_spec if pv == "a" then -- Make up likely past vowels based on weakness and actual radical. if split_root[3] == W then -- final-weak vowel_spec = "a~u" elseif split_root[3] == Y then vowel_spec = "a~i" elseif split_root[2] == W then --hollow vowel_spec = "a~u" elseif split_root[2] == Y then vowel_spec = "a~i" else -- most common; doesn't matter so much since we're not displaying the non-past vowel_spec = "a~u" end elseif pv == "i" then -- most common; doesn't matter so much since we're not displaying the non-past vowel_spec = "i~a" elseif pv == "u" then -- most common; doesn't matter so much since we're not displaying the non-past vowel_spec = "u~u" else error(("Internal error: Bad past vowel '%s' in {{ar-verb forms}}"):format(pv)) end table.insert(vowel_sufs, vowel_spec) end form_with_vowels = "I/" .. table.concat(vowel_sufs, "/") end else form_with_vowels = vform end local angle_bracket_spec = ("%s<%s.pass>"):format(combined_root, form_with_vowels) local alternant_multiword_spec = export.do_generate_forms({angle_bracket_spec}, "ar-verb forms") local function format_forms(forms) if not forms then return "-" -- FIXME: Throw an error? end local formatted = {} for _, form in ipairs(forms) do if form.translit then table.insert(formatted, ("%s//%s"):format(form.form, form.translit)) else table.insert(formatted, form.form) end end return table.concat(formatted, "،") end if props.verb == true then props.verb = format_forms(alternant_multiword_spec.forms.past_3ms) end for _, deriv in ipairs({"vn", "ap", "pp"}) do if props[deriv] == true then props[deriv] = format_forms(alternant_multiword_spec.forms[deriv]) end end end end end -- Go through and output the result local formtextarr = {} for _, vplist in ipairs(verb_properties) do local form = vplist[1] for _, props in ipairs(vplist[2]) do local textarr = {} if props.verb then local text = "* '''[[Appendix:Arabic verbs#Form " .. form .. "|Form " .. form .. "]]''': " local linktext = {} local splitheads = rsplit(props.verb, "[,،]") for _, head in ipairs(splitheads) do table.insert(linktext, m_links.full_link({lang = lang, term = head, gloss = props["verb-gloss"]})) end text = text .. table.concat(linktext, "، ") table.insert(textarr, text) for _, derivengl in ipairs({{"vn", "Verbal noun"}, {"ap", "Active participle"}, {"pp", "Passive participle"}}) do local deriv = derivengl[1] local engl = derivengl[2] if props[deriv] then local text = "** " .. engl .. ": " local linktext = {} local splitheads = rsplit(props[deriv], "[,،]") for _, head in ipairs(splitheads) do local ar, translit = head:match("^(.*)//(.-)$") if not ar then ar = head end table.insert(linktext, m_links.full_link {lang = lang, term = ar, tr = translit, gloss = props[deriv .. "-gloss"]} ) end text = text .. table.concat(linktext, "، ") table.insert(textarr, text) end end table.insert(formtextarr, table.concat(textarr, "\n")) end end end return table.concat(formtextarr, "\n") end -- Infer radicals from lemma headword (i.e. 3rd masculine singular past) and verb form (I, II, etc.). Throw an error if -- headword is malformed. A given returned radical may be actually be a list of possible radicals, where the first one -- should be used if the user didn't explicitly give the radical. If the list contains a field `ambig = true`, the -- radical is considered ambiguous and should not be categorized. `is_reduced` indicates that the user specified -- `.reduced` to indicate that the verb form is reduced by assimilation and/or haplology (typically archaic Koranic -- forms such as اِدَّارَأَ instead of تَدَارَأَ; or اِسْطَاعَ instead of اِسْتِطَاعَ; etc. function export.infer_radicals(data) local headword, vform, passive, past_vowel, nonpast_vowel, is_reduced = data.headword, data.vform, data.passive, data.past_vowel, data.nonpast_vowel, data.is_reduced past_vowel = past_vowel or "-" nonpast_vowel = nonpast_vowel or "-" local function verify_vowel(vowel, param) --W: A past/non-past vowel may legitimately be a form object {form=..., footnotes=...} -- when the user attaches a footnote, e.g. <I/a[نادر]~u>, so compare the formval. -- Comparing the raw value crashed on Lua 5.1 ("bad argument #2 to 'format'"). local formval = rget(vowel) if formval ~= A and formval ~= I and formval ~= U and formval ~= "-" then error(("Internal error: Bad value for %s: %s (should be Arabic diacritic vowel or '-')"):format( param, dump(vowel))) end end verify_vowel(past_vowel, "past_vowel") verify_vowel(nonpast_vowel, "nonpast_vowel") local ch = {} local form_viii_assim, variant -- sub out alif-madda for easier processing headword = rsub(headword, AMAD, HAMZA .. ALIF) local function infer_err(msg, noann) local anns = {} local nohead, novform if noann == "nohead" then nohead = true elseif noann == "novform" then novform = true elseif noann == "nohead-vform" then nohead = true novform = true elseif noann then error(("Internal error: Unrecognized value for 'noann': %s"):format(dump(noann))) end if not nohead then table.insert(anns, ("headword=%s"):format(data.headword)) end if not novform then table.insert(anns, ("verb form=%s"):format(data.vform)) end anns = table.concat(anns, "، ") if anns ~= "" then anns = ": " .. anns end error(msg .. anns) end local len = ulen(headword) local expected_length -- extract the headword letters into an array for i = 1, len do table.insert(ch, usub(headword, i, i)) end -- check that the letter at the given index is the given string, or -- is one of the members of the given array local function check(index, must) local letter = ch[index] if type(must) == "string" then if not letter then infer_err("Letter " .. index .. " is nil") end if letter ~= must then infer_err(("For verb form %s, letter %s must be %s, not %s"):format(vform, index, must, letter), "novform") end elseif not m_table.contains(must, letter) then infer_err("For verb form " .. vform .. ", radical " .. index .. " must be one of " .. table.concat(must, " ") .. ", not " .. letter, "novform") end end -- Check that length of headword is within [min, max] local function check_len(min, max) if min and len < min then infer_err(("Not enough letters for verb form %s, expected at least %s"):format(vform, min), "novform") end if max and len > max then infer_err(("Too many letters for verb form %s, expected at most %s"):format(vform, max), "novform") end end -- If the vowels are i~a or u~u, a form I verb beginning with w- normally keeps the w in the non-past. Otherwise it -- loses it (i.e. it is "assimilated"). local function form_I_w_non_assimilated() return req(past_vowel, I) and req(nonpast_vowel, A) or req(past_vowel, U) and req(nonpast_vowel, U) end -- Convert radicals to canonical form (handle various hamza varieties and check for misplaced alif or alif maqṣūra; -- legitimate cases of these letters are handled above). local function convert(rad, index) if type(rad) == "table" then for i, r in ipairs(rad) do rad[i] = convert(r, index) end return rad elseif rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or rad == HAMZA_ON_W or rad == HAMZA_ON_Y then return HAMZA elseif rad == AMAQ then infer_err("Radical " .. index .. " must not be alif maqṣūra") elseif rad == ALIF then infer_err("Radical " .. index .. " must not be alif") else return rad end end local quadlit = vform:find("q$") -- find first radical, start of second/third radicals, check for -- required letters local radstart, rad1, rad2, rad3, rad4 local weakness if vform == "I" or vform == "II" then rad1 = ch[1] radstart = 2 elseif vform == "III" then rad1 = ch[1] check(2, {ALIF, W}) -- W occurs in passive-only verbs radstart = 3 elseif vform == "IV" then -- this would be alif-madda but we replaced it with hamza-alif above. if ch[1] == HAMZA and ch[2] == ALIF then rad1 = HAMZA else check(1, HAMZA_ON_ALIF) rad1 = ch[2] end radstart = 3 elseif vform == "V" then check(1, is_reduced and ALIF or T) rad1 = ch[2] radstart = 3 elseif vform == "VI" then check(1, is_reduced and ALIF or T) if ch[2] == AMAD then rad1 = HAMZA radstart = 3 else rad1 = ch[2] check(3, {ALIF, W}) -- W occurs in passive-only verbs radstart = 4 end elseif vform == "VII" then check(1, ALIF) if is_reduced then check(2, M) rad1 = M radstart = 3 else check(2, N) rad1 = ch[3] radstart = 4 end elseif vform == "VIII" then check(1, ALIF) rad1 = ch[2] if rad1 == "د" then rad1 = {"د", "ذ"} -- not considered ambiguous since it's usually د radstart = 3 form_viii_assim = "دّ" elseif rad1 == "ظ" and ch[3] == "ط" and len >= 5 then -- [[اظطلم]], variant of [[اظلم]] radstart = 4 form_viii_assim = "ظْط" elseif rad1 == "ذ" and ch[3] == "د" and len >= 5 then -- [[اذدكر]], variant of [[اذكر]] radstart = 4 form_viii_assim = "ذْد" elseif rad1 == T or rad1 == "ث" or rad1 == "ذ" or rad1 == "ط" or rad1 == "ظ" then radstart = 3 form_viii_assim = rad1 .. SH elseif rad1 == "ز" then check(3, "د") radstart = 4 form_viii_assim = "زْد" elseif rad1 == "ص" or rad1 == "ض" then check(3, "ط") radstart = 4 form_viii_assim = rad1 .. SK .. "ط" else check(3, T) radstart = 4 rad1 = convert(rad1, 1) form_viii_assim = rad1 .. SK .. "ت" end if rad1 == T then -- Radical is ambiguous, might be ت or و or ي but doesn't affect conjugation. Note that there are no -- form-VIII verbs with initial radical ي given in Hans Wehr but Lane mentions at least: -- - (page 2973) اِتَّأَسَ, with assimilation of the ي to ت, from root ي ء س; -- - (page 2975) اِتَّبَسَ non-past يَتَّبِسُ and alternative اِيتَبَسَ non-past يَاتَبِسُ from the root ي ب س; -- - (page 2976) اِتَّسَرَ non-past يَتَّسِرُ or alternatively يَأْتَسِرُ with hamza preserved from the root ي س ر. -- These alternative forms seem very rare and probably not worth worrying about, but if we want to handle -- them, we can do it when the time comes. rad1 = {T, W, Y, ambig = true} -- اِتَّخَذَ irregularly has hamza as the radical but assimilates like و if ch[3] == "خ" and ch[4] == "ذ" then rad1[4] = HAMZA end end elseif vform == "IX" then check(1, ALIF) rad1 = ch[2] radstart = 3 elseif vform == "X" then check(1, ALIF) check(2, S) if is_reduced then rad1 = ch[3] radstart = 4 else check(3, T) rad1 = ch[4] radstart = 5 end elseif vform == "Iq" then rad1 = ch[1] rad2 = ch[2] radstart = 3 elseif vform == "IIq" then check(1, T) rad1 = ch[2] rad2 = ch[3] radstart = 4 elseif vform == "IIIq" then check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, N) radstart = 5 elseif vform == "IVq" then check(1, ALIF) rad1 = ch[2] rad2 = ch[3] radstart = 4 elseif vform == "XI" then check_len(5, 5) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, ALIF) rad3 = ch[5] weakness = "sound" elseif vform == "XII" then check(1, ALIF) rad1 = ch[2] if ch[3] ~= ch[5] then infer_err("For verb form XII, letters 3 and 5 should be the same", "novform") end check(4, W) radstart = 5 elseif vform == "XIII" then check_len(5, 5) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, W) rad3 = ch[5] if rad3 == AMAQ then weakness = "final-weak" else weakness = "sound" end elseif vform == "XIV" then check_len(6, 6) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, N) rad3 = ch[5] if ch[6] == AMAQ then check_waw_ya(rad3) weakness = "final-weak" else if ch[5] ~= ch[6] then infer_err("For verb form XIV, letters 5 and 6 should be the same", "novform") end weakness = "sound" end elseif vform == "XV" then check_len(6, 6) check(1, ALIF) rad1 = ch[2] rad2 = ch[3] check(4, N) rad3 = ch[5] if rad3 == Y then check(6, ALIF) else check(6, AMAQ) end weakness = "sound" else error("Internal error: Unrecognized verb form " .. vform) end -- Process the last two radicals. RADSTART is the index of the first of the two. If it's nil then all radicals have -- already been processed above, and we don't do anything. if radstart then -- There must (normally) be one or two letters left. if len == radstart then if vform == "I" and ch[len] == Y then -- short form حَيَّ weakness = "final-weak" rad2 = Y rad3 = Y variant = "short" elseif vform == "IV" and rad1 == "ر" and ch[len] == AMAQ then -- irregular verb أَرَى weakness = "final-weak" rad2 = HAMZA rad3 = Y elseif vform == "X" and rad1 == "ح" and ch[len] == AMAQ then -- irregular verb اِسْتَحَى weakness = "final-weak" rad2 = Y rad3 = Y variant = "short" else -- If one letter left, then it's a geminate verb. If the letter is alif or alif maqṣūra, it will trigger -- an error down the line. if vform_supports_geminate(vform) then weakness = "geminate" rad2 = ch[len] rad3 = ch[len] if vform == "III" or vform == "VI" then variant = "short" end else infer_err("Apparent geminate verb, but geminate verbs not allowed for this verb form") end end elseif quadlit then -- Process last two radicals of a quadriliteral verb form. rad3 = ch[radstart] rad4 = ch[radstart + 1] expected_length = radstart + 1 check_len(expected_length) if rad4 == AMAQ or rad4 == ALIF and rad3 == Y or rad4 == Y then -- rad4 can be Y in passive-only verbs. if vform_supports_final_weak(vform) then weakness = "final-weak" -- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws in a row); it could be wāw or -- yāʾ, but doesn't affect the conjugation. rad4 = rad3 == W and {Y, W, ambig = true} or {W, Y, ambig = true} else infer_err("Last radical is " .. rad4 .. " but verb form " .. vform .. " doesn't support final-weak verbs", "novform") end else weakness = "sound" end else -- Process last two radicals of a triliteral verb form. rad2 = ch[radstart] rad3 = ch[radstart + 1] expected_length = radstart + 1 check_len(expected_length) if vform == "I" and (is_waw_ya(rad3) or rad3 == ALIF or rad3 == AMAQ) then local inferred_past_vowel, inferred_nonpast_vowel -- Check for final-weak form I verb. It can end in tall alif (rad3 = wāw) or alif maqṣūra (rad3 = yāʾ) -- or a wāw or yāʾ (with a past vowel of i or u, e.g. nasiya/yansā "forget" or with a passive-only -- verb). if rad1 == W and not form_I_w_non_assimilated() then weakness = "assimilated+final-weak" else weakness = "final-weak" end if rad3 == ALIF then rad3 = W inferred_past_vowel = A inferred_nonpast_vowel = U if is_passive_only(passive) then infer_err("Final-weak form-I passive verbs should end in yāʔ (ي), not tall alif (ا)", "novform") end elseif rad3 == AMAQ then rad3 = Y inferred_past_vowel = A inferred_nonpast_vowel = I if is_passive_only(passive) then infer_err("Final-weak form-I passive verbs should end in yāʔ (ي), not alif maqṣūra (ى)", "novform") end elseif rad1 == "ح" and rad2 == Y and rad3 == Y then -- Long variant حَيِيَ. inferred_past_vowel = I inferred_nonpast_vowel = A variant = "long" else if not is_passive_only(passive) then -- does a non-passive final-weak verb in -uwa ever happen? (YES: e.g. [[رجو]] "to be slack") inferred_past_vowel = rad3 == Y and I or U inferred_nonpast_vowel = A end -- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws); it could be wāw or yāʾ, but -- doesn't affect the conjugation. rad3 = (rad1 == W or rad2 == W) and {Y, W, ambig = true} or {W, Y, ambig = true} -- ambiguous end if inferred_past_vowel then local raw_past_vowel = rget(past_vowel) local raw_nonpast_vowel = rget(nonpast_vowel) if raw_past_vowel ~= "-" then if raw_past_vowel ~= inferred_past_vowel then infer_err(("Final-weak form-I verb inferred past vowel %s, which disagrees with " .. "explicitly specified %s"):format(undia[inferred_past_vowel], undia[raw_past_vowel]), "novform") else -- in case of footnote in past_vowel inferred_past_vowel = past_vowel end end if raw_nonpast_vowel ~= "-" and raw_nonpast_vowel ~= A and inferred_nonpast_vowel == U then -- if inferred as I or A, the reality can be the reverse; form-I final-weak verbs with a~a and -- i~i exist, e.g. سَعَى/يَسْعَى, وَلِيَ/يَلِي. Weird verb [[صها]] (also written [[صهى]]) has non-past -- يصهى so we can't throw an error in this situation. if raw_nonpast_vowel ~= inferred_nonpast_vowel then infer_err(("Final-weak form-I verb inferred non-past vowel %s, which disagrees with " .. "explicitly specified %s"):format(undia[inferred_nonpast_vowel], undia[raw_nonpast_vowel]), "novform") else -- in case of footnote in nonpast_vowel inferred_nonpast_vowel = nonpast_vowel end end end if not is_passive_only(passive) then if rget(past_vowel) == "-" then past_vowel = inferred_past_vowel end if rget(nonpast_vowel) == "-" then nonpast_vowel = inferred_nonpast_vowel end end elseif vform == "IX" and is_waw_ya(rad3) and len == radstart + 2 and ch[len] == AMAQ then -- Final-weak form IX verbs like اِرْعَوَى "to desist, to repent, to see the light". weakness = "final-weak" expected_length = radstart + 2 elseif vform == "X" and rad1 == "ح" and rad2 == Y and rad3 == ALIF then -- Long variant اِسْتَحْيَا. weakness = "final-weak" rad3 = Y variant = "long" elseif rad3 == AMAQ or rad2 == Y and rad3 == ALIF or rad3 == Y then -- rad3 == Y happens in passive-only verbs. if vform_supports_final_weak(vform) then weakness = "final-weak" else infer_err("Last radical is " .. rad3 .. " but verb form doesn't support final-weak verbs") end -- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws); it could be wāw or yāʾ, but -- doesn't affect the conjugation. rad3 = (rad1 == W or rad2 == W) and {Y, W, ambig = true} or {W, Y, ambig = true} elseif rad2 == ALIF then if vform_supports_hollow(vform) then weakness = "hollow" local function set_past_to_a() if req(past_vowel, A) then -- already set elseif req(past_vowel, "-") or req(past_vowel, rget(nonpast_vowel)) then past_vowel = A else infer_err(("Form I hollow verb with nonpast vowel set to '%s' must have past vowel set to 'a' or the same value, not %s"): format(undia[rget(nonpast_vowel)], undia[rget(past_vowel)]), "novform") end end if vform == "I" and req(nonpast_vowel, U) then rad2 = W set_past_to_a() elseif vform == "I" and req(nonpast_vowel, I) then rad2 = Y set_past_to_a() else if req(nonpast_vowel, A) and not req(past_vowel, I) then infer_err(("Form I hollow verb with nonpast vowel set to 'a' must have past vowel set to 'i', not %s"): format(undia[rget(past_vowel)]), "novform") end -- Ambiguous radical; could be wāw or yāʾ; if verb form I, it's critical to get this right, and -- the caller checks for this situation and throws an error if non-past vowel is "a" and second -- radical isn't explicitly given. rad2 = {W, Y, ambig = true, need_radical = true} end else infer_err("Second radical is alif but verb form doesn't support hollow verbs") end elseif vform == "I" and rad1 == W and not form_I_w_non_assimilated() then weakness = "assimilated" elseif rad2 == rad3 and (vform == "III" or vform == "VI") then weakness = "geminate" variant = "long" else weakness = "sound" end end if expected_length then check_len(expected_length, expected_length) end end rad1 = convert(rad1, 1) rad2 = convert(rad2, 2) rad3 = convert(rad3, 3) rad4 = convert(rad4, 4) if not weakness then error("Internal error: Returned weakness from infer_radicals() is nil") end return { weakness = weakness, rad1 = rad1, rad2 = rad2, rad3 = rad3, rad4 = rad4, past_vowel = past_vowel, nonpast_vowel = nonpast_vowel, form_viii_assim = form_viii_assim, variant = variant, } end -- bot interface to infer_radicals() function export.infer_radicals_json(frame) local iparams = { headword = {}, vform = {}, passive = {}, past_vowel = {}, nonpast_vowel = {}, is_reduced = {type = "boolean"}, } local iargs = require("Module:parameters").process(frame.args, iparams) return require("Module:JSON").toJSON(export.infer_radicals(iargs)) end -- Infer vocalization from participle headword (active or passive), verb form (I, II, etc.) and whether the headword is -- active or passive. Throw an error if headword is malformed. Returned radicals may contain Latin letters "t", "w" or "y" -- indicating ambiguous radicals guessed to be tāʾ, wāw or yāʾ respectively. function export.infer_participle_vocalization(headword, vform, weakness, is_active) local chars = {} local orig_headword = headword -- Sub out alif-madda for easier processing. headword = rsub(headword, AMAD, HAMZA .. ALIF) local len = ulen(headword) -- Extract the headword letters into an array. for i = 1, len do table.insert(chars, usub(headword, i, i)) end local function form_intro_error_msg() return ("For verb form %s %s%s participle %s, "):format(vform, orig_headword ~= headword and "normalized " or "", is_active and "active" or "passive", headword) end local function err(msg) error(form_intro_error_msg() .. msg, 1) end -- Check that length of headword is within [min, max]. local function check_len(min, max) if min and len < min then err(("expected at least %s letters but saw %s"):format(min, len)) elseif max and len > max then err(("expected at most %s letters but saw %s"):format(max, len)) end end -- Get the character at `ind`, making sure it exists. local function c(ind) check_len(ind) return chars[ind] end -- Check that the letter at the given index is the given string, or is one of the members of the given array local function check(index, must) local letter = chars[index] local function make_possible_values() if type(must) == "string" then return must else return list_to_text(must, nil, " or ") end end if not letter then err(("expected a letter (specifically %s) at position %s, but participle is too short"):format( make_possible_values(), index)) end local matches if type(must) == "string" then matches = letter == must else matches = m_table.contains(must, letter) end if not matches then err(("letter %s at index %s must be %s"):format(letter, index, make_possible_values())) end end local function check_weakness(values, allow_missing, invert_condition) local function make_possible_weaknesses() for i, val in ipairs(values) do values[i] = "'" .. val .. "'" end return list_to_text(values, nil, " or ") end if allow_missing and invert_condition then error("Internal error: Can't specify both allow_missing and invert_condition") end if not weakness then if allow_missing or invert_condition then return else err(("weakness is unspecified but must be %s"):format(make_possible_weaknesses())) end else local matches = m_table.contains(values, weakness) if invert_condition and matches then err(("weakness '%s' must not be %s"):format(weakness, make_possible_weaknesses())) elseif not invert_condition and not matches then err(("weakness '%s' must be %s"):format(weakness, make_possible_weaknesses())) end end end local vocalized local function handle_possibly_final_weak(sound_prefix, expected_length) check_len(expected_length, expected_length) if c(expected_length) == AMAQ then -- passive final-weak if is_active then err("participle in -ِى only allowed for passive participles") end check_weakness({"final-weak", "assimilated+final-weak"}, "allow missing") vocalized = sound_prefix .. AN .. AMAQ else -- all others behave as if sound check_weakness({"final-weak", "assimilated+final-weak"}, nil, "invert condition") vocalized = sound_prefix .. (is_active and I or A) .. c(expected_length) end end if not (vform == "I" and is_active) then -- all participles except verb form I active begin in م-. check(1, M) end if vform == "I" then if is_active then check(2, ALIF) local sound_prefix = c(1) .. AA .. c(3) if len == 3 then if c(3) == HAMZA then -- Either hollow with hamzated third radical, e.g. [[شاء]] active participle 'شَاءٍ', or final-weak -- with hamzated second radical, e.g. [[رأى]] active participle 'رَاءٍ'. Theoretically (?), also -- geminate with hamzated second/third radical, but I don't know if any such verbs exist. if weakness == "geminate" then vocalized = sound_prefix .. SH else check_weakness({"hollow", "final-weak"}, "allow missing") vocalized = sound_prefix .. IN end else check_weakness({"final-weak", "geminate"}) if weakness == "geminate" then vocalized = sound_prefix .. SH else vocalized = sound_prefix .. IN end end else check_len(4, 4) -- we will convert back to alif maqṣūra below as needed vocalized = sound_prefix .. I .. c(4) end else -- assimilated verbs: regular, e.g. مَوْزُون "weighed" -- geminate verbs: regular, e.g. مَبْلُول "moistened" -- third-hamzated verbs: مَبْرُوء -- hollow verbs: مَقُود "led, driven"; مَزِيد "added, increased" -- hollow first-hamzated verbs: مَئِيض "returned, reverted"; مَأْيُوس "despaired" (NOTE: formation is sound); -- مَأُود or مَؤُود "bent; depleted" -- hollow third-hamzated verbs: مَشِيء "willed, intended", مَضُوء "glittered?" -- final-weak: مَلْقِيّ "found, encountered"; مَصْغُوّ "inclined" -- hollow + final-weak: مَشْوِيّ "fried, grilled", مَهْوِيّ "loved" -- first-hamzated + hollow + final-weak: مَأْوِيّ "received hospitably" local sound_prefix = MA .. c(2) .. SK .. c(3) if len == 5 then -- sound, assimilated or geminate check(4, W) vocalized = sound_prefix .. UU .. c(5) else check_len(4, 4) if c(4) == W then -- final-weak third-wāw vocalized = sound_prefix .. U .. W .. SH elseif c(4) == Y then -- final-weak third-yāʾ vocalized = sound_prefix .. I .. Y .. SH else -- hollow check(3, {W, Y}) if c(3) == W then vocalized = MA .. c(2) .. UU .. c(4) else vocalized = MA .. c(2) .. II .. c(4) end end end end elseif vform == "II" or vform == "V" or vform == "XII" or vform == "XIII" or vform == "Iq" or vform == "IIq" or vform == "IIIq" then local sound_prefix, expected_length if vform == "II" then sound_prefix = MU .. c(2) .. A .. c(3) .. SH expected_length = 4 elseif vform == "V" then check(2, T) sound_prefix = MU .. T .. A .. c(3) .. A .. c(4) .. SH expected_length = 5 elseif vform == "XII" then -- e.g. [[احدودب]] "to be or become convex or humpbacked", مُحْدَوْدِب (active); -- [[اثنونى]] "to be bent; to be doubled up", مُثْنَوْنٍ (active) check(4, W) if c(3) ~= c(5) then err(("third letter %s should be the same as the fifth letter %s"):format(c(3), c(5))) end sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. W .. SK .. c(5) expected_length = 6 elseif vform == "XIII" then -- e.g. [[اخروط]] "to get entangled; to extend", مُخْرَوِّط (active), مُخْرَوَّط (passive) check(4, W) sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. W .. SH expected_length = 5 elseif vform == "Iq" then sound_prefix = MU .. c(2) .. A .. c(3) .. SK .. c(4) expected_length = 5 elseif vform == "IIq" then check(2, T) sound_prefix = MU .. T .. A .. c(3) .. A .. c(4) .. SK .. c(5) expected_length = 6 elseif vform == "IIIq" then -- e.g. [[اخرنطم]] "to be proud and angry" check(4, T) sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. N .. SK .. c(5) expected_length = 6 else error("Internal error: Unhandled verb form " .. vform) end if len == expected_length - 1 then -- active final-weak if not is_active then err(("length-%s participle only allowed for active participles"):format(len)) end check_weakness({"final-weak", "assimilated+final-weak"}, "allow missing") vocalized = sound_prefix .. IN else handle_possibly_final_weak(sound_prefix, expected_length) end elseif vform == "III" or vform == "VI" then local sound_prefix, expected_length if vform == "VI" then check(2, T) check(4, ALIF) sound_prefix = MU .. T .. A .. c(3) .. AA .. c(5) expected_length = 6 else sound_prefix = MU .. c(2) .. AA .. c(4) expected_length = 5 end if len == expected_length - 1 then -- active final-weak or active or passive geminate if is_active then check_weakness({"geminate", "final-weak", "assimilated+final-weak"}) if weakness == "geminate" then vocalized = sound_prefix .. SH else vocalized = sound_prefix .. IN end else check_weakness({"geminate"}, "allow missing") vocalized = sound_prefix .. SH end else handle_possibly_final_weak(sound_prefix, expected_length) end elseif vform == "IV" or vform == "X" then -- form IV: -- sound: مُرْسِخ (active, "entrenching"), مُرْسَخ (passive, "entrenched") -- first-hamzated (like sound): مُؤْيِس (active, "causing to despair"), مُؤْيَس (passive, "caused to despair") -- final-weak: مُكْرٍ (active, "renting out"), مُكْرًى (passive, "rented out") -- assimilated: مُورِد (active, "transferring"), مُورَد (passive, "transferred"); same when first-Y, e.g. -- أَيْقَنَ "to be certain of": مُوقِن (active), مُوقَن (passive) -- assimilated + final-weak: مُورٍ (active, "setting fire, kindling"), مُورًى (passive, "set fire, kindled") -- geminate: مُمِدّ (active, "granting, helping"), مُمَدّ (passive, "granted, helped") -- hollow: مُزِيل (active, "eliminating"), مُزَال (passive, "eliminated") -- hollow + final-weak: مُعْيٍ (active, "tiring"), مُعْيًى (passive, "tired") local sound_prefix, expected_length if vform == "X" then check(2, S) check(3, T) sound_prefix = MU .. S .. SK .. T .. A .. c(4) expected_length = 6 else sound_prefix = MU .. c(2) expected_length = 4 end if len == expected_length and c(len - 1) == Y and c(len) ~= AMAQ then -- active hollow if not is_active then err("this shape only allowed for active participles") end check_weakness({"hollow"}, "allow missing") vocalized = sound_prefix .. II .. c(len) elseif len == expected_length and c(len - 1) == ALIF then -- passive hollow if is_active then err("this shape only allowed for passive participles") end check_weakness({"hollow"}, "allow missing") vocalized = sound_prefix .. AA .. c(len) elseif len == expected_length - 1 then -- active final-weak or active or passive geminate if is_active then check_weakness({"geminate", "final-weak", "assimilated+final-weak"}) if weakness == "geminate" then vocalized = sound_prefix .. I .. c(len) .. SH elseif vform == "IV" and c(2) == W then -- assimilated final-weak vocalized = sound_prefix .. c(len) .. IN else vocalized = sound_prefix .. SK .. c(len) .. IN end else check_weakness({"geminate"}, "allow missing") vocalized = sound_prefix .. A .. c(len) .. SH end else if vform == "IV" and c(2) == W then -- assimilated, possibly final-weak sound_prefix = sound_prefix .. c(expected_length - 1) else sound_prefix = sound_prefix .. SK .. c(expected_length - 1) end handle_possibly_final_weak(sound_prefix, expected_length) end elseif vform == "VII" or vform == "VIII" then -- form VII (passive participles are fairly rare but do exist): -- sound: مُنْكَتِب (active "subscribing"), مُنْكَتَب (passive "subscribed") -- geminate: مُنْضَمّ (both active "joining, containing" and passive "joined, contained") -- final-weak: مُنْطَلٍ (active "fooling (someone)"), مُنْطَلًى (passive "fooled") -- final-weak with medial wāw: مُنْطَوٍ (active "involving"), مُنْطَوًى (passive "involved") -- hollow: مُنْقَاد (both active "complying with" and passive "complied with") -- -- for form VIII, the same variants exist but things are complicated by assimilations involving the template T. -- sound third-hamzated no assimilation: مُبْتَدِئ (active "beginning"), مُبْتَدَأ (passive "begun") -- geminate no assimilation: مُبْتَزّ (both active "robbing" and passive "robbed") -- final-weak no assimilation: مُبْتَنٍ (active "building"), مُبْتَنًى (passive "built") -- final-weak with medial wāw no assimilation: مُحْتَوٍ (active "containing"), مُحْتَوًى (passive "contained") -- hollow no assimilation: مُخْتَار (both active "choosing" and passive "chosen") -- -- sound with total assimilation: مُتَّبِع (active "following"), مُتَّبَع (passive "followed") -- sound with total assimilation, assimilating wāw: مُتَّعِد (active "threatening"), مُتَّعَد (passive "threatened") -- sound with total assimilation, irregularly assimilating hamza: مُتَّخِذ (active "taking"), مُتَّخَذ (passive "taken") -- sound with total assimilation (to ḏāl, producing dāl): مُدَّخِر (active "reserving"), مُدَّخَر (passive "reserved") -- sound with total assimilation (to ḏāl): مُذَّكِر (active "remembering"), مُذَّكَر (passive "remembered") -- sound with total assimilation (to ṭāʔ): مُطَّرِح (active "discarding"), مُطَّرَح (passive "discarded") -- sound with total assimilation (to ẓāʔ): مُظَّلِم (active "tolerating"), مُظَّلَم (passive "tolerated") -- final-weak with total assimilation, assimilating wāw: مُتَّقٍ (active "guarding against"), مُتَّقًى (passive "guarded against") -- final-weak with total assimilation (to ṯāʔ): مُثَّنٍ (active "undulating"), مُثَّنًى (passive "undulated") -- final-weak with total assimilation (to dāl): مُدَّعٍ (active "claiming"), مُدَّعًى (passive "claimed") -- sound with partial assimilation (to zayn): مُزْدَهِر (active "thriving"), مُزْدَهَر (passive "thrived") -- sound with medial wāw with partial assimilation (to zayn): مُزْدَوِج (active "appearing twice") -- sound with partial assimilation (to ṣād): مُصْطَبِح (active "illuminating"), مُصْطَبَح (passive, "illuminated") -- sound with partial assimilation (to ḍād): مُضْطَرِب (active "to be disturbed"; no passive) -- geminate with partial assimilation (to ṣād): مُصْطَبّ (both active "effusing" and passive "effused") -- geminate with partial assimilation (to ḍād): مُضْطَرّ (both active "forcing" and passive "forced") -- final-weak with partial assimilation (to ṣād): مُصْطَلٍ (active "warming"), مُصْطَلًى (passive "warmed") -- hollow with partial assimilation (to zayn): مُزْدَاد (both active "increasing" and passive "increased") -- hollow with partial assimilation (to ṣad): مُصْطَاد (both active "hunting" and passive "hunted") local sound_prefix, sufind if vform == "VII" then check(2, N) sound_prefix = MU .. N .. SK .. c(3) sufind = 4 else local c2 = c(2) if c2 == T or c2 == "د" or c2 == "ث" or c2 == "ذ" or c2 == "ط" or c2 == "ظ" then -- full assimilation sound_prefix = MU .. c2 .. SH sufind = 3 else -- partial or no assimilation if c2 == "ز" then check(3, "د") elseif c2 == "ص" or c2 == "ض" then check(3, "ط") else check(3, T) end sound_prefix = MU .. c2 .. SK .. c(3) sufind = 4 end end if c(sufind) == ALIF then -- hollow, active or passive check_len(sufind + 1, sufind + 1) check_weakness({"hollow"}, "allow missing") vocalized = sound_prefix .. AA .. c(sufind + 1) elseif len == sufind then -- active final-weak or active or passive geminate if is_active then check_weakness({"geminate", "final-weak", "assimilated+final-weak"}) if weakness == "geminate" then vocalized = sound_prefix .. A .. c(len) .. SH else vocalized = sound_prefix .. A .. c(len) .. IN end else check_weakness({"geminate"}, "allow missing") vocalized = sound_prefix .. A .. c(len) .. SH end else sound_prefix = sound_prefix .. A .. c(sufind) handle_possibly_final_weak(sound_prefix, sufind + 1) end elseif vform == "IX" then check_len(4, 4) vocalized = MU .. c(2) .. SK .. c(3) .. A .. c(4) .. SH elseif vform == "IVq" then -- e.g. [[اذلعب]] "to scamper away", مُذْلَعِبّ (active), مُذْلَعَبّ (passive); -- [[اطمأن]] "to remain quietly; to be certain", مُطْمَئِنّ (active), مُطْمَأَنّ (passive) check_len(5, 5) local sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. c(4) if is_active then vocalized = sound_prefix .. I .. c(5) .. SH else vocalized = sound_prefix .. A .. c(5) .. SH end elseif vform == "XI" then check_len(5, 5) check(4, ALIF) vocalized = MU .. c(2) .. SK .. c(3) .. AA .. c(5) .. SH -- e.g. [[احمار]] "to turn red, to blush", مُحْمَارّ (active) elseif vform == "XIV" or vform == "XV" then -- FIXME: Implement. No examples in Wiktionary currently; need to look up in a grammar. error("Support for verb form " .. vform .. " not implemented yet") else error("Don't recognize verb form " .. vform) end vocalized = rsub(vocalized, HAMZA .. AA, AMAD) local reconstructed_headword = lang:makeEntryName(vocalized) if reconstructed_headword ~= orig_headword then error(("Internal error: Vocalized participle %s doesn't match original participle %s"):format( vocalized, orig_headword)) end -- Apply NFC-style normalization to match MediaWiki's automatic normalization return apply_nfc_shadda(vocalized) end function export.infer_participle_vocalization_json(frame) local iparams = { [1] = {required = true}, [2] = {required = true}, ["weakness"] = {}, ["passive"] = {type = "boolean"} } local iargs = require("Module:parameters").process(frame.args, iparams) return export.infer_participle_vocalization(iargs[1], iargs[2], iargs.weakness, not iargs.passive) end return export qbl44lwcwcyv9s8d2hrq4hvz42p7top وحدة:languages/ملعب 828 188246 1097577 1003428 2026-07-26T18:30:26Z ForzaGreen 28665 إفراغ وإدراج للحذف السريع: نسخة أقدم من [[وحدة:languages]]، لم يعد يستعملها شيء حيّ. 1097577 Scribunto text/plain -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش الوحدة:languages/ملعب]]. -- -- نسخة أقدم من [[وحدة:languages]]، لم يعد يستعملها شيء حيّ. -- (2026-07-26) h4jjvos70bkeyiga2jpc61mb3dd670l 1097585 1097577 2026-07-26T18:50:38Z ForzaGreen 28665 إدراج للحذف السريع بقالب شطب 1097585 Scribunto text/plain -- {{شطب|نسخة أقدم من [[وحدة:languages]] لم تختلف عنها إلا بسطر إعادة توجيه، ولم يعد يستعملها شيء حيّ.}} -- -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع (2026-07-26). -- نسخة أقدم من [[وحدة:languages]] لم تختلف عنها إلا بسطر إعادة توجيه، ولم يعد يستعملها شيء حيّ. -- -- ملاحظة: نموذج محتوى صفحات الوحدات هو Scribunto، فلا يُصيَّر قالب {{شطب}} -- هنا ولا تدخل الصفحة في تصنيف الحذف السريع؛ القالب الفعّال في [[نقاش الوحدة:languages/ملعب]]. 99kqxydiwemxje6c6r9455bjct1ck0n ويكاموس:GUS2Wiki 4 216488 1097549 1096840 2026-07-26T12:56:13Z Alexis Jazz 28437 Updating gadget usage statistics from [[Special:GadgetUsage]] ([[phab:T121049]]) 1097549 wikitext text/x-wiki {{#ifexist:Project:GUS2Wiki/top|{{/top}}|This page provides a historical record of [[Special:GadgetUsage]] through its page history. To get the data in CSV format, see wikitext. To customize this message or add categories, create [[/top]].}} البيانات التالية مخزنة، وكان آخر تحديث لها في 2026-07-25T05:09:48Z. العدد الأقصى للنتائج المخزنة هو {{PLURAL:5000||نتيجة واحدة|نتيجتان|5000 نتائج|5000 نتيجة}}. {| class="sortable wikitable" ! الإضافة !! data-sort-type="number" | عدد المستخدمين !! data-sort-type="number" | مستخدمين نشطين |- |AjaxEdit || 30 || 1 |- |BiDiEditing || 72 || 2 |- |Cat-a-lot || 10 || 0 |- |HotCat || 87 || 5 |- |JSL || 14 || 1 |- |Linkscount || 18 || 1 |- |MobileCategories || 14 || 1 |- |Numeral converter || 1 || 0 |- |Numeral-converter-toggle || 5 || 1 |- |ShortLink || 60 || 1 |- |UTCLiveClock || 84 || 0 |- |VE-wditem-input-loader || 11 || 1 |- |XTools-ArticleInfo || 2 || 0 |- |auto-cite-wikidata-loader || data-sort-value="Infinity" | افتراضي || data-sort-value="Infinity" | افتراضي |- |autocomplete || 27 || 0 |- |mobile-sidebar || 2 || 0 |- |newsectionbottom || 11 || 0 |- |searchlang || 22 || 0 |- |speed-deletion || 6 || 2 |- |userinfo-without-deletedcontribs || 22 || 2 |- |wikEd || 44 || 2 |} * [[خاص:GadgetUsage]] * [[m:Meta:GUS2Wiki/Script|GUS2Wiki]] <!-- data in CSV format: AjaxEdit,30,1 BiDiEditing,72,2 Cat-a-lot,10,0 HotCat,87,5 JSL,14,1 Linkscount,18,1 MobileCategories,14,1 Numeral converter,1,0 Numeral-converter-toggle,5,1 ShortLink,60,1 UTCLiveClock,84,0 VE-wditem-input-loader,11,1 XTools-ArticleInfo,2,0 auto-cite-wikidata-loader,default,default autocomplete,27,0 mobile-sidebar,2,0 newsectionbottom,11,0 searchlang,22,0 speed-deletion,6,2 userinfo-without-deletedcontribs,22,2 wikEd,44,2 --> 7upgh8kfpz37v0q3sl3hpomx89pfwop وحدة:ar-verb/ملعب 828 230858 1097571 1003493 2026-07-26T18:29:26Z ForzaGreen 28665 إفراغ وإدراج للحذف السريع: كانت نسخة تجريبية من [[وحدة:ar-verb]]، وقد تجاوزتها النسخة الحالية. 1097571 Scribunto text/plain -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش الوحدة:ar-verb/ملعب]]. -- -- كانت نسخة تجريبية من [[وحدة:ar-verb]]، وقد تجاوزتها النسخة الحالية. -- (2026-07-26) tse08jgeieivsvvauwev2l3c5nf2srj 1097582 1097571 2026-07-26T18:50:08Z ForzaGreen 28665 إدراج للحذف السريع بقالب شطب 1097582 Scribunto text/plain -- {{شطب|نسخة ملعب قديمة من [[وحدة:ar-verb]] تجاوزتها النسخة الحالية، ولا تُستعمل في أي صفحة.}} -- -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع (2026-07-26). -- نسخة ملعب قديمة من [[وحدة:ar-verb]] تجاوزتها النسخة الحالية، ولا تُستعمل في أي صفحة. -- -- ملاحظة: نموذج محتوى صفحات الوحدات هو Scribunto، فلا يُصيَّر قالب {{شطب}} -- هنا ولا تدخل الصفحة في تصنيف الحذف السريع؛ القالب الفعّال في [[نقاش الوحدة:ar-verb/ملعب]]. orqpnrj13rbdr5pqsrjiamj7306j57q وحدة:inflection utilities 828 230859 1097560 1005326 2026-07-26T16:11:37Z ForzaGreen 28665 استعمال وحدة:links بدل نسخة الملعب links/ملعب2 1097560 Scribunto text/plain local export = {} local m_links = require("Module:links") --W local m_str_utils = require("Module:string utilities") local m_table = require("Module:table") local put = require("Module:parse utilities") local headword_data_module = "Module:headword/data" local script_utilities_module = "Module:script utilities" local table_tools_module = "Module:table tools" local is_callable = require("Module:fun").is_callable local split = m_str_utils.split local rfind = mw.ustring.find local rmatch = mw.ustring.match local rsubn = mw.ustring.gsub local ucfirst = m_str_utils.ucfirst local unpack = unpack or table.unpack -- Lua 5.2 compatibility local dump = mw.dumpObject -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end local function track(page) require("Module:debug/track")("inflection utilities/" .. page) return true end local footnote_abbrevs = { ["a"] = "archaic", ["c"] = "colloquial", ["d"] = "dialectal", ["fp"] = "folk-poetic", ["l"] = "literary", ["lc"] = "low colloquial", ["p"] = "poetic", ["pej"] = "pejorative", ["r"] = "rare", } --[==[ intro: The following code is used in building up the inflection of terms in inflected languages, where a term can potentially consist of several inflected words, each surrounded by fixed text, and a given slot (e.g. accusative singular) of a given word can potentially consist of multiple possible inflected forms. In addition, each form may be associated with a manual transliteration and/or a list of footnotes (or qualifiers, in the case of headword lines). The following terminology is helpful to understand: * A '''term''' is a word or multiword expression that can be inflected. A multiword term may in turn consist of several single-word inflected terms with surrounding fixed text. A term belongs to a particular '''part of speech''' (e.g. noun, verb, adjective, etc.). * An '''inflection dimension''' is a particular dimension over which a term may be inflected, such as case, number, gender, person, tense, mood, voice, aspect, etc. * The '''lemma''' is the particular form of a term under which the term is entered into a dictionary. For example, for verbs, it is most commonly the infinitive, but this differs for some languages: e.g. Latin, Greek and Bulgarian use the first-person singular present indicative (active voice in the case of Latin and Greek); Sanskrit and Macedonian use the third-person singular present indicative (active voice in the case of Sanskrit); Hebrew and Arabic use the third-person singular masculine past (aka "perfect"); etc. For nouns, the lemma form is most commonly the nominative singular, but e.g. for Old French it is the objective singular and for Sanskrit it is the root. * A '''slot''' is a particular combination of inflection dimensions. An example might be "accusative plural" for a noun, or "first-person singular present indicative" for a verb. Slots are named in a language-specific fashion. For example, the slot "accusative plural" might have a name `accpl`, while "first-person singular present indicative" might be variously named `pres1s`, `pres_ind_1_sg`, etc. Each slot is filled with zero or more '''forms'''. * A '''form''' is a particular inflection of a slot for a particular term. Note that a given slot may (and often does) have more than one associated form; these different forms are termed '''variants'''. An example is {{m+|de|Bug||bow (of a ship)}}, which has two genitive singular forms ''Buges'' and ''Bugs''; two plural forms in all cases, e.g. nominative plural ''Buge'' and ''Büge''; and two dative singular forms ''Bug'' and rare/archaic ''Buge''. The form variants for a given slot are ordered, and generally should have the more common and/or preferred variants first, along with rare, archaic or obsolete variants last (if they are included at all). * Forms are described using '''form objects''', which are Lua objects taking the form `{form="``form_value``", translit="``manual_translit``", footnotes={"``footnote``", "``footnote``", ...}}`. (Additional '''metadata''' may be present in a form object, although the support for preserving such metadata when transformations are applied to form objects isn't yet complete.) ``form_value`` is a '''form value''' specifying the value of the form itself in the term's script. ``manual_translit`` specifies optional manual transliteration for the form, in case (a) the form value is in a different script; and (b) either the form's automatic transliteration is incorrect and needs to be overridden, or the language of the term has no automatic transliteration (e.g. in the case of Persian and Hebrew). ``footnote`` is a footnote to be attached to the form in question, and should be e.g. {"[archaic]"} or {"[only in the meaning 'to succeed (an officeholder)']"}, i.e. the string must be surrounded by brackets and should begin with a lowercase letter and not end in a period/full stop. When such footnotes are converted to actual footnotes in a table of inflected forms, the brackets will be removed, the first letter will be capitalized and a period/full stop will be added to the end. (However, when such footnotes are used as qualifiers in headword lines, only the brackets will be removed, with no capitalization or final period.) Note that only ``form_value`` is mandatory. * A list of zero or more form objects is termed a '''form object list''', or usually just a '''form list'''. Such lists are ordered and go into form tables (see below). * A '''form table''' is a Lua table (i.e. a dictionary) describing all the possible inflections of a given term. The keys in such a table are slots (strings) and the values are form lists. '''NOTE:''' All inflection code assumes and maintains the invariant that no two slots, and no two forms in a single slot, share the same form object (by reference, i.e. the Lua object describing a form object should never be shared in two places). This allows for safely side-effecting form objects in certain sorts of operations. This same invariant necessarily applies to the Lua list objects containing the form objects, but does '''NOT''' apply to metadata inside of form objects. In particular, a list of footnotes may well be shared among different form objects. This means it is '''NOT''' safe to side-effect such lists, and in fact no code in this module that manipulates footnote lists will ever side-effect such lists; they are treated as immutable. * Some functions, to save memory, accept and work with abbreviated forms of form objects and/or form lists. Specifically, an '''abbreviated form object''' is either a form object or a string, the latter corresponding to a form object whose form value is the string and all other properties are nil. Similarly, an '''abbreviated form list''' is either a single abbreviated form object or a list of such objects, i.e. any of a string, form object or list of strings and/or form objects. Functions that do not accept such abbreviated structures may be said to insist on being passed form objects in '''general form''', or form lists in '''general list form'''. * Each slot is associated with an '''accelerator tag set''', which is a list of inflection tags that are used when generating an accelerator entry for the forms in the slot (see [[WT:ACCEL]]). For example, the first singular present indicative of a verb might have slot name `pres_1sg` and corresponding accelerator tag set `1|s|pres|ind`. As shown, the accelerator tag set is a string consisting of inflection tags (as used in {{tl|inflection of}}) separated by `|`. Despite the terminology ''tag set'', the tags in a tag set are ordered, although the same tag should never occur twice. * Some inflected terms are '''multiword''', i.e. they consist of multiple '''words''', where each word is generally separated by spaces or sometimes hyphens. In such a term, some of the words inflect, while others remain fixed. Words that inflect are termed '''inflecting words''' (or more correctly '''inflecting parts''', since in some circumstances, parts of a word can inflect). The '''fixed text''' is all the parts of a multiword term that do not inflect. * The descriptor that describes how a given term inflects is called an '''inflection spec''', and consists of the lemma form of the term itself, annotated with an '''angle bracket spec''' after each inflecting word. As the name implies, an angle bracket spec is surrounded by angle brackets (`<...>`). A simple example is {{m+|de|Feder||feather}}, whose inflection spec looks like `Feder<f>`, where `f` specifies the feminine gender. In this case, although there are several properties that could be specified between angle brackets, all except the gender are optional and have been left out, indicating that defaults should be used. Another example is {{m+|de|Baske|Basque person}}, whose inflection spec looks like `Baske<m.weak>`, where `m` specifies the masculine gender and `weak` specifies the weak inflection. Note that individual components of an angle bracket spec like `m` and `weak` are termed '''indicators''' and are separated by periods/full stops. A slightly more complex example is {{m+|de|Zeitgeist||zeitgeist}}, whose inflection spec looks like `Zeitgeist<m,es:s,er>` and which specifies three things in a single '''compound indicator''': `m` (the masculine gender); `es:s` (the genitive singular, which can end in either ''-es'' or ''-s''); and `er` (the nomininative plural, which ends in ''-er''). * If there are several inflecting words in a term, each one will be followed by its own angle bracket spec. An example is {{m+|de|schwarzes Loch||black hole}}, whose inflection spec looks like `schwarzes<+> Loch<n,es:s,^er>`. Here, the adjective ''schwarzes'' (the nominative neuter singular of {{m|de|schwarz||black}}) is followed by the angle bracket spec `<+>` specifying that it inflects as an adjective, and the noun ''Loch'' has the angle bracket spec `<n,es:s,^er>`, indicating (similarly to the above example) that it is neuter, has a genitive singular in either ''-es'' or ''-s'', and has a nominative plural in ''-er'' with umlaut, hence ''Löcher'' (the `^` specifies that the form requires umlaut). * Sometimes a given term has multiple ways of inflecting that differ in ways that can't be specified using a single angle bracket spec. This is supported using '''alternants''', which are specified using double parentheses. (This is so that terms that themselves contain parentheses can be specified without interference.) An example is {{m+|uk|русин||Rusyn}}, which can be stressed either as ''ру́син'' (stress on the first syllable and following accent paradigm ''a'', hence genitive singular ''ру́сина'') or ''руси́н'' (stress on the second syllable and following accent paradigm ''b'', hence genitive singular ''русина́''; note how the stress moves onto the ending, in accordance with the accent paradigm). This is specified using `((ру́син<pr>,руси́н<b.pr>))`, i.e. each separate the alternants with a comma and surround them with double parentheses. (Here, `pr` means that the terms belong to the personal animacy class, and `b` specifies the accent paradigm; paradigm ''a'' is the default and hence is omitted.) * Note that occasionally, parts of a single space-delimited word can inflect separately. An example is {{m+|la|rōsmarīnus||rosemary}}, which is a compound of {{m+|la|rōs||dew}} and {{m|la|marīnus||marine, of the sea}}. In this compound, both parts of the compound can inflect separately; hence genitive singular ''rōrismarīnī'', accusative singular ''rōremmarīnum'', etc. Alternatively, only the second part inflects; hence genitive singular ''rōsmarīnī'', accusative singular ''rōsmarīnum'', etc. This is specified as `((rōs/rōr<3.M>marīnus<2>,rōsmarīnus<2>))`. Here, the term {{m|la|rōs}} by itself would have inflection spec `rōs/rōr<3.M>` (indicating that it is third declension masculine with a non-nominative-singular stem ''rōr-'') and the term {{m|la|marīnus}} would have inflection spec `<2>` (indicating that it is second declension; the masculine gender is inferred from the ''-us'' ending). When combined in a single inflection spec, the doubly-inflecting alternant is written `rōs/rōr<3.M>marīnus<2>`, with each inflecting part followed by its corresponding angle bracket spec, and the singly-inflecting alternant is written `rōsmarīnus<2>`. As this example shows, the two alternants need not correspond in how many inflecting parts there are. It should also be noted that fixed text can surround an alternant and it is even possible to supply multiple alternants in a single inflection spec (e.g. if the term has two words in it and each word requires an alternant to inflect). * The result of parsing a single angle bracket spec is stored into a '''word spec'''. The structure of a word spec is fairly arbitrary and is determined by the user-written `parse_indicator_spec` function, but always contains a form table under the `forms` key that is populated during inflection (see below). A parameter or local variable that holds a word spec is conventionally named `base` for historical reasons. Word specs are grouped together into a structure termed a '''multiword spec''', which describes one or more word specs along with the fixed text in between and around the inflected words. Multiword specs are in turn grouped into structures termed '''alternant specs''', indicating the distinct alternants and the words in each alternant. Finally, multiword specs and alternant specs are grouped into an '''alternant multiword spec''', which is the top-level object describing an inflection spec. Each of these different specs has a form table in it stored in the `forms` key that is populated during the inflection process and contains the form objects that specify the inflections of this part of the full multiword term. (It should be noted that the term '''spec''' is overloaded to mean two different things: the user-specified descriptor that specifies the lemma form of the term and associated inflection, and the associated internal Lua object that encapsulates all information derived from the descriptor, along with later-generated information on how to inflect the term(s) being described.) * Among these various "spec" structures, the two most important are the top-level alternant multiword spec and the bottom-level word spec or "base". You will rarely find it necessary to manipulate the intermediate structures or concern yourself with the details of their formation. * The term ''form'' is unfortunatately overloaded in various modules to mean several things. In particular, for historical reasons, the form value inside of a form object is stored using the key `form`; the form table inside of an alternant multiword spec, a word spec (or "base") and the intermediate structures is stored using the key `forms`; and the accelerator tag set is internally referred to in [[WT:ACCEL]] as a "form". To avoid confusion, the following conventions are followed in code in this module, and should be followed for code in invoking modules as well: *# Functions that accept form objects often name the relevant parameter `form` (if a single form object is required) or `forms` (if a list of form objects, aka form list, is required). *# Functions that accept abbreviated form objects should (but don't always) indicate this by naming the parameter `abform` (for a single abbreviated form object) or `abforms` (for an abbreviated form list). *# Functions that accept a form value (the native-script string portion of a form object, stored for historical reasons in the `.form` property) should '''not''' call such a parameter `form`, but instead use something that makes clear that a form value is required, such as `formval` or sometimes just `val`. *# Similarly, functions that accept a form table should '''not''' call such a parameter `forms` (although for historical reasons the form table in an alternant multiword spec is stored in the field `forms`). Instead, use `formtable` or `formtab`, or similar name that makes clear that the value is a form table (i.e. a map from slot to form list). ====Footnote handling==== Each form can have one or more attached footnotes. The form of a footnote as specified by the user and stored in form values is e.g. {"[archaic]"} or {"[only in the meaning 'to succeed (an officeholder)']"}, i.e. the string must be surrounded by brackets and should begin with a lowercase letter and not end in a period/full stop. When such footnotes are converted to actual footnotes in a table of inflected forms, the brackets will be removed, the first letter will be capitalized and a period/full stop will be added to the end. (However, when such footnotes are used as qualifiers in headword lines, only the brackets will be removed, with no capitalization or final period.) When merging two forms into one, such as when concatenating the form objects of two inflected words in a multiword term or deduplicating form objects sharing the same form value during `show_forms()`, the footnotes are generally combined as well. This means that if one form object has footnotes and the other doesn't, the resulting form object inherits the footnotes of the object that has them, and if both form objects have footnotes, the resulting form object gets all footnotes from both source form objects, with duplicates removed. However, when inserting a form into a form table slot that already has a form whose form value and translit are identical to the new form, the behavior is different. In under normal circumstances the footnotes of the new form are ''not'' incorporated into those of the existing form (if any), but are simply dropped. To understand why this makes sense, consider a term that has two possible forms of its lemma (e.g. two forms differing in stress or in vowel length), where the second form is archaic, rare, colloquial or the like, and has an attached footnote indicating this. An example of this is {{m+|ru|кожух||sheepskin coat; bullet shell}}, where the form ''кожу́х'' with accent pattern ''b'' is more common overall but the form ''ко́жух'' with accent pattern ''c(1)'' is more common among professionals. On first glance, this could be indicated using `((кожу́х&lt;b>,ко́жух<c(1).[professional usage only]>))`. But some forms of these two declensions are the same (in particular, the genitive, dative, instrumental and prepositional plural). If for these slots, the footnotes of the duplicate forms were combined (i.e. the footnotes of the second declension pattern were added to the already-existing form taken from the first declension pattern), these forms would wrongly be labeled as ''professional usage only''. For this reason, it makes more sense to drop the footnotes of the second form when deduplicating. The same sort of behavior makes sense when a single lemma can have two different declensions, the second of which requires a footnote and where some forms in the two declensions are shared. An example of this is {{m+|uk|окови́та||strong, high-quality liquor}}, which can be inflected adjectivally or (rarely) nominally. This would be indicated as `((окови́та<sg.+>,окови́та<sg.[rare]>))` where the `+` indicates adjectival declension and the `sg` indicates that this term only exists in the singular. Here, the two declensions differ in the genitive, dative/locative and vocative (respectively, adjectival ''окови́тої'', ''окови́тій'', ''окови́та'' vs. nominal ''окови́ти'', ''окови́ті'', ''окови́то'') but are the same in the accusative (''окови́ту'') and instrumental (''окови́тою''). Again, dropping the footnotes of the second form when deduplicating is correct and including them would be wrong. This behavior can be changed by attaching a '''footnote modifier''' to the footnote associated the second form. A footnote modifier is a symbol attached to the beginning of a footnote, directly following the opening bracket. The following modifiers are currently recognized: * `!` or `+`: If placed on a footnote of the second form, combine that footnote with those of the first form (if any) rather than dropping it. * `*`: If placed on a footnote of the first form, drop that footnote when merging a second form with any footnotes. An example where the `*` modifier makes sense is a modification of the above example with {{m+|ru|кожух}}. If we notated it as `((кожу́х<b.[more common among laymen]>,ко́жух<c(1).[more common among professionals]>))`, the shared forms would wrongly have the footnote ''more common among laymen'' when in fact they are the only possible forms. If instead we used `((кожу́х<b.[*more common among laymen]>,ко́жух<c(1).[more common among professionals]>))`, the shared forms would correctly have no footnote. Finally, be aware of '''old-style footnote symbols'''. For compatibility reasons, some inflection implementations support a system whereby footnote symbols (consisting of numbers; certain ASCII symbols such as `*`, `~`, `@`, `#`, `+`, etc.; and a large number of Unicode symbols) are directly attached to form values and the footnotes themselves specified manually using the `footnotes` property passed to `show_forms()`. This is allowed only when `allow_footnote_symbols` is set and is highly deprecated. All uses of such symbols should be converted to standard footnotes and the support for such symbols removed. ]==] local function extract_footnote_modifiers(footnote) local footnote_mods, footnote_without_mods = rmatch(footnote, "^%[([!*+]?)(.*)%]$") if not footnote_mods then error("Saw footnote '" .. footnote .. "' not surrounded by brackets") end return footnote_mods, footnote_without_mods end --[==[ Insert a form object (see above) into a list of such objects. If the form is already present (i.e. both the form value and translit, if any, match), the footnotes of the existing and new form might be combined (specifically, footnotes in the new form beginning with `!` will be combined). ]==] function export.insert_form_into_list(list, form) -- Don't do anything if the form object or the form inside it is nil. This simplifies -- form insertion in the presence of inflection generating functions that may return nil, -- such as generate_noun_vocative() and generate_noun_count_form(). if not form or not form.form then return end for _, listform in ipairs(list) do if listform.form == form.form and listform.translit == form.translit then -- Form already present; maybe combine footnotes. if form.footnotes then -- Check to see if there are existing footnotes with *; if so, remove them. if listform.footnotes then local any_footnotes_with_asterisk = false for _, footnote in ipairs(listform.footnotes) do local footnote_mods, _ = extract_footnote_modifiers(footnote) if rfind(footnote_mods, "%*") then any_footnotes_with_asterisk = true break end end if any_footnotes_with_asterisk then local filtered_footnotes = {} for _, footnote in ipairs(listform.footnotes) do local footnote_mods, _ = extract_footnote_modifiers(footnote) if not rfind(footnote_mods, "%*") then table.insert(filtered_footnotes, footnote) end end if #filtered_footnotes > 0 then listform.footnotes = filtered_footnotes else listform.footnotes = nil end end end -- The behavior here has changed; track cases where the old behavior might -- be needed by adding ! to the footnote. track("combining-footnotes") local any_footnotes_with_bang = false for _, footnote in ipairs(form.footnotes) do local footnote_mods, _ = extract_footnote_modifiers(footnote) if rfind(footnote_mods, "[!+]") then any_footnotes_with_bang = true break end end if any_footnotes_with_bang then if not listform.footnotes then listform.footnotes = {} else listform.footnotes = m_table.shallowCopy(listform.footnotes) end for _, footnote in ipairs(form.footnotes) do local already_seen = false local footnote_mods, footnote_without_mods = extract_footnote_modifiers(footnote) if rfind(footnote_nods, "[!+]") then for _, existing_footnote in ipairs(listform.footnotes) do local existing_footnote_mods, existing_footnote_without_mods = extract_footnote_modifiers(existing_footnote) if existing_footnote_without_mods == footnote_without_mods then already_seen = true break end end if not already_seen then table.insert(listform.footnotes, footnote) end end end end end return end end -- Form not found. table.insert(list, form) end --[==[ Insert a form object (see above) into the given slot in the given form table. ``form`` can be {nil}, in which case nothing happens. ]==] function export.insert_form(formtable, slot, form) -- Don't do anything if the form object or the form inside it is nil. This simplifies -- form insertion in the presence of inflection generating functions that may return nil, -- such as generate_noun_vocative() and generate_noun_count_form(). if not form or not form.form then return end if not formtable[slot] then formtable[slot] = {} end export.insert_form_into_list(formtable[slot], form) end --[==[ Insert a list of form objects (see above) into the given slot in the given form table. ``forms`` can be {nil}, in which case nothing happens. ]==] function export.insert_forms(formtable, slot, forms) if not forms then return end for _, form in ipairs(forms) do export.insert_form(formtable, slot, form) end end --[==[ Identity mapping function. ]==] function export.identity(formval, translit) return formval, translit end local function form_value_transliterable(formval) return formval ~= "?" and formval ~= "—" end local function call_map_function_str(str, fun) if str == "?" then return "?" end local newformval, newtranslit = fun(str) if newtranslit then return {form=newformval, translit=newtranslit} else return newformval end end -- FIXME: This doesn't correctly handle metadata. local function call_map_function_obj(form, fun) if form.form == "?" then return {form = "?", footnotes = form.footnotes} end local newformval, newtranslit = fun(form.form, form.translit) return {form = newformval, translit = newtranslit, footnotes = form.footnotes} end --[==[ Map a function over the form values in ``forms`` (a list of form objects in "general list form; see above). If an input form value is {"?"}, it is preserved on output and the function is not called. Otherwise, the function is called with two arguments, the original form and manual translit; if manual translit isn't relevant, it's fine to declare the function with only one argument. The return value is either a single value (the new form) or two values (the new form and new manual translit). The footnotes (if any) from the input form objects are preserved on output. Uses `insert_form_into_list()` to insert the resulting form objects into the returned list in case two different forms map to the same thing. FIXME: Expand this to correctly handle metadata, or create a variant that correctly handles metadata. ]==] function export.map_forms(forms, fun) if not forms then return nil end local retval = {} for _, form in ipairs(forms) do export.insert_form_into_list(retval, call_map_function_obj(form, fun)) end return retval end --[==[ Map a list-returning function over the form values in ``forms`` (a list of form objects in "general list form"; see above). If an input form value is {"?"}, it is preserved on output and the function is not called. Otherwise, the function is called with two arguments, the original form and manual translit; if manual translit isn't relevant, it's fine to declare the function with only one argument. The return value of the function can be {nil} or an abbreviated form list (i.e. anything that is convertible into a general list form, such as a single form value, a list of form values, a form object or a list of form objects). For each form object in the return value, the footnotes of that form object (if any) are combined with any footnotes from the input form object, and the result inserted into the returned list using `insert_form_into_list()` in case two different forms map to the same thing. FIXME: Expand this to correctly handle metadata, or create a variant that correctly handles metadata. ]==] function export.flatmap_forms(forms, fun) if not forms then return nil end local retval = {} for _, form in ipairs(forms) do local funret = form.form == "?" and {"?"} or fun(form.form, form.translit) if funret then funret = export.convert_to_general_list_form(funret) for _, fr in ipairs(funret) do local newform = { form = fr.form, translit = fr.translit, footnotes = export.combine_footnotes(form.footnotes, fr.footnotes) } export.insert_form_into_list(retval, newform) end end end return retval end --[==[ Map a function over the form values in ``abforms`` (an abbreviated form list). If the input form value is {"?"}, it is preserved on output and the function is not called. If ``first_only`` is given and ``abforms`` is a list, only map over the first element. Return value is of the same form as ``abforms``, unless ``abforms`` is a string and the function returns both form value and manual translit (in which case the return value is a form object). The function is called with two arguments, the original form value and manual translit; if manual translit isn't relevant, it's fine to declare the function with only one argument. The return value is either a single value (the new form value) or two values (the new form value and new manual translit). The footnotes (if any) from the input form objects are preserved on output. FIXME: This function is used only in [[Module:bg-verb]] and should be moved into that module. ]==] function export.map_form_or_forms(abforms, fun, first_only) if not abforms then return nil elseif type(abforms) == "string" then return call_map_function_str(abforms, fun) elseif abforms.form then return call_map_function_obj(abforms, fun) else local retval = {} for i, abform in ipairs(abforms) do if first_only then return export.map_form_or_forms(abform, fun) end table.insert(retval, export.map_form_or_forms(abform, fun)) end return retval end end --[==[ Combine two sets of footnotes. If either is {nil}, just return the other, and if both are {nil}, return {nil}. ]==] function export.combine_footnotes(notes1, notes2) if not notes1 and not notes2 then return nil end if not notes1 then return notes2 end if not notes2 then return notes1 end local combined = m_table.shallowCopy(notes1) for _, note in ipairs(notes2) do m_table.insertIfNot(combined, note) end return combined end --[==[ Expand a given footnote (as specified by the user, including the surrounding brackets) into the form to be inserted into the final generated table. If ``no_parse_refs`` is not given and the footnote is a reference (of the form {"[ref:...]"}), parse and return the specified reference(s). Two values are returned, `footnote_string` (the expanded footnote, or nil if the second value is present) and `references` (a list of objects of the form `{text = ``text``, name = ``name``, group = ``group``}` if the footnote is a reference and ``no_parse_refs`` is not given, otherwise {nil}). Unless ``return_raw`` is given, the returned footnote string is capitalized and has a final period added. ]==] function export.expand_footnote_or_references(note, return_raw, no_parse_refs) local _, notetext = extract_footnote_modifiers(note) if not no_parse_refs and notetext:find("^ref:") then -- a reference notetext = rsub(notetext, "^ref:", "") local parsed_refs = require("Module:references").parse_references(notetext) for i, ref in ipairs(parsed_refs) do if type(ref) == "string" then parsed_refs[i] = {text = ref} end end return nil, parsed_refs end if footnote_abbrevs[notetext] then notetext = footnote_abbrevs[notetext] track("footnote-whole-abbrev") else local split_notes = split(notetext, "<(.-)>") for i, split_note in ipairs(split_notes) do if i % 2 == 0 then split_notes[i] = footnote_abbrevs[split_note] track("footnote-angle-bracket-abbrev") if not split_notes[i] then -- Don't error for now, because HTML might be in the footnote. -- Instead we should switch the syntax here to e.g. <<a>> to avoid -- conflicting with HTML. split_notes[i] = "<" .. split_note .. ">" track("footnote-unrecognized-angle-bracket-abbrev") --error("Unrecognized footnote abbrev: <" .. split_note .. ">") else track("footnote-recognized-angle-bracket-abbrev") end end end notetext = table.concat(split_notes) end return return_raw and notetext or ucfirst(notetext) .. "." end --[==[ Convert a list of foonotes to qualifiers and references for use in [[Module:headword]] or similar. Returns two values, a list of qualifiers (possibly {nil}) and a list of reference structures (possibly {nil}), following the structure defined in [[Module:references]]). ]==] function export.convert_footnotes_to_qualifiers_and_references(footnotes) if not footnotes then return nil end local quals, refs for _, qualifier in ipairs(footnotes) do local this_footnote, this_refs = export.expand_footnote_or_references(qualifier, "return raw") if this_refs then if not refs then refs = this_refs else for _, ref in ipairs(this_refs) do table.insert(refs, ref) end end else if not quals then quals = {this_footnote} else table.insert(quals, this_footnote) end end end return quals, refs end --[==[ Combine an abbreviated form object (either a string or a table) with additional footnotes, possibly replacing the form value and/or translit in the process. Normally called in one of two ways: (1) `combine_form_and_footnotes(``form_obj``, ``addl_footnotes``, ``new_form``, ``new_translit``)` where ``form_obj`` is an existing abbreviated form object; ``addl_footnotes`` is either {nil}, a single string (a footnote) or a list of footnotes; ``new_formval`` is either {nil} or the new form value to substitute; and ``new_translit`` is either {nil} or the new translit string to substitute. (2) `combine_form_and_footnotes(``form_value``, ``footnotes``)`, where ``form_value`` is a form value (a string) and ``footnotes`` is either {nil}, a single string (a footnote) or a list of footnotes. In either case, a form object is returned, preserving as many properties as possible from any existing form object in ``abform``. Do the minimal amount of work; e.g. if ``abform`` is a form object and ``addl_footnotes``, ``new_formval`` and ``new_translit`` are all {nil}, the same object as passed in is returned. Under no circumstances is the existing form object side-effected. '''FIXME:''' This does not correctly preserve metadata. ]==] function export.combine_form_and_footnotes(abform, addl_footnotes, new_formval, new_translit) if type(addl_footnotes) == "string" then addl_footnotes = {addl_footnotes} end if not addl_footnotes and not new_formval and not new_translit then return abform end if type(abform) == "string" then new_formval = new_formval or abform return {form = new_formval, translit = new_translit, footnotes = addl_footnotes} end abform = m_table.shallowCopy(abform) if new_formval then abform.form = new_formval end if new_translit then abform.translit = new_translit end if addl_footnotes then abform.footnotes = export.combine_footnotes(abform.footnotes, addl_footnotes) end return abform end --[==[ Convert an abbreviated form list (either a string, form object, or list of either) into general list form. If ``footnotes`` is supplied, then for each form in the form list, combine the form's footnotes with ``footnotes``. This function does not side-effect any of the objects passed into ``abforms``, but will return ``abforms`` unchanged if already in general list form and ``footnotes`` is {nil}. '''FIXME:''' This does not correctly preserve metadata. ]==] function export.convert_to_general_list_form(abforms, footnotes) if type(footnotes) == "string" then footnotes = {footnotes} end if type(abforms) == "string" then return {{form = abforms, footnotes = footnotes}} elseif abforms.form then return {export.combine_form_and_footnotes(abforms, footnotes)} elseif not footnotes then -- Check if already in general list form and return directly if so. local must_convert = false for _, form in ipairs(abforms) do if type(form) == "string" then must_convert = true break end end if not must_convert then return abforms end end local retval = {} for _, form in ipairs(abforms) do if type(form) == "string" then table.insert(retval, {form = form, footnotes = footnotes}) else table.insert(retval, export.combine_form_and_footnotes(form, footnotes)) end end return retval end local function is_table_of_strings(forms) for k, v in pairs(forms) do if type(k) ~= "number" or type(v) ~= "string" then return false end end return true end local function lang_or_func_transliterate(func, lang, text) local retval if func then retval = func(text) else retval = (lang:transliterate(text)) end -- FIXME! Hack to work around bug in ...:transliterate(). Remove me as soon as this bug is fixed. if not retval and (text == " " or text == "-" or text == "?") then retval = text end if not retval then error(("Unable to transliterate text '%s'"):format(text)) end return retval end --[==[ Combine ``stems`` and ``endings`` and store into slot ``slot`` of form table ``formtable``. Either of ``stems`` and ``endings`` can be {nil} or an abbreviated form list. The combination of a given stem and ending happens using ``combine_stem_ending``, which takes two parameters (stem and ending, each a string) and returns one value (a string). If manual transliteration is present in either ``stems`` or ``endings``, ``lang`` (a language object or a function of one argument to transliterate a string) along with ``combine_stem_ending_tr`` (a function for combining manual transliterations that works much like ``combine_stem_ending``) must be given. ``footnotes``, if specified, is a list of additional footnotes to attach to the resulting inflections (stem+ending combinations). The resulting inflections are inserted into the form table using `insert_form()`, in case of duplication. ]==] function export.add_forms(formtable, slot, stems, endings, combine_stem_ending, lang, combine_stem_ending_tr, footnotes) if stems == nil or endings == nil then return end local function combine(stem, ending) if stem == "?" or ending == "?" then return "?" end return combine_stem_ending(stem, ending) end local function transliterate(text) return lang_or_func_transliterate(is_callable(lang) and lang or nil, lang, text) end if type(stems) == "string" and type(endings) == "string" then export.insert_form(formtable, slot, {form = combine(stems, endings), footnotes = footnotes}) elseif type(stems) == "string" and is_table_of_strings(endings) then for _, ending in ipairs(endings) do export.insert_form(formtable, slot, {form = combine(stems, ending), footnotes = footnotes}) end else stems = export.convert_to_general_list_form(stems) endings = export.convert_to_general_list_form(endings, footnotes) for _, stem in ipairs(stems) do for _, ending in ipairs(endings) do local footnotes = nil if stem.footnotes and ending.footnotes then footnotes = m_table.shallowCopy(stem.footnotes) for _, footnote in ipairs(ending.footnotes) do m_table.insertIfNot(footnotes, footnote) end elseif stem.footnotes then footnotes = stem.footnotes elseif ending.footnotes then footnotes = ending.footnotes end local new_form = combine(stem.form, ending.form) local new_translit if new_form ~= "?" and (stem.translit or ending.translit) then if not lang or not combine_stem_ending_tr then error("Internal error: With manual translit, 'lang' and 'combine_stem_ending_tr' must be passed to 'add_forms'") end local stem_tr = stem.translit or transliterate(m_links.remove_links(stem.form)) local ending_tr = ending.translit or transliterate(m_links.remove_links(ending.form)) new_translit = combine_stem_ending_tr(stem_tr, ending_tr) end export.insert_form(formtable, slot, {form = new_form, translit = new_translit, footnotes = footnotes}) end end end end --[==[ Combine any number of form components and store into slot ``slot`` of form table ``formtable``. ``components`` is a list of abbreviated form lists which should be concatenated similarly to how `add_forms()` does it, and stored in ``slot`` along with any footnotes in ``footnotes``. More specifically: # If there are no components, nothing happens. # If there is one component, it is converted to general list form and `insert_forms()` called. # If there are two components, they are treated as stems and endings respectively and `add_forms()` is called. # If there are three or more components, they are concatenated left-to-right in the manner of a `reduce()` operation: the first two components are combined using `add_forms()` and stored into a temporary table, then the next component is combined with the result of the previous operation, etc. In the last combination, footnotes in `footnotes` are combined in, and the result stored into `formtable`. This should generally be used when you are likely to have three or more components, as in [[Module:ar-verb]] (prefixes, stems and endings) and [[Module:de-verb]] (which in some situations has five components combined together). ``combine_stem_ending``, ``lang``, ``combine_stem_ending_tr`` and ``footnotes`` are as in `add_forms()`. ]==] function export.add_multiple_forms(formtable, slot, components, combine_stem_ending, lang, combine_stem_ending_tr, footnotes) if #components == 0 then return elseif #components == 1 then local forms = export.convert_to_general_list_form(components[1], footnotes) export.insert_forms(formtable, slot, forms) elseif #components == 2 then local stems = components[1] local endings = components[2] export.add_forms(formtable, slot, stems, endings, combine_stem_ending, lang, combine_stem_ending_tr, footnotes) else local prev = components[1] for i=2, #components do local temptable = {} export.add_forms(temptable, slot, prev, components[i], combine_stem_ending, lang, combine_stem_ending_tr, i == #components and footnotes or nil) prev = temptable[slot] end export.insert_forms(formtable, slot, prev) end end local function iterate_slot_list_or_table(props, do_slot) if props.slot_list then for _, slot_and_accel_tag_set in ipairs(props.slot_list) do local slot, accel_tag_set = unpack(slot_and_accel_tag_set) do_slot(slot, accel_tag_set) end else for slot, accel_tag_set in pairs(props.slot_table) do do_slot(slot, accel_tag_set) end end end function export.default_split_bracketed_runs_into_words(bracketed_runs, data) -- If the text begins with a hyphen, include the hyphen in the set of allowed characters -- for an inflected segment. This way, e.g. conjugating "-ir" is treated as a regular -- -ir verb rather than a hyphen + irregular [[ir]]. local is_suffix = (not data or data.text_index == 1) and rfind(bracketed_runs[1], "^%-") local split_pattern = is_suffix and " " or "[ %-]" return put.split_alternating_runs(bracketed_runs, split_pattern, "preserve splitchar") end local function props_transliterate(props, text) return lang_or_func_transliterate(props.transliterate, props.lang, text) end local function parse_before_or_post_text(data) local props, text, text_index, segments, lemma_is_last = data.props, data.text, data.text_index, data.segments, data.lemma_is_last -- Call parse_balanced_segment_run() to keep multiword links together. local bracketed_runs = put.parse_balanced_segment_run(text, "[", "]") -- Split normally on space or hyphen (but customizable). Use preserve_splitchar so we know whether the separator was -- a space or hyphen. local space_separated_groups if props.split_bracketed_runs_into_words then space_separated_groups = props.split_bracketed_runs_into_words(bracketed_runs) end if not space_separated_groups then space_separated_groups = export.default_split_bracketed_runs_into_words(bracketed_runs, data) end local parsed_components = {} local parsed_components_translit = {} local saw_manual_translit = false local lemma for j, space_separated_group in ipairs(space_separated_groups) do local component = table.concat(space_separated_group) if lemma_is_last and j == #space_separated_groups then lemma = component if lemma == "" and not props.allow_blank_lemma then error("Word is blank: '" .. table.concat(segments) .. "'") end elseif rfind(component, "//") then -- Manual translit or respelling specified. if not props.lang then error("Manual translit not allowed for this language; if this is incorrect, 'props.lang' must be set internally") end saw_manual_translit = true local split = split(component, "//", "plain") if #split ~= 2 then error("Term with translit or respelling should have only one // in it: " .. component) end local translit component, translit = unpack(split) if props.transliterate_respelling then translit = props.transliterate_respelling(translit) end table.insert(parsed_components, component) table.insert(parsed_components_translit, translit) else table.insert(parsed_components, component) table.insert(parsed_components_translit, false) -- signal that it may need later transliteration end end if saw_manual_translit then for j, parsed_component in ipairs(parsed_components) do if not parsed_components_translit[j] then parsed_components_translit[j] = props_transliterate(props, m_links.remove_links(parsed_component)) end end end text = table.concat(parsed_components) local translit if saw_manual_translit then translit = table.concat(parsed_components_translit) end return text, translit, lemma end --[=[ Parse a segmented multiword spec such as "[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>" (in Ukrainian). "Segmented" here means it is broken up on <...> segments using parse_balanced_segment_run(text, "<", ">"), e.g. the above text would be passed in as {"[[медичний|меди́чна]]", "<+>", " [[сестра́]]", "<*,*#.pr>", ""}. The return value is a table of the form { word_specs = {``word_spec``, ``word_spec``, ...}, post_text = "``text-at-end``", post_text_no_links = "``text-at-end-no-links``", post_text_translit = "``manual-translit-of-text-at-end``" or nil (if no manual translit or respelling was specified in the post-text) } where ``word_spec`` describes an individual inflected word and "``text-at-end``" is any raw text that may occur after all inflected words. Individual words or linked text (including multiword text) may be given manual transliteration or respelling in languages that support this using ``text``//``translit`` or ``text``//``respelling``. Each ``word_spec`` is of the form returned by parse_indicator_spec(): { lemma = "``lemma``", before_text = "``text-before-word``", before_text_no_links = "``text-before-word-no-links``", before_text_translit = "``manual-translit-of-text-before-word``" or nil (if no manual translit or respelling was specified in the before-text) -- Fields as described in parse_indicator_spec() ... } For example, the return value for "[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>" is { word_specs = { { lemma = "[[медичний|меди́чна]]", overrides = {}, adj = true, before_text = "", before_text_no_links = "", forms = {}, }, { lemma = "[[сестра́]]", overrides = {}, stresses = { { reducible = true, genpl_reversed = false, }, { reducible = true, genpl_reversed = true, }, }, animacy = "pr", before_text = " ", before_text_no_links = " ", forms = {}, }, }, post_text = "", post_text_no_links = "", } ]=] local function parse_multiword_spec(segments, props, disable_allow_default_indicator) local multiword_spec = { word_specs = {} } if not disable_allow_default_indicator then if #segments == 1 then if props.allow_default_indicator then table.insert(segments, "<>") table.insert(segments, "") elseif props.angle_brackets_omittable then segments[1] = "<" .. segments[1] .. ">" table.insert(segments, 1, "") table.insert(segments, "") end end end -- Loop over every other segment. The even-numbered segments are angle-bracket specs while -- the odd-numbered segments are the text between them. for i = 2, #segments - 1, 2 do local before_text, before_text_translit, lemma = parse_before_or_post_text { props = props, text = segments[i - 1], text_index = i - 1, segments = segments, lemma_is_last = true } local base = props.parse_indicator_spec(segments[i], lemma) base.before_text = before_text base.before_text_no_links = m_links.remove_links(base.before_text) base.before_text_translit = before_text_translit base.lemma = base.lemma or lemma table.insert(multiword_spec.word_specs, base) end multiword_spec.post_text, multiword_spec.post_text_translit = parse_before_or_post_text { props = props, text = segments[#segments], text_index = #segments, segments = segments, lemma_is_last = false } multiword_spec.post_text_no_links = m_links.remove_links(multiword_spec.post_text) return multiword_spec end --[=[ Parse an alternant, e.g. "((родо́вий,родови́й))" or "((ру́син<pr>,руси́н<b.pr>))" (both in Ukrainian). The return value is a table of the form { alternants = {``multiword_spec``, ``multiword_spec``, ...} } where ``multiword_spec`` describes a given alternant and is as returned by parse_multiword_spec(). ]=] local function parse_alternant(alternant, props) local parsed_alternants = {} local alternant_text = rmatch(alternant, "^%(%((.*)%)%)$") local segments = put.parse_balanced_segment_run(alternant_text, "<", ">") local comma_separated_groups = put.split_alternating_runs(segments, "%s*,%s*") local alternant_spec = {alternants = {}} for _, comma_separated_group in ipairs(comma_separated_groups) do table.insert(alternant_spec.alternants, parse_multiword_spec(comma_separated_group, props)) end return alternant_spec end --[==[ Top-level parsing function. Parse text describing one or more inflected words. `text` is the inflected text to parse, which generally has `<...>` specs following words to be inflected, and may have alternants indicated using double parens. Examples: * {"[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>"} (Ukrainian, for {{m|uk|меди́чна сестра́||nurse|lit=medical sister}}); * {"((ру́син<pr>,руси́н<b.pr>))"} (Ukrainian, for {{m|uk|русин||Rusyn}}, with two possible stress patterns); * {"पंचायती//पंचाय*ती राज<M>"} (Hindi, for {{m|hi|पंचायती राज||village council}}, with phonetic respelling in the before-text component); * {"((<M>,<M.plstem:फ़तूह.dirpl:फ़तूह>))"} (Hindi, for {{m|hi|फ़तह||win, victory}} when used on that page, where the lemma is omitted and taken from the pagename); * {""} (for any number of Hindi adjectives, where the lemma is omitted and taken from the pagename, and the angle bracket spec <> is assumed); * {"काला<+>धन<M>"} (Hindi, for {{m|hi|कालाधन||black money}}, showing that closed compounds where each part is declined can be correctly handled). `props` is an object specifying properties used during parsing, as follows: ```{ parse_indicator_spec = __function__(``angle_bracket_spec``, ``lemma``) `''(required)''`, lang = __lang object__, transliterate_respelling = __function__(``respelling_or_translit``) `''(optional)''`, split_bracketed_runs_into_words = __function__(``bracket_split_runs``) `''(optional)''`, allow_default_indicator = __boolean__, angle_brackets_omittable = __boolean__, allow_blank_lemma = __boolean__, }``` `parse_indicator_spec` is a required function that takes two arguments, a string surrounded by angle brackets and the lemma, and should return an arbitrary object containing properties describing the indicators inside of the angle brackets). This object is often called a '''base''' and given the argument name `base` in inflection code. `lang` is the language object for the language in question; only needed if manual translit or respelling may be present using `//`. `transliterate_respelling` is a function that is only needed if respelling is allowed in place of manual translit after `//`. It takes one argument, the respelling or translit, and should return the transliteration of any respelling but return any translit unchanged. `split_bracketed_runs_into_words` is an optional function to split the passed-in text into words. It is used, for example, to determine what text constitutes a word when followed by an angle-bracket spec, i.e. what the lemma to be inflected is vs. surrounding fixed text. It takes one argument, the result of splitting the original text on brackets, and should return alternating runs of words and split characters, or nil to apply the default algorithm. Specifically, the value passed in is the result of calling `parse_balanced_segment_run(``text``, "[", "]")` from [[Module:parse utilities]] on the original text, and the default version of this function calls `split_alternating_runs(``bracketed_runs``, ``pattern``, "preserve splitchar")`, where ``bracketed_runs`` is the value passed in and ``pattern`` splits on either spaces or hyphens (unless the text begins with a hyphen, in which case splitting is only on spaces, so that suffixes can be inflected). `allow_default_indicator` should be {true} if an empty indicator in angle brackets `<>` can be omitted and should be automatically added at the end of the multiword text (if no alternants) or at the end of each alternant (if alternants present). `angle_brackets_omittable` should be {true} if angle brackets can be omitted around a non-empty indicator in the presence of a blank lemma. In this case, if the combined indicator spec has no angle brackets, they will be added around the indicator (or around all indicators, if alternants are present). This only makes sense when `allow_blank_lemma` is specified. `allow_blank_lemma` should be {true} of if a blank lemma is allowed; in such a case, the calling function should substitute a default lemma, typically taken from the pagename. The return value is a table referred to as an '''alternant multiword spec''', and is of the form ```{ alternant_or_word_specs = {``alternant_or_word_spec``, ``alternant_or_word_spec``, ...}, post_text = "``text_at_end``", post_text_no_links = "``text_at_end_no_links``", post_text_translit = "``translit_of_text_at_end``" `(or nil)`, }``` where `alternant_or_word_spec` is either an '''alternant spec''' as returned by `parse_alternant()` or a '''multiword spec''' as described in the comment above `parse_multiword_spec()`. An alternant spec looks as follows: ```{ alternants = {``multiword_spec``, ``multiword_spec``, ...}, before_text = "``text_before_alternant``", before_text_no_links = "``text_before_alternant``", before_text_translit = "``translit_of_text_before_alternant``" `(or nil)`, }``` i.e. it is like what is returned by `parse_alternant()` but has extra `before_text` and `before_text_no_links` fields. ]==] function export.parse_inflected_text(text, props) if props.angle_brackets_omittable and not props.allow_blank_lemma then error("If 'angle_brackets_omittable' is specified, so should 'allow_blank_lemma'") end local alternant_multiword_spec = {alternant_or_word_specs = {}} local alternant_segments = split(text, "(%(%(.-%)%))") local last_post_text, last_post_text_no_links, last_post_text_translit for i = 1, #alternant_segments do if i % 2 == 1 then local segments = put.parse_balanced_segment_run(alternant_segments[i], "<", ">") -- Disable allow_default_indicator if alternants are present and we're processing -- the non-alternant text. Otherwise we will try to treat the non-alternant text -- surrounding the alternants as an inflected word rather than as raw text. local multiword_spec = parse_multiword_spec(segments, props, #alternant_segments ~= 1) for _, word_spec in ipairs(multiword_spec.word_specs) do table.insert(alternant_multiword_spec.alternant_or_word_specs, word_spec) end last_post_text = multiword_spec.post_text last_post_text_no_links = multiword_spec.post_text_no_links last_post_text_translit = multiword_spec.post_text_translit else local alternant_spec = parse_alternant(alternant_segments[i], props) alternant_spec.before_text = last_post_text alternant_spec.before_text_no_links = last_post_text_no_links alternant_spec.before_text_translit = last_post_text_translit table.insert(alternant_multiword_spec.alternant_or_word_specs, alternant_spec) end end alternant_multiword_spec.post_text = last_post_text alternant_multiword_spec.post_text_no_links = last_post_text_no_links alternant_multiword_spec.post_text_translit = last_post_text_translit -- Save boolean properties from `props`. We need at least `allow_default_indicator` when implementing -- `reconstruct_original_spec()`. alternant_multiword_spec.allow_default_indicator = props.allow_default_indicator alternant_multiword_spec.angle_brackets_omittable = props.angle_brackets_omittable alternant_multiword_spec.allow_blank_lemma = props.allow_blank_lemma return alternant_multiword_spec end -- Inflect alternants in ``alternant_spec`` (an object as returned by parse_alternant()). -- This sets the form values in ```alternant_spec``.forms` for all slots. -- (If a given slot has no values, it will not be present in ```alternant_spec``.forms`). local function inflect_alternants(alternant_spec, props) alternant_spec.forms = {} for _, multiword_spec in ipairs(alternant_spec.alternants) do export.inflect_multiword_or_alternant_multiword_spec(multiword_spec, props) iterate_slot_list_or_table(props, function(slot) if not props.skip_slot or not props.skip_slot(slot) then export.insert_forms(alternant_spec.forms, slot, multiword_spec.forms[slot]) end end) end end --[=[ Subfunction of `inflect_multiword_or_alternant_multiword_spec()`. This is used in building up the inflections of multiword expressions. The basic purpose of this function is to append a set of forms representing the inflections of a given inflected term in a given slot onto the existing forms for that slot. Given a multiword expression potentially consisting of several inflected terms along with fixed text in between, we work iteratively from left to right, adding the new forms onto the existing ones. Normally, all combinations of new and existing forms are created, meaning if there are M existing forms and N new ones, we will end up with M*N forms. However, some of these combinations can be rejected using the variant mechanism (see the description of get_variants below). Specifically, `formtable` is a table of per-slot forms, where the key is a slot and the value is a list of form objects (objects of the form {form=``form``, translit=``manual_translit``, footnotes=``footnotes``}). `slot` is the slot in question. `forms` specifies the forms to be appended onto the existing forms, and is likewise a list of form objects. `props` is the same as in `inflect_multiword_or_alternant_multiword_spec()`. `before_text` is the fixed text that goes before the forms to be added. `before_text_no_links` is the same as `before_text` but with any links (i.e. hyperlinks of the form [[``term``]] or [[``term``|``display``]]) converted into raw terms using remove_links() in [[Module:links]], and `before_text_translit` is optional manual translit of `before_text_no_links`. Note that the value "?" in a form is "infectious" in that if either the existing or new form has the value "?", the resulting combination will also be "?". This allows "?" to be used to mean "unknown". ]=] local function append_forms(props, formtable, slot, forms, before_text, before_text_no_links, before_text_translit) if not forms then return end local old_forms = formtable[slot] or {{form = ""}} local ret_forms = {} for _, old_form in ipairs(old_forms) do for _, form in ipairs(forms) do local old_form_vars = props.get_variants and props.get_variants(old_form.form) or "" local form_vars = props.get_variants and props.get_variants(form.form) or "" if old_form_vars ~= "" and form_vars ~= "" and old_form_vars ~= form_vars then -- Reject combination due to non-matching variant codes. else local new_formval local new_translit if old_form.form == "?" or form.from == "?" then new_formval = "?" else new_formval = old_form.form .. before_text .. form.form if old_form.translit or before_text_translit or form.translit then if not props.lang then error("Internal error: If manual translit is given, 'props.lang' must be set") end if not before_text_translit then before_text_translit = props_transliterate(props, before_text_no_links) or "" end local old_translit = old_form.translit or props_transliterate(props, m_links.remove_links(old_form.form)) or "" local translit = form.translit or props_transliterate(props, m_links.remove_links(form.form)) or "" new_translit = old_translit .. before_text_translit .. translit end end local new_formobj local new_footnotes = export.combine_footnotes(old_form.footnotes, form.footnotes) if new_formval == form.form and new_translit == form.translit then -- Automatically preserve metadata when possible. new_formobj = m_table.shallowCopy(form) new_formobj.footnotes = new_footnotes else local new_footnotes = export.combine_footnotes(old_form.footnotes, form.footnotes) new_formobj = {form=new_formval, translit=new_translit, footnotes=new_footnotes} if props.combine_metadata then props.combine_metadata { slot = slot, dest_form = new_formobj, form1 = old_form, form2 = form, between_text = before_text, between_text_no_links = before_text_no_links, between_text_translit = before_text_translit, } end end table.insert(ret_forms, new_formobj) end end end formtable[slot] = ret_forms end --[==[ Top-level inflection function. Create the inflections of a noun, verb, adjective or similar. `alternant_multiword_spec` is as returned by `parse_inflected_text` and describes the properties of the term to be inflected, including all the user-provided inflection specifications (e.g. the number, gender, conjugation/declension/etc. of each word) and the surrounding text. `props` indicates how to do the actual inflection (see below). The resulting inflected forms are stored into the `.forms` property of `multiword_spec`. This property holds a table whose keys are slots (i.e. ID's of individual inflected forms, such as "pres_1sg" for the first-person singular present indicative tense of a verb) and whose values are lists of the form `{ form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``}`, where ``form`` is a string specifying the value of the form (e.g. "ouço" for the first-person singular present indicative of the Portuguese verb [[ouvir]]); ``manual_translit_or_nil`` is the corresponding manual transliteration if needed (i.e. if the form is in a non-Latin script and the automatic transliteration is incorrect or unavailable), otherwise nil; and ``footnote_list_or_nil`` is a list of footnotes to be attached to the form, or nil for no footnotes. Note that currently footnotes must be surrounded by brackets, e.g "[archaic]", and should not begin with a capital letter or end with a period. (Conversion from "[archaic]" to "Archaic." happens automatically.) This function has no return value, but modifies `multiword_spec` in-place, adding the `forms` table as described above. After calling this function, call show_forms() on the `forms` table to convert the forms and footnotes given in this table to strings suitable for display. `props` is an object specifying properties used during inflection, as follows: ```{ slot_list = {{"``slot``", "``accel``"}, {"``slot``", "``accel``"}, ...}, slot_table = {``slot`` = "``accel``", ``slot`` = "``accel``", ...}, skip_slot = nil `or` __function__(slot), lang = nil `or` __lang_object__, inflect_word_spec = __function__(base), get_variants = nil 'or` __function__(formval), combine_metadata = nil `or` __function__(data), include_user_specified_links = __boolean__, }``` `slot_list` is a list of two-element lists of slots and associated accelerator tags. ``slot`` is arbitrary but should correspond with slot names as generated by `inflect_word_spec`. ``accel`` is the corresponding accelerator tags; e.g. if ``slot`` is "pres_1sg", ``accel`` might be "1|s|pres|ind". ``accel`` is actually unused during inflection, but is used during `show_forms()`, which takes the same `slot_list` as a property upon input. `slot_table` is a table mapping slots to associated accelerator tags and serves the same function as `slot_list`. Only one of `slot_list` or `slot_table` must be given. For new code it is preferable to use `slot_list` because this allows you to control the order of processing slots, which may occasionally be important. `skip_slot` is a function of one argument, a slot name, and should return a boolean indicating whether to skip the given slot during inflection. It can be used, for example, to skip singular slots if the overall term being inflected is plural-only, and vice-versa. `lang` is a language object. This is only used to generate manual transliteration. If the language is written in the Latin script or manual transliteration cannot be specified in the input to parse_inflected_text(), this can be omitted. (Manual transliteration is allowed if the `lang` object is set in the `props` passed to parse_inflected_text().) `inflect_word_spec` is the function to do the actual inflection. It is passed a single argument, which is a ``word_spec`` object describing the word to be inflected and the user-provided inflection specifications. It is exactly the same as was returned by the `parse_indicator_spec` function provided in the `props` sent on input to `parse_inflected_text`, but has additional fields describing the word to be inflected and the surrounding text, as follows: ```{ lemma = "``lemma``", before_text = "``text-before-word``", before_text_no_links = "``text-before-word-no-links``", before_text_translit = "``manual-translit-of-text-before-word``" or nil (if no manual translit or respelling was specified in the before-text) -- Fields as described in parse_indicator_spec() ... }``` Here ``lemma`` is the word to be inflected as specified by the user (including any links if so given), and the `before_text*` fields describe the raw text preceding the word to be inflected. Any other fields in this object are as set by `parse_inflected_text`, and describe things like the gender, number, conjugation/declension, etc. as specified by the user in the <...> spec following the word to be inflected. `inflect_word_spec` should initialize the `.forms` property of the passed-in ``word_spec`` object to the inflected forms of the word in question. The value of this property is a table of the same format as the `.forms` property that is ultimately generated by inflect_multiword_or_alternant_multiword_spec() and described above near the top of this documentation: i.e. a table whose keys are slots and whose values are lists of the form `{ form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``}`. `get_variants` is either {nil} or a function of one argument (a string, a form value). The purpose of this function is to ensure that in a multiword term where a given slot has more than one possible variant, the final output has only parallel variants in it. For example, feminine nouns and adjectives in Russian have two possible endings, one typically in -ой (-oj) and the other in -ою (-oju). If we have a feminine adjective-noun combination (or a hyphenated feminine noun-noun combination, or similar), and we don't specify `get_variants`, we'll end up with four values for the instrumental singular: one where both adjective and noun end in -ой, one where both end in -ою, and two where one of the words ends in -ой and the other in -ою. In general if we have N words each with K variants, we'll end up with an explosion of N^K possibilities. `get_variants` avoids this by returning a variant code (an arbitary string) for each variant. If two words each have a non-empty variant code, and the variant codes disagree, the combination will be rejected. If `get_variants` is not provided, or either variant code is an empty string, or the variant codes agree, the combination is allowed. The recommended way to use `get_variants` is as follows: 1. During inflection in `inflect_word_spec`, add a special character or string to each of the variants generated for a given slot when there is more than one. (As an optimization, do this only when there is more than one word being inflected.) Special Unicode characters can be used for this purpose, e.g. U+FFF0, U+FFF1, ..., U+FFFD, which have no meaning in Unicode. 2. Specify `get_variants` as a function that pulls out and returns the special character(s) or string included in the variant forms. 3. When calling show_forms(), specify a `canonicalize` function that removes the variant code character(s) or string from each form before converting to the display form. See [[Module:hi-verb]] and [[Module:hi-common]] for an example of doing this in a generalized fashion. (Look for add_variant_codes(), get_variants() and remove_variant_codes().) `combine_metadata` is a function that is invoked when combining two form objects along along with in-between text and storing into a destination form object. When this happens, if the the form value and translit in the first form object is empty and the in-between text is likewise empty (which regularly happens when appending the form object describing the first word in a multiword expression to empty base text), the second form object is simply shallow-copied along with all of its metadata, and any footnotes are combined appropriately (normally the first form object is such a case won't have footnotes). Otherwise, a new form object is constructed by combining the form values, translit and footnotes from the two objects and in-between text, and calling `combine_metadata` to combine any other metadata. Leave this unspecified if there is no additional metadata or if you don't want any metadata carried over. (Examples of metadata that should generally not be carried over are glosses of individual words, sense ID's and similar word-level properties that can't easily be combined to generate a multiword equivalent. Examples of metadata that should be carried over and combined are qualifiers, labels and certain boolean properties such as an uncertainty flag indicating that a given form is uncertain. For some metadata, it is more complex; for example, if both source words have the same gender or part of speech, the destination should keep that value, but if they differ, it may be safest to leave the field blank.) This function, if specified, is called with a single argument as follows: ```{ slot = "__string__", dest_form = __formobj__, form1 = __formobj__, form2 = __formobj__, between_text = "__string__", between_text_no_links = "__string__", between_text_translit = "__string__" `or` nil }``` Here, `slot` is the slot whose forms are being constructed. `dest_form` is the destination form object into which the combined metadata should be written, and is pre-populated with appropriate `form`, `translit` and `footnotes` fields. `form1` and `form2` are the two source forms being combined, and `between_text` is the text to be inserted between the two source forms. `between_text_no_links` is the same as `between_text` but with double-bracket links removed, and `between_text_translit` is the manual transliteration of `between_text_no_links`, if specified. The function should return nothing, but should side-effect `dest_form` as appropriate. `include_user_specified_links`, if given, ensures that user-specified links in the raw text surrounding a given word are preserved in the output. If omitted or set to false, such links will be removed and the whole multiword expression will be linked. ]==] function export.inflect_multiword_or_alternant_multiword_spec(multiword_spec, props) multiword_spec.forms = {} local is_alternant_multiword = not not multiword_spec.alternant_or_word_specs for _, word_spec in ipairs(is_alternant_multiword and multiword_spec.alternant_or_word_specs or multiword_spec.word_specs) do if word_spec.alternants then inflect_alternants(word_spec, props) else props.inflect_word_spec(word_spec) end iterate_slot_list_or_table(props, function(slot) if not props.skip_slot or not props.skip_slot(slot) then append_forms(props, multiword_spec.forms, slot, word_spec.forms[slot], (rfind(slot, "linked") or props.include_user_specified_links) and word_spec.before_text or word_spec.before_text_no_links, word_spec.before_text_no_links, word_spec.before_text_translit ) end end) end if multiword_spec.post_text ~= "" then local pseudoform = {{form=""}} iterate_slot_list_or_table(props, function(slot) -- If slot is empty or should be skipped, don't try to append post-text. if (not props.skip_slot or not props.skip_slot(slot)) and multiword_spec.forms[slot] then append_forms(props, multiword_spec.forms, slot, pseudoform, (rfind(slot, "linked") or props.include_user_specified_links) and multiword_spec.post_text or multiword_spec.post_text_no_links, multiword_spec.post_text_no_links, multiword_spec.post_text_translit ) end end) end end function export.map_word_specs(alternant_multiword_spec, fun) for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do for _, word_spec in ipairs(multiword_spec.word_specs) do fun(word_spec) end end else fun(alternant_or_word_spec) end end end function export.create_footnote_obj() return { notes = {}, seen_notes = {}, noteindex = 1, seen_refs = {}, } end function export.get_footnote_text(footnotes, footnote_obj) if not footnotes then return "" end local link_indices = {} local all_refs = {} for _, footnote in ipairs(footnotes) do local refs footnote, refs = export.expand_footnote_or_references(footnote) if footnote then local this_noteindex = footnote_obj.seen_notes[footnote] if not this_noteindex then -- Generate a footnote index. this_noteindex = footnote_obj.noteindex footnote_obj.noteindex = footnote_obj.noteindex + 1 table.insert(footnote_obj.notes, '<sup style="color: var(--wikt-palette-red, red)">' .. this_noteindex .. '</sup>' .. footnote) footnote_obj.seen_notes[footnote] = this_noteindex end m_table.insertIfNot(link_indices, this_noteindex) end if refs then for _, ref in ipairs(refs) do if not ref.name then local this_refhash = footnote_obj.seen_refs[ref.text] if not this_refhash then -- Different text needs to have different auto-generated names, globally across the entire page, -- including across different invocations of {{it-verb}} or {{it-conj}}. The easiest way to accomplish -- this is to use a message-digest hashing function. It does not have to be cryptographically secure -- (MD5 is insecure); it just needs to have low probability of collisions. this_refhash = mw.hash.hashValue("md5", ref.text) footnote_obj.seen_refs[ref.text] = this_refhash end ref.autoname = this_refhash end -- I considered using "n" as the default group rather than nothing, to more clearly distinguish regular -- footnotes from references, but this requires referencing group "n" as <references group="n"> below, -- which is non-obvious. m_table.insertIfNot(all_refs, ref) end end end table.sort(link_indices) local function sort_refs(r1, r2) -- FIXME, we are now sorting on an arbitrary hash. Should we keep track of the order we -- saw the autonamed references and sort on that? if r1.autoname and r2.name then return true elseif r1.name and r2.autoname then return false elseif r1.name and r2.name then return r1.name < r2.name else return r1.autoname < r2.autoname end end table.sort(all_refs, sort_refs) for i, ref in ipairs(all_refs) do local refargs = {name = ref.name or ref.autoname, group = ref.group} all_refs[i] = mw.getCurrentFrame():extensionTag("ref", ref.text, refargs) end local link_text if #link_indices > 0 then link_text = '<sup style="color: var(--wikt-palette-red, red)">' .. table.concat(link_indices, ",") .. '</sup>' else link_text = "" end local ref_text = table.concat(all_refs) if link_text ~= "" and ref_text ~= "" then return link_text .. "<sup>,</sup>" .. ref_text else return link_text .. ref_text end end --[==[ Add links around words in a term. If multiword_only, do it only in multiword terms. ]==] function export.add_links(form, multiword_only) if form == "" or form == " " then return form end if not form:find("%[%[") then if rfind(form, "[%s%p]") then --optimization to avoid loading [[Module:headword]] on single-word forms local m_headword = require("وحدة:headword/ملعب2") --W if m_headword.head_is_multiword(form) then form = m_headword.add_multiword_links(form) end end if not multiword_only and not form:find("%[%[") then form = "[[" .. form .. "]]" end end return form end --[==[ Remove redundant link surrounding entire term. ]==] function export.remove_redundant_links(term) return rsub(term, "^%[%[([^%[%]|]*)%]%]$", "%1") end --[==[ Add links to all before and after text; for use in inflection modules that preserve links in multiword lemmas and include links in non-lemma forms rather than allowing the entire form to be a link. If `remember_original`, remember the original user-specified before/after text so we can reconstruct the original spec later. `add_links` is a function of one argument to add links to a given piece of text; if unspecified, it defaults to `export.add_links`. ]==] function export.add_links_to_before_and_after_text(alternant_multiword_spec, remember_original, add_links) add_links = add_links or export.add_links local function add_links_remember_original(object, field) if remember_original then object["user_specified_" .. field] = object[field] end object[field] = add_links(object[field]) end for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do add_links_remember_original(alternant_or_word_spec, "before_text") if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do for _, word_spec in ipairs(multiword_spec.word_specs) do add_links_remember_original(word_spec, "before_text") end add_links_remember_original(multiword_spec, "post_text") end end end add_links_remember_original(alternant_multiword_spec, "post_text") end --[==[ Reconstruct the original overall spec from the output of parse_inflected_text(), so we can use it in the language-specific acceleration module in the implementation of {{tl|pt-verb form of}} and the like. `props` is an optional table of properties. Currently only `preprocess_angle_bracket_spec` is recognized, and is an optional function of one argument that is called to process an angle-bracket spec before inserting into the reconstructed spec. ]==] function export.reconstruct_original_spec(alternant_multiword_spec, props) local parts = {} props = props or {} local function ins(txt) table.insert(parts, txt) end local function insert_angle_bracket_spec(spec) if props.preprocess_angle_bracket_spec then spec = props.preprocess_angle_bracket_spec(spec) end ins(spec) end for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do ins(alternant_or_word_spec.user_specified_before_text) if alternant_or_word_spec.alternants then ins("((") for i, multiword_spec in ipairs(alternant_or_word_spec.alternants) do if i > 1 then ins(",") end for _, word_spec in ipairs(multiword_spec.word_specs) do ins(word_spec.user_specified_before_text) ins(word_spec.user_specified_lemma) insert_angle_bracket_spec(word_spec.angle_bracket_spec) end ins(multiword_spec.user_specified_post_text) end ins("))") else ins(alternant_or_word_spec.user_specified_lemma) insert_angle_bracket_spec(alternant_or_word_spec.angle_bracket_spec) end end ins(alternant_multiword_spec.user_specified_post_text) local retval = table.concat(parts) if alternant_multiword_spec.allow_default_indicator then -- As a special case, if we see e.g. "amar<>", remove the <>. Don't do this if there are spaces or alternants. if not retval:find(" ") and not retval:find("%(%(") then local retval_no_angle_brackets = retval:match("^(.*)<>$") if retval_no_angle_brackets then return retval_no_angle_brackets end end end return retval end --[==[ Convert the forms in ``formtable`` (a form table, whose keys are slots and whose values are lists of form objects, each of which is a table of the form `form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``, no_accel = ``true_to_suppress_accelerators``, ... `) into strings. The form table is side-effected. Each form list turns into a string consisting of a comma-separated list of linked forms, with accelerators (unless `no_accel` is set in a given form object). If `include_translit` is specified, each string consists of a comma-separated list of form values (each formatted as a link), an HTML `&lt;br/>`, and a comma-separated list of transliterations. `props` is a table used in generating the strings, as follows: ```{ lang = __lang_object__, lemmas = {"``lemma``", "``lemma``", ...}, slot_list = {{"``slot``", "``accel``"}, {"``slot``", "``accel``"}, ...}, slot_table = {``slot`` = "``accel``", ``slot`` = "``accel``", ...}, include_translit = __boolean__, create_footnote_obj = nil `or` __function__(), canonicalize = nil or __function__(formval), preprocess_forms = nil `or` __function__(data), no_deduplicate_forms = __boolean__, combine_metadata_during_dedup = nil `or` __function__(data), transform_accel_obj = nil `or` __function__(slot, form, accel_obj), format_forms = nil `or` __function__(data), generate_link = nil `or` __function__(data), format_tr = nil `or` __function__(data), join_spans = nil `or` __function__(data), allow_footnote_symbols = __boolean__, footnotes = nil or {"``extra_footnote``", "``extra_footnote``", ...}, }``` `lemmas` is the list of lemmas, used in the accelerators. `slot_list` is a list of two-element lists of slots and associated accelerator tag sets. ``slot`` should correspond to slots generated during `inflect_multiword_or_alternant_multiword_spec()`. ``accel`` is the corresponding accelerator tag set; e.g. if ``slot`` is "pres_1sg", ``accel`` might be "1|s|pres|ind". ``accel`` is used in generating entries for accelerator support (see [[WT:ACCEL]]). `slot_table` is a table mapping slots to associated accelerator tag sets and serves the same function as `slot_list`. Only one of `slot_list` or `slot_table` must be given. For new code it is preferable to use `slot_list` because this allows you to control the order of processing slots, which may occasionally be important. `include_translit`, if given, causes transliteration to be included in the generated strings. The function works as follows: # Create an object to hold footnotes (customizable using `create_footnote_obj`). # Generate the comma-separated lemma form values and store in `.lemma` in the form table. # Loop over the slots specified using `slot_list` or `slot_table`. For each slot: ## Canonicalize the form values (customizable using `canonicalize`; by default does nothing). ## Preprocess the forms (customizable using `preprocess_forms`; by default does nothing). ## Unless `no_deduplicate_forms` is set, deduplicate forms in a slot sharing the same form value but possibly different transliteration. (This happens e.g. in Russian, where it is relatively common for a given form to have two possible transliterations, one reflecting a more nativized pronunciation where Cyrillic е triggers palatalization of the preceding consonant, and one reflecting a more "foreign" pronunciation where this palatalization does not happen. In such a case, the automatic transliteration would normally suffice for the more nativized pronunciation but the more "foreign" pronunciation will need manual transliteration.) As part of deduplication, footnotes will be combined using `combine_footnotes`; distinct manual transliterations will be combined into a list (meaning the `translit` field of form objects in some subsequent `props` functions may hold a list; this will be noted when possible); and any remaining metadata will be combined using the `combine_metadata_during_dedup` method, if provided. ## Add acceleration to all forms. The acceleration tag set associated with a given form comes from `slot_list` or `slot_table`, i.e. all forms in a given slot have the same tag set. However, different forms will have different associated transliterations stored into the accelerator object associated with the form, as well as possibly different lemmas. In particular, when there are multiple lemma forms, this is often due to alternative ways to pronounce the lemma (e.g. alternative stress positions or vowel lengths), and there are often associated non-lemma forms that match each lemma. An example given in the introduction is {{m+|uk|русин||Rusyn}}, stressed in the lemma as ''ру́син'' or ''руси́н'' with associated genitive singulars ''ру́сина'' and ''русина́''. We would like the auto-generated accelerator entry for {{m|uk|русина}} to show the variant ''ру́сина'' as having lemma ''ру́син'' and the variant ''русина́'' as having the lemma ''руси́н'', rather than showing both variants as having both lemmas, which is less accurate. As a result, the code that generates acceleration objects for forms matches up forms and lemmas one-to-one if possible. If this is not possible, the matching is usually one lemma to many forms, as in {{m+|uk|міст||bridge}} with genitive singular ''мо́сту'' or ''моста́'' (in which case all forms get the same lemma), or many lemmas to one form, as in {{m+|uk|черга||turn, queue}} stressed either ''че́рга'' or ''черга́'' with nominative singular only ''че́рги'' (in which case the single form gets assicated all lemmas). If there are multiple lemmas and multiple forms, the algorithm attempts to align them as evenly as possible (e.g. two lemma variants to four forms means the first two forms get assigned the first lemma variant and the last two forms get assigned the second lemma variant); this is often going to be incorrect, but (a) there's unlikely to be a single algorithm that works in all such circumstances, and (b) these cases are very rare. Finally, note the following: ##* No acceleration is assigned to a form if any of the following apply: (a) there are no lemmas given in `props.lemmas`; (b) the `no_accel` key in the form object has a non-falsy value; (c) the form value of the form is {"?"} or an em-dash ({"—"}); (d) the accelerator tag set is given as a hyphen {"-"}); or (e) the form value contains an internal link. ##* The accelerator code sets the `formval_for_link` key in each form object to the version of the form value that should be passed to `full_link()` in [[Module:links]]. This is usually the same as the passed-in form value, but differs when `props.allow_footnote_symbols` is specified and an old-style footnote symbol is attached to the form (the removed footnote symbol is stored in the `formval_old_style_footnote_symbol` key), and also differs when the entire form value is surrounded with a redundant internal link (which is removed). ##* The resulting accelerator object can be modified (or replaced entirely) by the `transform_accel_obj` function. This is used, for example, in [[Module:es-verb]], [[Module:pt-verb]] and other Romance-language verb conjugation modules (likewise [[Module:ar-verb]]) to replace the tag set with the original verb spec used to generate the verb, so that the accelerator code can generate the appropriate call to {{tl|es-verb form of}}, {{tl|pt-verb form of}} or the like, which computes the inflections, instead of directly listing the inflections. ## Format the forms into strings. The entire default process can be replaced using `format_forms`; otherwise the default algorithm works as follows: ### Generate the '''form value spans''', with one entry (a linked HTML-ized version of the form value) per form. This can be customized using `generate_link`. (Various modules do this. For example, the Arabic verb module includes qualifiers, labels, ID's and the like that can be specified by the user; the Portuguese and reintegrated Galician verb modules italicize certain superseded or otherwise less-desirable forms instead of linking them normally; the German verb module adds {{m|de|dass}} to subjunctive forms and optional pronouns to imperative forms; and the German adjective module adds articles to adjective forms normally accompanied by articles and the equivalent of "he/she is" etc. to predicate forms.) The default uses `full_link()` in [[Module:links]] (with transliteration generation disabled) concatenated with the appropriate footnote symbol(s) (if any). ### Generate the '''transliteration spans''', with one entry per distinct translit, auto-generated if manual translit isn't available. Note that, due to the earlier form value deduplication step, there may be multiple translits per form object. These translits are themselves deduplicated to get the list of spans. (Such duplication can happen, for example, in Arabic with terms containing a glottal stop in them; there may be multiple ways of spelling the glottal stop or ''hamza'' in Arabic, but only one way of transliterating it.) Each span consists of an object specifying the translit minus any attached old-style footnote symbols (which are only allowed if `props.allow_footnote_symbols` is set); the attached old-style footnote symbol, which is always an empty string when `props.allow_footnote_symbols` is not set; and the list of (new-style) footnotes. These objects are then converted to formatted strings, either using `format_tr` if supplied or else calling `tag_translit()` in [[Module:script utilities]] and concatenating the appropriate footnote symbol(s) (if any). ### Combine the form value and transliteration spans. If `join_spans` is supplied, use it; otherwise, concatenate the form value spans (comma-separated) and (if available) transliteration spans (comma-separated), and (if appropriate) combine them using {<br />}. `create_footnote_obj` is an optional function of no arguments to create the footnote object used to track footnotes; see `create_footnote_obj()`. Customizing it is useful to prepopulate the footnote table using `get_footnote_text()`. `canonicalize` is an optional function of one argument (a form value) to canonicalize each form before processing; it can return nil for no change. The most common purpose of this function is to remove variant codes from the form value. See the documentation for `inflect_multiword_or_alternant_multiword_spec()` for a description of variant codes and their purpose. `preprocess_forms` is an optional function of one argument (a table of properties) to preprocess the form objects as a whole. It runs after `canonicalize` (meaning that the form values passed in are canonicalized) and before deduplication and the addition of acceleration info. The property table passed in has the following properties: * `slot`: The slot being processed. * `forms`: The list of form objects for this slot. * `accel_tag_set`: The accelerator tag set for this slot, taken from `slot_list` or `slot_table`. * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. `preprocess_forms` should return a list of preprocessed form objects, or {nil} to use the passed-in `forms`. If this function does deduplication, you should set `no_deduplicate_forms` to disable the default deduplication process. `no_deduplicate_forms`, if set, disables the deduplication step (see above). `combine_metadata_during_dedup` is an optional function of one argument (a table of properties) to combine the metadata of deduplicated form objects. The property table passed in has the following properties: * `slot`: The slot being processed. * `existing_form`: The existing form object into which a duplicated form is being combined. * `dup_form`: The duplicated form being combined into `existing_form`. * `existing_form_pos`: The one-based position of the existing form in the deduplicated form list (not necessarily its original position). * `dup_form_pos`: The one-based position of the duplicated form in its original list. The following should be noted about the form objects passed in: # The form values in `.form` have been canonicalized using `.canonicalize`, if provided. # The form values in `existing_form` and `dup_form` are always the same. # The footnotes in `existing_form` have already been combined with those in `dup_form`. # If there was manual translit either in `existing_form` (prior to deduplication) or in `dup_form`, there will be manual translit in `existing_form.translit` that is a list and combines any previous accumulated translits in `existing_form` as well as the translit in `dup_form` (even if one of them was specified as {nil} indicating an automatic translit). This means that the translit in `existing_form.translit` is always either {nil} or a list of strings (and the same applies to `dup_form.translit`). `transform_accel_obj` is an optional function of three arguments (``slot``, ``formobj``, ``accel_obj``) to transform the default constructed accelerator object in ``accel_obj`` into an object that should be passed to `full_link()` in [[Module:links]]. It should return the new accelerator object, or {nil} for no acceleration. (If {nil} is returned, the corresponding form has no acceleration; this is unlike most customization functions, where returning {nil} causes the default algorithm to be invoked.) The function can destructively modify the accelerator object passed in. '''NOTE''': This is called even when the passed-in ``accel_obj`` is {nil} (see the (a) through (e) reasons above why no acceleration may be assigned to a form). Thus, your code needs to do something sensible in this case. The description above of how `show_forms()` works inclues various examples of modules that supply a `transform_accel_obj` function and the reasons for doing so. `format_forms`, if supplied, is a function that entirely replaces the formatting portion of `show_forms()`. An example of why you might want to do this is to get a different layout than the default, e.g. one where translit is displayed next to each form value instead of the form values and translits grouped and displayed on separate lines. Under normal circumstances, you should not do this, but instead customize the functions that replace specific parts of the default formatting algorithm (see below). This function is passed one argument (a table of properties) and should return a string (the formatted forms, ready to store into the slot in the form table) or {nil} to proceed with the default algorithm (see above). The property table passed in has the following properties: * `slot`: The slot being processed. * `forms`: The list of form objects, deduplicated and with accelerator info added. * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. The following should be noted about the form objects in `forms`: # There are extra fields `formval_for_link`, `formval_old_style_footnote_symbol` and `accel_obj`. The first two are as described above under the paragraph beginning "Add acceleration to all forms" under "The function works as follows". The third one is the accelerator object in the format expected by [[Module:links]]. # The `translit` field, if non-{nil}, is a list of transliterations rather than a single transliteration; this is due to the form value deduplication step. `generate_link` is an optional function to generate the link text for a given form value. It is passed a single argument (a table of properties) and should return a string, the formatted link. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following properties: * `slot`: The slot being processed. * `form`: The form to be converted to a formatted link. As with the `format_forms` function described above, the form objects passed in contain extra fields `formval_for_link`, `formval_old_style_footnote_symbol` and `accel_obj` (all of which will normally be used), and the `translit` field, if non-{nil}, is a list. * `pos`: The one-based position of the form being processed, in the list of form value spans. Rarely used. * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. Normally used in order to get the (new-style) footnote symbol associated with any footnotes in `footnotes`. The description above of how `show_forms()` works inclues various examples of modules that supply a `generate_link` function and the reasons for doing so. `format_tr` is an optional function to generate the formatted text for a given transliteration. It is passed a single argument (a table of properties) and should return a string, the formatted transliteration text. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following properties: * `slot`: The slot being processed. * `tr_for_tag`: The transliteration to process, where old-style footnote symbols have been removed. * `old_style_footnote_symbol`: The removed old-style footnote symbol, or a blank string if no symbol was removed. * `pos`: The one-based position of the transliteration being processed, in the list of transliteration spans. Rarely used. * `footnotes`: The list of footnotes associated with all form objects with this transliteration. (If there were multiple form objects with the same transliteration, the list of footnotes will have been generated using `combine_footnotes()`.) * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. Normally used in order to get the (new-style) footnote symbol associated with any footnotes in `footnotes`. `join_spans` is an optional function to join the processed form value and transliteration spans into a formatted string. It is passed a single argument (a table of properties) and should return the final string to store into the form table slot. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following properties: * `slot`: The slot being processed. * `formval_spans`: A list of strings, the formatted form value spans. * `tr_spans`: A list of strings, the formatted transliteration spans. If there is no transliteration, this will be an empty list. A custom `join_spans` is provided by [[Module:de-verb]], which concatenates the form value spans vertically (using {"<br />"}) instead of horizontally using a comma, as is normal; this is because there is no translit and the form values are often long, containing extra words attached during `generate_link()`. The only exception is the `aux` slot holding the auxiliaries, which is concatenated horizontally using {" or "}. [[Module:de-adjective]] similarly provides a custom `join_spans` function that concatenates the form value spans vertically. `allow_footnote_symbols`, if given, causes any old-style footnote symbols attached to forms (e.g. numbers, asterisk) to be separated off, placed outside the links, and superscripted. In this case, `footnotes` should be a list of footnotes (preceded by footnote symbols, which are superscripted). These footnotes are combined with any footnotes found in the forms and placed into `forms.footnotes`. This mechanism of specifying footnotes is provided for backward compatibility with certain existing inflection modules and should not be used for new modules. Instead, use the regular footnote mechanism specified using the `footnotes` property attached to each form object. ]==] function export.show_forms(formtable, props) local footnote_obj = props.create_footnote_obj and props.create_footnote_obj() or export.create_footnote_obj() local function fetch_formval_and_translit(entry, remove_links) local formval, translit if type(entry) == "table" then formval, translit = entry.form, entry.translit else formval = entry end if remove_links then formval = m_links.remove_links(formval) end return formval, translit end local lemma_formvals = {} for _, lemma in ipairs(props.lemmas) do local lemma_formval, _ = fetch_formval_and_translit(lemma) m_table.insertIfNot(lemma_formvals, lemma_formval) end formtable.lemma = #lemma_formvals > 0 and table.concat(lemma_formvals, "، ") or mw.loadData(headword_data_module).pagename -- For safety, since we in-place modify `lemmas` usually before processing a given slot, make a copy. local props_lemmas = m_table.shallowCopy(props.lemmas) for i, lemma in ipairs(props_lemmas) do props_lemmas[i] = m_table.shallowCopy(lemma) end local function do_slot(slot, accel_tag_set) local formobjs = formtable[slot] if formobjs then if type(formobjs) ~= "table" then error("Internal error: For slot '" .. slot .. "', expected table but saw " .. dump(formobjs)) end -- Maybe canonicalize the form values (e.g. remove variant codes and monosyllabic accents). if props.canonicalize then for _, form in ipairs(formobjs) do form.form = props.canonicalize(form.form) or form.form end end -- Preprocess the forms as a whole if called for. if props.preprocess_forms then formobjs = props.preprocess_forms { slot = slot, forms = formobjs, accel_tag_set = accel_tag_set, footnote_obj = footnote_obj, } or formobjs end -- Maybe deduplicate form values (happens e.g. in Russian with two terms with the same Russian form but -- different translits). if not props.no_deduplicate_forms then local deduped_formobjs = {} for i, form in ipairs(formobjs) do local function combine_forms(existing_form, dup_form, pos) assert(existing_form.form == dup_form.form) -- Combine footnotes. existing_form.footnotes = export.combine_footnotes(existing_form.footnotes, dup_form.footnotes) -- If translit is being generated, and there's manual translit associated with either form, we -- need to generate any missing translits and combine them, taking into account the fact that a -- translit value may actually be a list of translits (particularly with the existing form if we -- already combined an item with manual translit into it). if props.include_translit and form_value_transliterable(existing_form.form) and ( existing_form.translit or dup_form.translit) then local combined_translit if not existing_form.translit then combined_translit = { props_transliterate(props, m_links.remove_links(existing_form.form)) } elseif type(existing_form.translit) == "string" then combined_translit = {existing_form.translit} else combined_translit = existing_form.translit end local dup_form_translit = dup_form.translit if not dup_form_translit then -- dup_form.form is the same as existing_form.form (see assert above), but this is -- defensive programming in case that changes dup_form_translit = {props_transliterate(props, m_links.remove_links(dup_form.form))} elseif type(dup_form_translit) == "string" then dup_form_translit = {dup_form_translit} end for _, translit in ipairs(dup_form_translit) do m_table.insertIfNot(combined_translit, translit) end existing_form.translit = combined_translit end if props.combine_metadata_during_dedup then props.combine_metadata_during_dedup { slot = slot, existing_form = existing_form, existing_form_pos = pos, dup_form = dup_form, dup_form_pos = i, } end end m_table.insertIfNot(deduped_formobjs, form, { key = function(form) return form.form end, combine = combine_forms, }) end formobjs = deduped_formobjs end -- Add acceleration info to form objects. for i, form in ipairs(formobjs) do local formval = form.form if not form_value_transliterable(formval) then form.formval_for_link = formval form.formval_old_style_footnote_symbol = "" else local formval_for_link, formval_old_style_footnote_symbol if props.allow_footnote_symbols then formval_for_link, formval_old_style_footnote_symbol = require(table_tools_module).get_notes(formval) if formval_old_style_footnote_symbol ~= "" then track("old-style-footnote-symbol") end else formval_for_link = formval formval_old_style_footnote_symbol = "" end -- remove redundant link surrounding entire form formval_for_link = export.remove_redundant_links(formval_for_link) form.formval_for_link = formval_for_link form.formval_old_style_footnote_symbol = formval_old_style_footnote_symbol -------------------- Compute the accelerator object. ----------------- local accel_obj -- Check if form still has links; if so, don't add accelerators because the resulting entries will -- be wrong. if props_lemmas[1] and not form.no_accel and accel_tag_set ~= "-" and not rfind(formval_for_link, "%[%[") then -- If there is more than one form or more than one lemma, things get tricky. Often, there are -- the same number of forms as lemmas, e.g. for Ukrainian [[зимовий]] "wintry; winter (rel.)", -- which can be stressed зимо́вий or зимови́й with corresponding masculine/neuter genitive -- singulars зимо́вого or зимово́го etc. In this case, usually the forms and lemmas match up so -- we do this. If there are different numbers of forms than lemmas, it's usually one lemma -- against several forms e.g. Ukrainian [[міст]] "bridge" with genitive singular мо́сту or моста́ -- (accent patterns b or c) or [[ложка|ло́жка]] "spoon" with nominative plural ло́жки or ложки́ -- (accent patterns a or c). Here, we should assign the same lemma to both forms. The opposite -- can happen, e.g. [[черга]] "turn, queue" stressed че́рга or черга́ with nominative plural only -- че́рги (accent patterns a or d). Here we should assign both lemmas to the same form. In more -- complicated cases, with more than one lemma and form and different numbers of each, we try -- to align them as much as possible, e.g. if there are somehow eight forms and three lemmas, -- we assign lemma 1 to forms 1-3, lemma 2 to forms 4-6 and lemma 3 to forms 7 and 8, and -- conversely if there are somehow three forms and eight lemmas. This is likely to be wrong, but -- (a) there's unlikely to be a single algorithm that works in all such circumstances, and (b) -- these cases are vanishingly rare or nonexistent. Properly we should try to remember which -- form was generated by which lemma, but that is significant extra work for little gain. local first_lemma, last_lemma if #formobjs >= #props_lemmas then -- More forms than lemmas. Try to even out the forms assigned per lemma. local forms_per_lemma = math.ceil(#formobjs / #props_lemmas) first_lemma = math.floor((i - 1) / forms_per_lemma) + 1 last_lemma = first_lemma else -- More lemmas than forms. Try to even out the lemmas assigned per form. local lemmas_per_form = math.ceil(#props_lemmas / #formobjs) first_lemma = (i - 1) * lemmas_per_form + 1 last_lemma = math.min(first_lemma + lemmas_per_form - 1, #props_lemmas) end local accel_lemma, accel_lemma_translit if first_lemma == last_lemma then accel_lemma, accel_lemma_translit = fetch_formval_and_translit(props_lemmas[first_lemma], "remove links") else accel_lemma = {} accel_lemma_translit = {} for j=first_lemma, last_lemma do local this_lemma = props_lemmas[j] local this_accel_lemma, this_accel_lemma_translit = fetch_formval_and_translit(props_lemmas[j], "remove links") -- Do not use table.insert() especially for the translit because it may be nil and in -- that case we want gaps in the array. accel_lemma[j - first_lemma + 1] = this_accel_lemma accel_lemma_translit[j - first_lemma + 1] = this_accel_lemma_translit end end local accel_translit if props.include_translit and form.translit then if type(form.translit) == "table" then accel_translit = table.concat(form.translit, ", ") elseif type(form.translit) == "string" then accel_translit = form.translit else error(("Internal error: For slot '%s', form translit is not a table or string: %s"): format(slot, dump(accel_translit))) end end accel_obj = { form = accel_tag_set, translit = accel_translit, lemma = accel_lemma, lemma_translit = props.include_translit and accel_lemma_translit or nil, } end -- Postprocess if requested. if props.transform_accel_obj then accel_obj = props.transform_accel_obj(slot, form, accel_obj) end form.accel_obj = accel_obj end end -- Format the form objects into a string for insertion into the table. local formatted_forms if props.format_forms then formatted_forms = props.format_forms { slot = slot, forms = forms, footnote_obj = footnote_obj, } end if not formatted_forms then -- Default algorithm: Separate form values and translits and concatenate on separate lines. -- Form values have already been deduplicated but we may need to deduplicate translits (this happens -- e.g. in Arabic where there may be multiple ways of spelling a hamza in the Arabic script but only -- one way in transliteration). local formval_spans = {} local tr_spans = {} for i, form in ipairs(formobjs) do local link if props.generate_link then link = props.generate_link { slot = slot, pos = i, form = form, footnote_obj = footnote_obj, } end if not link then link = m_links.full_link { lang = props.lang, term = form.formval_for_link, tr = "-", accel = form.accel_obj } .. form.formval_old_style_footnote_symbol .. export.get_footnote_text(form.footnotes, footnote_obj) end formval_spans[i] = link if props.include_translit then -- Note that if there is an attached old-style footnote symbol, we transliterate it. local translits = form.translit or props_transliterate(props, m_links.remove_links(form.form)) if type(translits) == "string" then translits = {translits} end for _, tr in ipairs(translits) do local tr_for_tag, tr_old_style_footnote_symbol if props.allow_footnote_symbols then tr_for_tag, tr_old_style_footnote_symbol = require(table_tools_module).get_notes(tr) if tr_old_style_footnote_symbol ~= "" then track("old-style-footnote-symbol") end else tr_for_tag = tr tr_old_style_footnote_symbol = "" end m_table.insertIfNot(tr_spans, { tr_for_tag = tr_for_tag, old_style_footnote_symbol = tr_old_style_footnote_symbol, footnotes = form.footnotes, }, { key = function(trobj) return trobj.tr_for_tag end, combine = function(tr, newtr) -- Combine footnotes. tr.footnotes = export.combine_footnotes(tr.footnotes, newtr.footnotes) tr.old_style_footnote_symbol = tr.old_style_footnote_symbol .. newtr.old_style_footnote_symbol end, }) end end end for i, tr_span in ipairs(tr_spans) do local formatted_tr if props.format_tr then formatted_tr = props.format_tr { slot = slot, pos = i, tr_for_tag = tr_span.tr_for_tag, old_style_footnote_symbol = tr_span.old_style_footnote_symbol, footnotes = tr_span.footnotes, footnote_obj = footnote_obj, } end if not formatted_tr then formatted_tr = require(script_utilities_module).tag_translit(tr_span.tr_for_tag, props.lang, "default", " style=\"color: var(--wikt-palette-grey-8,#888);\"") .. tr_span.old_style_footnote_symbol .. export.get_footnote_text(tr_span.footnotes, footnote_obj) end tr_spans[i] = formatted_tr end if props.join_spans then formatted_forms = props.join_spans { slot = slot, formval_spans = formval_spans, tr_spans = tr_spans, } end if not formatted_forms then local formval_span = table.concat(formval_spans, "، ") local tr_span if #tr_spans > 0 then tr_span = table.concat(tr_spans, "، ") end if tr_span then formatted_forms = formval_span .. "<br />" .. tr_span else formatted_forms = formval_span end end end formtable[slot] = formatted_forms else formtable[slot] = "—" end end iterate_slot_list_or_table(props, do_slot) local all_notes = footnote_obj.notes if props.footnotes then for _, note in ipairs(props.footnotes) do track("old-style-footnote-symbol") local symbol, entry = require(table_tools_module).get_initial_notes(note) table.insert(all_notes, symbol .. entry) end end formtable.footnote = table.concat(all_notes, "<br />") end --[==[ Given a list of forms (each of which is a table of the form `{form=``form``, translit=``manual_translit``, footnotes=``footnotes``}`), concatenate into a `"``slot``=``form``//``translit``,``form``//``translit``,..."` string (or `"``slot``=``form``,``form``,..."` if no translit), replacing embedded `|` signs with `<!>`. '''NOTE:''' This function is deprecated. Use an argument {{para|json|1}} to return a JSON encoding of the alternant multiword spec (including any forms) instead. ]==] function export.concat_forms_in_slot(forms) if forms then local new_vals = {} for _, v in ipairs(forms) do local form = v.form if v.translit then form = form .. "//" .. v.translit end table.insert(new_vals, rsub(form, "|", "<!>")) end return table.concat(new_vals, ",") else return nil end end return export kbcr0k4p5ms3dwlta2u29qqnz70v2jx 1097569 1097560 2026-07-26T18:26:38Z ForzaGreen 28665 استعمال [[وحدة:headword]] بدل نسخة الملعب headword/ملعب2 (الدالتان المستعملتان متطابقتان) 1097569 Scribunto text/plain local export = {} local m_links = require("Module:links") --W local m_str_utils = require("Module:string utilities") local m_table = require("Module:table") local put = require("Module:parse utilities") local headword_data_module = "Module:headword/data" local script_utilities_module = "Module:script utilities" local table_tools_module = "Module:table tools" local is_callable = require("Module:fun").is_callable local split = m_str_utils.split local rfind = mw.ustring.find local rmatch = mw.ustring.match local rsubn = mw.ustring.gsub local ucfirst = m_str_utils.ucfirst local unpack = unpack or table.unpack -- Lua 5.2 compatibility local dump = mw.dumpObject -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end local function track(page) require("Module:debug/track")("inflection utilities/" .. page) return true end local footnote_abbrevs = { ["a"] = "archaic", ["c"] = "colloquial", ["d"] = "dialectal", ["fp"] = "folk-poetic", ["l"] = "literary", ["lc"] = "low colloquial", ["p"] = "poetic", ["pej"] = "pejorative", ["r"] = "rare", } --[==[ intro: The following code is used in building up the inflection of terms in inflected languages, where a term can potentially consist of several inflected words, each surrounded by fixed text, and a given slot (e.g. accusative singular) of a given word can potentially consist of multiple possible inflected forms. In addition, each form may be associated with a manual transliteration and/or a list of footnotes (or qualifiers, in the case of headword lines). The following terminology is helpful to understand: * A '''term''' is a word or multiword expression that can be inflected. A multiword term may in turn consist of several single-word inflected terms with surrounding fixed text. A term belongs to a particular '''part of speech''' (e.g. noun, verb, adjective, etc.). * An '''inflection dimension''' is a particular dimension over which a term may be inflected, such as case, number, gender, person, tense, mood, voice, aspect, etc. * The '''lemma''' is the particular form of a term under which the term is entered into a dictionary. For example, for verbs, it is most commonly the infinitive, but this differs for some languages: e.g. Latin, Greek and Bulgarian use the first-person singular present indicative (active voice in the case of Latin and Greek); Sanskrit and Macedonian use the third-person singular present indicative (active voice in the case of Sanskrit); Hebrew and Arabic use the third-person singular masculine past (aka "perfect"); etc. For nouns, the lemma form is most commonly the nominative singular, but e.g. for Old French it is the objective singular and for Sanskrit it is the root. * A '''slot''' is a particular combination of inflection dimensions. An example might be "accusative plural" for a noun, or "first-person singular present indicative" for a verb. Slots are named in a language-specific fashion. For example, the slot "accusative plural" might have a name `accpl`, while "first-person singular present indicative" might be variously named `pres1s`, `pres_ind_1_sg`, etc. Each slot is filled with zero or more '''forms'''. * A '''form''' is a particular inflection of a slot for a particular term. Note that a given slot may (and often does) have more than one associated form; these different forms are termed '''variants'''. An example is {{m+|de|Bug||bow (of a ship)}}, which has two genitive singular forms ''Buges'' and ''Bugs''; two plural forms in all cases, e.g. nominative plural ''Buge'' and ''Büge''; and two dative singular forms ''Bug'' and rare/archaic ''Buge''. The form variants for a given slot are ordered, and generally should have the more common and/or preferred variants first, along with rare, archaic or obsolete variants last (if they are included at all). * Forms are described using '''form objects''', which are Lua objects taking the form `{form="``form_value``", translit="``manual_translit``", footnotes={"``footnote``", "``footnote``", ...}}`. (Additional '''metadata''' may be present in a form object, although the support for preserving such metadata when transformations are applied to form objects isn't yet complete.) ``form_value`` is a '''form value''' specifying the value of the form itself in the term's script. ``manual_translit`` specifies optional manual transliteration for the form, in case (a) the form value is in a different script; and (b) either the form's automatic transliteration is incorrect and needs to be overridden, or the language of the term has no automatic transliteration (e.g. in the case of Persian and Hebrew). ``footnote`` is a footnote to be attached to the form in question, and should be e.g. {"[archaic]"} or {"[only in the meaning 'to succeed (an officeholder)']"}, i.e. the string must be surrounded by brackets and should begin with a lowercase letter and not end in a period/full stop. When such footnotes are converted to actual footnotes in a table of inflected forms, the brackets will be removed, the first letter will be capitalized and a period/full stop will be added to the end. (However, when such footnotes are used as qualifiers in headword lines, only the brackets will be removed, with no capitalization or final period.) Note that only ``form_value`` is mandatory. * A list of zero or more form objects is termed a '''form object list''', or usually just a '''form list'''. Such lists are ordered and go into form tables (see below). * A '''form table''' is a Lua table (i.e. a dictionary) describing all the possible inflections of a given term. The keys in such a table are slots (strings) and the values are form lists. '''NOTE:''' All inflection code assumes and maintains the invariant that no two slots, and no two forms in a single slot, share the same form object (by reference, i.e. the Lua object describing a form object should never be shared in two places). This allows for safely side-effecting form objects in certain sorts of operations. This same invariant necessarily applies to the Lua list objects containing the form objects, but does '''NOT''' apply to metadata inside of form objects. In particular, a list of footnotes may well be shared among different form objects. This means it is '''NOT''' safe to side-effect such lists, and in fact no code in this module that manipulates footnote lists will ever side-effect such lists; they are treated as immutable. * Some functions, to save memory, accept and work with abbreviated forms of form objects and/or form lists. Specifically, an '''abbreviated form object''' is either a form object or a string, the latter corresponding to a form object whose form value is the string and all other properties are nil. Similarly, an '''abbreviated form list''' is either a single abbreviated form object or a list of such objects, i.e. any of a string, form object or list of strings and/or form objects. Functions that do not accept such abbreviated structures may be said to insist on being passed form objects in '''general form''', or form lists in '''general list form'''. * Each slot is associated with an '''accelerator tag set''', which is a list of inflection tags that are used when generating an accelerator entry for the forms in the slot (see [[WT:ACCEL]]). For example, the first singular present indicative of a verb might have slot name `pres_1sg` and corresponding accelerator tag set `1|s|pres|ind`. As shown, the accelerator tag set is a string consisting of inflection tags (as used in {{tl|inflection of}}) separated by `|`. Despite the terminology ''tag set'', the tags in a tag set are ordered, although the same tag should never occur twice. * Some inflected terms are '''multiword''', i.e. they consist of multiple '''words''', where each word is generally separated by spaces or sometimes hyphens. In such a term, some of the words inflect, while others remain fixed. Words that inflect are termed '''inflecting words''' (or more correctly '''inflecting parts''', since in some circumstances, parts of a word can inflect). The '''fixed text''' is all the parts of a multiword term that do not inflect. * The descriptor that describes how a given term inflects is called an '''inflection spec''', and consists of the lemma form of the term itself, annotated with an '''angle bracket spec''' after each inflecting word. As the name implies, an angle bracket spec is surrounded by angle brackets (`<...>`). A simple example is {{m+|de|Feder||feather}}, whose inflection spec looks like `Feder<f>`, where `f` specifies the feminine gender. In this case, although there are several properties that could be specified between angle brackets, all except the gender are optional and have been left out, indicating that defaults should be used. Another example is {{m+|de|Baske|Basque person}}, whose inflection spec looks like `Baske<m.weak>`, where `m` specifies the masculine gender and `weak` specifies the weak inflection. Note that individual components of an angle bracket spec like `m` and `weak` are termed '''indicators''' and are separated by periods/full stops. A slightly more complex example is {{m+|de|Zeitgeist||zeitgeist}}, whose inflection spec looks like `Zeitgeist<m,es:s,er>` and which specifies three things in a single '''compound indicator''': `m` (the masculine gender); `es:s` (the genitive singular, which can end in either ''-es'' or ''-s''); and `er` (the nomininative plural, which ends in ''-er''). * If there are several inflecting words in a term, each one will be followed by its own angle bracket spec. An example is {{m+|de|schwarzes Loch||black hole}}, whose inflection spec looks like `schwarzes<+> Loch<n,es:s,^er>`. Here, the adjective ''schwarzes'' (the nominative neuter singular of {{m|de|schwarz||black}}) is followed by the angle bracket spec `<+>` specifying that it inflects as an adjective, and the noun ''Loch'' has the angle bracket spec `<n,es:s,^er>`, indicating (similarly to the above example) that it is neuter, has a genitive singular in either ''-es'' or ''-s'', and has a nominative plural in ''-er'' with umlaut, hence ''Löcher'' (the `^` specifies that the form requires umlaut). * Sometimes a given term has multiple ways of inflecting that differ in ways that can't be specified using a single angle bracket spec. This is supported using '''alternants''', which are specified using double parentheses. (This is so that terms that themselves contain parentheses can be specified without interference.) An example is {{m+|uk|русин||Rusyn}}, which can be stressed either as ''ру́син'' (stress on the first syllable and following accent paradigm ''a'', hence genitive singular ''ру́сина'') or ''руси́н'' (stress on the second syllable and following accent paradigm ''b'', hence genitive singular ''русина́''; note how the stress moves onto the ending, in accordance with the accent paradigm). This is specified using `((ру́син<pr>,руси́н<b.pr>))`, i.e. each separate the alternants with a comma and surround them with double parentheses. (Here, `pr` means that the terms belong to the personal animacy class, and `b` specifies the accent paradigm; paradigm ''a'' is the default and hence is omitted.) * Note that occasionally, parts of a single space-delimited word can inflect separately. An example is {{m+|la|rōsmarīnus||rosemary}}, which is a compound of {{m+|la|rōs||dew}} and {{m|la|marīnus||marine, of the sea}}. In this compound, both parts of the compound can inflect separately; hence genitive singular ''rōrismarīnī'', accusative singular ''rōremmarīnum'', etc. Alternatively, only the second part inflects; hence genitive singular ''rōsmarīnī'', accusative singular ''rōsmarīnum'', etc. This is specified as `((rōs/rōr<3.M>marīnus<2>,rōsmarīnus<2>))`. Here, the term {{m|la|rōs}} by itself would have inflection spec `rōs/rōr<3.M>` (indicating that it is third declension masculine with a non-nominative-singular stem ''rōr-'') and the term {{m|la|marīnus}} would have inflection spec `<2>` (indicating that it is second declension; the masculine gender is inferred from the ''-us'' ending). When combined in a single inflection spec, the doubly-inflecting alternant is written `rōs/rōr<3.M>marīnus<2>`, with each inflecting part followed by its corresponding angle bracket spec, and the singly-inflecting alternant is written `rōsmarīnus<2>`. As this example shows, the two alternants need not correspond in how many inflecting parts there are. It should also be noted that fixed text can surround an alternant and it is even possible to supply multiple alternants in a single inflection spec (e.g. if the term has two words in it and each word requires an alternant to inflect). * The result of parsing a single angle bracket spec is stored into a '''word spec'''. The structure of a word spec is fairly arbitrary and is determined by the user-written `parse_indicator_spec` function, but always contains a form table under the `forms` key that is populated during inflection (see below). A parameter or local variable that holds a word spec is conventionally named `base` for historical reasons. Word specs are grouped together into a structure termed a '''multiword spec''', which describes one or more word specs along with the fixed text in between and around the inflected words. Multiword specs are in turn grouped into structures termed '''alternant specs''', indicating the distinct alternants and the words in each alternant. Finally, multiword specs and alternant specs are grouped into an '''alternant multiword spec''', which is the top-level object describing an inflection spec. Each of these different specs has a form table in it stored in the `forms` key that is populated during the inflection process and contains the form objects that specify the inflections of this part of the full multiword term. (It should be noted that the term '''spec''' is overloaded to mean two different things: the user-specified descriptor that specifies the lemma form of the term and associated inflection, and the associated internal Lua object that encapsulates all information derived from the descriptor, along with later-generated information on how to inflect the term(s) being described.) * Among these various "spec" structures, the two most important are the top-level alternant multiword spec and the bottom-level word spec or "base". You will rarely find it necessary to manipulate the intermediate structures or concern yourself with the details of their formation. * The term ''form'' is unfortunatately overloaded in various modules to mean several things. In particular, for historical reasons, the form value inside of a form object is stored using the key `form`; the form table inside of an alternant multiword spec, a word spec (or "base") and the intermediate structures is stored using the key `forms`; and the accelerator tag set is internally referred to in [[WT:ACCEL]] as a "form". To avoid confusion, the following conventions are followed in code in this module, and should be followed for code in invoking modules as well: *# Functions that accept form objects often name the relevant parameter `form` (if a single form object is required) or `forms` (if a list of form objects, aka form list, is required). *# Functions that accept abbreviated form objects should (but don't always) indicate this by naming the parameter `abform` (for a single abbreviated form object) or `abforms` (for an abbreviated form list). *# Functions that accept a form value (the native-script string portion of a form object, stored for historical reasons in the `.form` property) should '''not''' call such a parameter `form`, but instead use something that makes clear that a form value is required, such as `formval` or sometimes just `val`. *# Similarly, functions that accept a form table should '''not''' call such a parameter `forms` (although for historical reasons the form table in an alternant multiword spec is stored in the field `forms`). Instead, use `formtable` or `formtab`, or similar name that makes clear that the value is a form table (i.e. a map from slot to form list). ====Footnote handling==== Each form can have one or more attached footnotes. The form of a footnote as specified by the user and stored in form values is e.g. {"[archaic]"} or {"[only in the meaning 'to succeed (an officeholder)']"}, i.e. the string must be surrounded by brackets and should begin with a lowercase letter and not end in a period/full stop. When such footnotes are converted to actual footnotes in a table of inflected forms, the brackets will be removed, the first letter will be capitalized and a period/full stop will be added to the end. (However, when such footnotes are used as qualifiers in headword lines, only the brackets will be removed, with no capitalization or final period.) When merging two forms into one, such as when concatenating the form objects of two inflected words in a multiword term or deduplicating form objects sharing the same form value during `show_forms()`, the footnotes are generally combined as well. This means that if one form object has footnotes and the other doesn't, the resulting form object inherits the footnotes of the object that has them, and if both form objects have footnotes, the resulting form object gets all footnotes from both source form objects, with duplicates removed. However, when inserting a form into a form table slot that already has a form whose form value and translit are identical to the new form, the behavior is different. In under normal circumstances the footnotes of the new form are ''not'' incorporated into those of the existing form (if any), but are simply dropped. To understand why this makes sense, consider a term that has two possible forms of its lemma (e.g. two forms differing in stress or in vowel length), where the second form is archaic, rare, colloquial or the like, and has an attached footnote indicating this. An example of this is {{m+|ru|кожух||sheepskin coat; bullet shell}}, where the form ''кожу́х'' with accent pattern ''b'' is more common overall but the form ''ко́жух'' with accent pattern ''c(1)'' is more common among professionals. On first glance, this could be indicated using `((кожу́х&lt;b>,ко́жух<c(1).[professional usage only]>))`. But some forms of these two declensions are the same (in particular, the genitive, dative, instrumental and prepositional plural). If for these slots, the footnotes of the duplicate forms were combined (i.e. the footnotes of the second declension pattern were added to the already-existing form taken from the first declension pattern), these forms would wrongly be labeled as ''professional usage only''. For this reason, it makes more sense to drop the footnotes of the second form when deduplicating. The same sort of behavior makes sense when a single lemma can have two different declensions, the second of which requires a footnote and where some forms in the two declensions are shared. An example of this is {{m+|uk|окови́та||strong, high-quality liquor}}, which can be inflected adjectivally or (rarely) nominally. This would be indicated as `((окови́та<sg.+>,окови́та<sg.[rare]>))` where the `+` indicates adjectival declension and the `sg` indicates that this term only exists in the singular. Here, the two declensions differ in the genitive, dative/locative and vocative (respectively, adjectival ''окови́тої'', ''окови́тій'', ''окови́та'' vs. nominal ''окови́ти'', ''окови́ті'', ''окови́то'') but are the same in the accusative (''окови́ту'') and instrumental (''окови́тою''). Again, dropping the footnotes of the second form when deduplicating is correct and including them would be wrong. This behavior can be changed by attaching a '''footnote modifier''' to the footnote associated the second form. A footnote modifier is a symbol attached to the beginning of a footnote, directly following the opening bracket. The following modifiers are currently recognized: * `!` or `+`: If placed on a footnote of the second form, combine that footnote with those of the first form (if any) rather than dropping it. * `*`: If placed on a footnote of the first form, drop that footnote when merging a second form with any footnotes. An example where the `*` modifier makes sense is a modification of the above example with {{m+|ru|кожух}}. If we notated it as `((кожу́х<b.[more common among laymen]>,ко́жух<c(1).[more common among professionals]>))`, the shared forms would wrongly have the footnote ''more common among laymen'' when in fact they are the only possible forms. If instead we used `((кожу́х<b.[*more common among laymen]>,ко́жух<c(1).[more common among professionals]>))`, the shared forms would correctly have no footnote. Finally, be aware of '''old-style footnote symbols'''. For compatibility reasons, some inflection implementations support a system whereby footnote symbols (consisting of numbers; certain ASCII symbols such as `*`, `~`, `@`, `#`, `+`, etc.; and a large number of Unicode symbols) are directly attached to form values and the footnotes themselves specified manually using the `footnotes` property passed to `show_forms()`. This is allowed only when `allow_footnote_symbols` is set and is highly deprecated. All uses of such symbols should be converted to standard footnotes and the support for such symbols removed. ]==] local function extract_footnote_modifiers(footnote) local footnote_mods, footnote_without_mods = rmatch(footnote, "^%[([!*+]?)(.*)%]$") if not footnote_mods then error("Saw footnote '" .. footnote .. "' not surrounded by brackets") end return footnote_mods, footnote_without_mods end --[==[ Insert a form object (see above) into a list of such objects. If the form is already present (i.e. both the form value and translit, if any, match), the footnotes of the existing and new form might be combined (specifically, footnotes in the new form beginning with `!` will be combined). ]==] function export.insert_form_into_list(list, form) -- Don't do anything if the form object or the form inside it is nil. This simplifies -- form insertion in the presence of inflection generating functions that may return nil, -- such as generate_noun_vocative() and generate_noun_count_form(). if not form or not form.form then return end for _, listform in ipairs(list) do if listform.form == form.form and listform.translit == form.translit then -- Form already present; maybe combine footnotes. if form.footnotes then -- Check to see if there are existing footnotes with *; if so, remove them. if listform.footnotes then local any_footnotes_with_asterisk = false for _, footnote in ipairs(listform.footnotes) do local footnote_mods, _ = extract_footnote_modifiers(footnote) if rfind(footnote_mods, "%*") then any_footnotes_with_asterisk = true break end end if any_footnotes_with_asterisk then local filtered_footnotes = {} for _, footnote in ipairs(listform.footnotes) do local footnote_mods, _ = extract_footnote_modifiers(footnote) if not rfind(footnote_mods, "%*") then table.insert(filtered_footnotes, footnote) end end if #filtered_footnotes > 0 then listform.footnotes = filtered_footnotes else listform.footnotes = nil end end end -- The behavior here has changed; track cases where the old behavior might -- be needed by adding ! to the footnote. track("combining-footnotes") local any_footnotes_with_bang = false for _, footnote in ipairs(form.footnotes) do local footnote_mods, _ = extract_footnote_modifiers(footnote) if rfind(footnote_mods, "[!+]") then any_footnotes_with_bang = true break end end if any_footnotes_with_bang then if not listform.footnotes then listform.footnotes = {} else listform.footnotes = m_table.shallowCopy(listform.footnotes) end for _, footnote in ipairs(form.footnotes) do local already_seen = false local footnote_mods, footnote_without_mods = extract_footnote_modifiers(footnote) if rfind(footnote_nods, "[!+]") then for _, existing_footnote in ipairs(listform.footnotes) do local existing_footnote_mods, existing_footnote_without_mods = extract_footnote_modifiers(existing_footnote) if existing_footnote_without_mods == footnote_without_mods then already_seen = true break end end if not already_seen then table.insert(listform.footnotes, footnote) end end end end end return end end -- Form not found. table.insert(list, form) end --[==[ Insert a form object (see above) into the given slot in the given form table. ``form`` can be {nil}, in which case nothing happens. ]==] function export.insert_form(formtable, slot, form) -- Don't do anything if the form object or the form inside it is nil. This simplifies -- form insertion in the presence of inflection generating functions that may return nil, -- such as generate_noun_vocative() and generate_noun_count_form(). if not form or not form.form then return end if not formtable[slot] then formtable[slot] = {} end export.insert_form_into_list(formtable[slot], form) end --[==[ Insert a list of form objects (see above) into the given slot in the given form table. ``forms`` can be {nil}, in which case nothing happens. ]==] function export.insert_forms(formtable, slot, forms) if not forms then return end for _, form in ipairs(forms) do export.insert_form(formtable, slot, form) end end --[==[ Identity mapping function. ]==] function export.identity(formval, translit) return formval, translit end local function form_value_transliterable(formval) return formval ~= "?" and formval ~= "—" end local function call_map_function_str(str, fun) if str == "?" then return "?" end local newformval, newtranslit = fun(str) if newtranslit then return {form=newformval, translit=newtranslit} else return newformval end end -- FIXME: This doesn't correctly handle metadata. local function call_map_function_obj(form, fun) if form.form == "?" then return {form = "?", footnotes = form.footnotes} end local newformval, newtranslit = fun(form.form, form.translit) return {form = newformval, translit = newtranslit, footnotes = form.footnotes} end --[==[ Map a function over the form values in ``forms`` (a list of form objects in "general list form; see above). If an input form value is {"?"}, it is preserved on output and the function is not called. Otherwise, the function is called with two arguments, the original form and manual translit; if manual translit isn't relevant, it's fine to declare the function with only one argument. The return value is either a single value (the new form) or two values (the new form and new manual translit). The footnotes (if any) from the input form objects are preserved on output. Uses `insert_form_into_list()` to insert the resulting form objects into the returned list in case two different forms map to the same thing. FIXME: Expand this to correctly handle metadata, or create a variant that correctly handles metadata. ]==] function export.map_forms(forms, fun) if not forms then return nil end local retval = {} for _, form in ipairs(forms) do export.insert_form_into_list(retval, call_map_function_obj(form, fun)) end return retval end --[==[ Map a list-returning function over the form values in ``forms`` (a list of form objects in "general list form"; see above). If an input form value is {"?"}, it is preserved on output and the function is not called. Otherwise, the function is called with two arguments, the original form and manual translit; if manual translit isn't relevant, it's fine to declare the function with only one argument. The return value of the function can be {nil} or an abbreviated form list (i.e. anything that is convertible into a general list form, such as a single form value, a list of form values, a form object or a list of form objects). For each form object in the return value, the footnotes of that form object (if any) are combined with any footnotes from the input form object, and the result inserted into the returned list using `insert_form_into_list()` in case two different forms map to the same thing. FIXME: Expand this to correctly handle metadata, or create a variant that correctly handles metadata. ]==] function export.flatmap_forms(forms, fun) if not forms then return nil end local retval = {} for _, form in ipairs(forms) do local funret = form.form == "?" and {"?"} or fun(form.form, form.translit) if funret then funret = export.convert_to_general_list_form(funret) for _, fr in ipairs(funret) do local newform = { form = fr.form, translit = fr.translit, footnotes = export.combine_footnotes(form.footnotes, fr.footnotes) } export.insert_form_into_list(retval, newform) end end end return retval end --[==[ Map a function over the form values in ``abforms`` (an abbreviated form list). If the input form value is {"?"}, it is preserved on output and the function is not called. If ``first_only`` is given and ``abforms`` is a list, only map over the first element. Return value is of the same form as ``abforms``, unless ``abforms`` is a string and the function returns both form value and manual translit (in which case the return value is a form object). The function is called with two arguments, the original form value and manual translit; if manual translit isn't relevant, it's fine to declare the function with only one argument. The return value is either a single value (the new form value) or two values (the new form value and new manual translit). The footnotes (if any) from the input form objects are preserved on output. FIXME: This function is used only in [[Module:bg-verb]] and should be moved into that module. ]==] function export.map_form_or_forms(abforms, fun, first_only) if not abforms then return nil elseif type(abforms) == "string" then return call_map_function_str(abforms, fun) elseif abforms.form then return call_map_function_obj(abforms, fun) else local retval = {} for i, abform in ipairs(abforms) do if first_only then return export.map_form_or_forms(abform, fun) end table.insert(retval, export.map_form_or_forms(abform, fun)) end return retval end end --[==[ Combine two sets of footnotes. If either is {nil}, just return the other, and if both are {nil}, return {nil}. ]==] function export.combine_footnotes(notes1, notes2) if not notes1 and not notes2 then return nil end if not notes1 then return notes2 end if not notes2 then return notes1 end local combined = m_table.shallowCopy(notes1) for _, note in ipairs(notes2) do m_table.insertIfNot(combined, note) end return combined end --[==[ Expand a given footnote (as specified by the user, including the surrounding brackets) into the form to be inserted into the final generated table. If ``no_parse_refs`` is not given and the footnote is a reference (of the form {"[ref:...]"}), parse and return the specified reference(s). Two values are returned, `footnote_string` (the expanded footnote, or nil if the second value is present) and `references` (a list of objects of the form `{text = ``text``, name = ``name``, group = ``group``}` if the footnote is a reference and ``no_parse_refs`` is not given, otherwise {nil}). Unless ``return_raw`` is given, the returned footnote string is capitalized and has a final period added. ]==] function export.expand_footnote_or_references(note, return_raw, no_parse_refs) local _, notetext = extract_footnote_modifiers(note) if not no_parse_refs and notetext:find("^ref:") then -- a reference notetext = rsub(notetext, "^ref:", "") local parsed_refs = require("Module:references").parse_references(notetext) for i, ref in ipairs(parsed_refs) do if type(ref) == "string" then parsed_refs[i] = {text = ref} end end return nil, parsed_refs end if footnote_abbrevs[notetext] then notetext = footnote_abbrevs[notetext] track("footnote-whole-abbrev") else local split_notes = split(notetext, "<(.-)>") for i, split_note in ipairs(split_notes) do if i % 2 == 0 then split_notes[i] = footnote_abbrevs[split_note] track("footnote-angle-bracket-abbrev") if not split_notes[i] then -- Don't error for now, because HTML might be in the footnote. -- Instead we should switch the syntax here to e.g. <<a>> to avoid -- conflicting with HTML. split_notes[i] = "<" .. split_note .. ">" track("footnote-unrecognized-angle-bracket-abbrev") --error("Unrecognized footnote abbrev: <" .. split_note .. ">") else track("footnote-recognized-angle-bracket-abbrev") end end end notetext = table.concat(split_notes) end return return_raw and notetext or ucfirst(notetext) .. "." end --[==[ Convert a list of foonotes to qualifiers and references for use in [[Module:headword]] or similar. Returns two values, a list of qualifiers (possibly {nil}) and a list of reference structures (possibly {nil}), following the structure defined in [[Module:references]]). ]==] function export.convert_footnotes_to_qualifiers_and_references(footnotes) if not footnotes then return nil end local quals, refs for _, qualifier in ipairs(footnotes) do local this_footnote, this_refs = export.expand_footnote_or_references(qualifier, "return raw") if this_refs then if not refs then refs = this_refs else for _, ref in ipairs(this_refs) do table.insert(refs, ref) end end else if not quals then quals = {this_footnote} else table.insert(quals, this_footnote) end end end return quals, refs end --[==[ Combine an abbreviated form object (either a string or a table) with additional footnotes, possibly replacing the form value and/or translit in the process. Normally called in one of two ways: (1) `combine_form_and_footnotes(``form_obj``, ``addl_footnotes``, ``new_form``, ``new_translit``)` where ``form_obj`` is an existing abbreviated form object; ``addl_footnotes`` is either {nil}, a single string (a footnote) or a list of footnotes; ``new_formval`` is either {nil} or the new form value to substitute; and ``new_translit`` is either {nil} or the new translit string to substitute. (2) `combine_form_and_footnotes(``form_value``, ``footnotes``)`, where ``form_value`` is a form value (a string) and ``footnotes`` is either {nil}, a single string (a footnote) or a list of footnotes. In either case, a form object is returned, preserving as many properties as possible from any existing form object in ``abform``. Do the minimal amount of work; e.g. if ``abform`` is a form object and ``addl_footnotes``, ``new_formval`` and ``new_translit`` are all {nil}, the same object as passed in is returned. Under no circumstances is the existing form object side-effected. '''FIXME:''' This does not correctly preserve metadata. ]==] function export.combine_form_and_footnotes(abform, addl_footnotes, new_formval, new_translit) if type(addl_footnotes) == "string" then addl_footnotes = {addl_footnotes} end if not addl_footnotes and not new_formval and not new_translit then return abform end if type(abform) == "string" then new_formval = new_formval or abform return {form = new_formval, translit = new_translit, footnotes = addl_footnotes} end abform = m_table.shallowCopy(abform) if new_formval then abform.form = new_formval end if new_translit then abform.translit = new_translit end if addl_footnotes then abform.footnotes = export.combine_footnotes(abform.footnotes, addl_footnotes) end return abform end --[==[ Convert an abbreviated form list (either a string, form object, or list of either) into general list form. If ``footnotes`` is supplied, then for each form in the form list, combine the form's footnotes with ``footnotes``. This function does not side-effect any of the objects passed into ``abforms``, but will return ``abforms`` unchanged if already in general list form and ``footnotes`` is {nil}. '''FIXME:''' This does not correctly preserve metadata. ]==] function export.convert_to_general_list_form(abforms, footnotes) if type(footnotes) == "string" then footnotes = {footnotes} end if type(abforms) == "string" then return {{form = abforms, footnotes = footnotes}} elseif abforms.form then return {export.combine_form_and_footnotes(abforms, footnotes)} elseif not footnotes then -- Check if already in general list form and return directly if so. local must_convert = false for _, form in ipairs(abforms) do if type(form) == "string" then must_convert = true break end end if not must_convert then return abforms end end local retval = {} for _, form in ipairs(abforms) do if type(form) == "string" then table.insert(retval, {form = form, footnotes = footnotes}) else table.insert(retval, export.combine_form_and_footnotes(form, footnotes)) end end return retval end local function is_table_of_strings(forms) for k, v in pairs(forms) do if type(k) ~= "number" or type(v) ~= "string" then return false end end return true end local function lang_or_func_transliterate(func, lang, text) local retval if func then retval = func(text) else retval = (lang:transliterate(text)) end -- FIXME! Hack to work around bug in ...:transliterate(). Remove me as soon as this bug is fixed. if not retval and (text == " " or text == "-" or text == "?") then retval = text end if not retval then error(("Unable to transliterate text '%s'"):format(text)) end return retval end --[==[ Combine ``stems`` and ``endings`` and store into slot ``slot`` of form table ``formtable``. Either of ``stems`` and ``endings`` can be {nil} or an abbreviated form list. The combination of a given stem and ending happens using ``combine_stem_ending``, which takes two parameters (stem and ending, each a string) and returns one value (a string). If manual transliteration is present in either ``stems`` or ``endings``, ``lang`` (a language object or a function of one argument to transliterate a string) along with ``combine_stem_ending_tr`` (a function for combining manual transliterations that works much like ``combine_stem_ending``) must be given. ``footnotes``, if specified, is a list of additional footnotes to attach to the resulting inflections (stem+ending combinations). The resulting inflections are inserted into the form table using `insert_form()`, in case of duplication. ]==] function export.add_forms(formtable, slot, stems, endings, combine_stem_ending, lang, combine_stem_ending_tr, footnotes) if stems == nil or endings == nil then return end local function combine(stem, ending) if stem == "?" or ending == "?" then return "?" end return combine_stem_ending(stem, ending) end local function transliterate(text) return lang_or_func_transliterate(is_callable(lang) and lang or nil, lang, text) end if type(stems) == "string" and type(endings) == "string" then export.insert_form(formtable, slot, {form = combine(stems, endings), footnotes = footnotes}) elseif type(stems) == "string" and is_table_of_strings(endings) then for _, ending in ipairs(endings) do export.insert_form(formtable, slot, {form = combine(stems, ending), footnotes = footnotes}) end else stems = export.convert_to_general_list_form(stems) endings = export.convert_to_general_list_form(endings, footnotes) for _, stem in ipairs(stems) do for _, ending in ipairs(endings) do local footnotes = nil if stem.footnotes and ending.footnotes then footnotes = m_table.shallowCopy(stem.footnotes) for _, footnote in ipairs(ending.footnotes) do m_table.insertIfNot(footnotes, footnote) end elseif stem.footnotes then footnotes = stem.footnotes elseif ending.footnotes then footnotes = ending.footnotes end local new_form = combine(stem.form, ending.form) local new_translit if new_form ~= "?" and (stem.translit or ending.translit) then if not lang or not combine_stem_ending_tr then error("Internal error: With manual translit, 'lang' and 'combine_stem_ending_tr' must be passed to 'add_forms'") end local stem_tr = stem.translit or transliterate(m_links.remove_links(stem.form)) local ending_tr = ending.translit or transliterate(m_links.remove_links(ending.form)) new_translit = combine_stem_ending_tr(stem_tr, ending_tr) end export.insert_form(formtable, slot, {form = new_form, translit = new_translit, footnotes = footnotes}) end end end end --[==[ Combine any number of form components and store into slot ``slot`` of form table ``formtable``. ``components`` is a list of abbreviated form lists which should be concatenated similarly to how `add_forms()` does it, and stored in ``slot`` along with any footnotes in ``footnotes``. More specifically: # If there are no components, nothing happens. # If there is one component, it is converted to general list form and `insert_forms()` called. # If there are two components, they are treated as stems and endings respectively and `add_forms()` is called. # If there are three or more components, they are concatenated left-to-right in the manner of a `reduce()` operation: the first two components are combined using `add_forms()` and stored into a temporary table, then the next component is combined with the result of the previous operation, etc. In the last combination, footnotes in `footnotes` are combined in, and the result stored into `formtable`. This should generally be used when you are likely to have three or more components, as in [[Module:ar-verb]] (prefixes, stems and endings) and [[Module:de-verb]] (which in some situations has five components combined together). ``combine_stem_ending``, ``lang``, ``combine_stem_ending_tr`` and ``footnotes`` are as in `add_forms()`. ]==] function export.add_multiple_forms(formtable, slot, components, combine_stem_ending, lang, combine_stem_ending_tr, footnotes) if #components == 0 then return elseif #components == 1 then local forms = export.convert_to_general_list_form(components[1], footnotes) export.insert_forms(formtable, slot, forms) elseif #components == 2 then local stems = components[1] local endings = components[2] export.add_forms(formtable, slot, stems, endings, combine_stem_ending, lang, combine_stem_ending_tr, footnotes) else local prev = components[1] for i=2, #components do local temptable = {} export.add_forms(temptable, slot, prev, components[i], combine_stem_ending, lang, combine_stem_ending_tr, i == #components and footnotes or nil) prev = temptable[slot] end export.insert_forms(formtable, slot, prev) end end local function iterate_slot_list_or_table(props, do_slot) if props.slot_list then for _, slot_and_accel_tag_set in ipairs(props.slot_list) do local slot, accel_tag_set = unpack(slot_and_accel_tag_set) do_slot(slot, accel_tag_set) end else for slot, accel_tag_set in pairs(props.slot_table) do do_slot(slot, accel_tag_set) end end end function export.default_split_bracketed_runs_into_words(bracketed_runs, data) -- If the text begins with a hyphen, include the hyphen in the set of allowed characters -- for an inflected segment. This way, e.g. conjugating "-ir" is treated as a regular -- -ir verb rather than a hyphen + irregular [[ir]]. local is_suffix = (not data or data.text_index == 1) and rfind(bracketed_runs[1], "^%-") local split_pattern = is_suffix and " " or "[ %-]" return put.split_alternating_runs(bracketed_runs, split_pattern, "preserve splitchar") end local function props_transliterate(props, text) return lang_or_func_transliterate(props.transliterate, props.lang, text) end local function parse_before_or_post_text(data) local props, text, text_index, segments, lemma_is_last = data.props, data.text, data.text_index, data.segments, data.lemma_is_last -- Call parse_balanced_segment_run() to keep multiword links together. local bracketed_runs = put.parse_balanced_segment_run(text, "[", "]") -- Split normally on space or hyphen (but customizable). Use preserve_splitchar so we know whether the separator was -- a space or hyphen. local space_separated_groups if props.split_bracketed_runs_into_words then space_separated_groups = props.split_bracketed_runs_into_words(bracketed_runs) end if not space_separated_groups then space_separated_groups = export.default_split_bracketed_runs_into_words(bracketed_runs, data) end local parsed_components = {} local parsed_components_translit = {} local saw_manual_translit = false local lemma for j, space_separated_group in ipairs(space_separated_groups) do local component = table.concat(space_separated_group) if lemma_is_last and j == #space_separated_groups then lemma = component if lemma == "" and not props.allow_blank_lemma then error("Word is blank: '" .. table.concat(segments) .. "'") end elseif rfind(component, "//") then -- Manual translit or respelling specified. if not props.lang then error("Manual translit not allowed for this language; if this is incorrect, 'props.lang' must be set internally") end saw_manual_translit = true local split = split(component, "//", "plain") if #split ~= 2 then error("Term with translit or respelling should have only one // in it: " .. component) end local translit component, translit = unpack(split) if props.transliterate_respelling then translit = props.transliterate_respelling(translit) end table.insert(parsed_components, component) table.insert(parsed_components_translit, translit) else table.insert(parsed_components, component) table.insert(parsed_components_translit, false) -- signal that it may need later transliteration end end if saw_manual_translit then for j, parsed_component in ipairs(parsed_components) do if not parsed_components_translit[j] then parsed_components_translit[j] = props_transliterate(props, m_links.remove_links(parsed_component)) end end end text = table.concat(parsed_components) local translit if saw_manual_translit then translit = table.concat(parsed_components_translit) end return text, translit, lemma end --[=[ Parse a segmented multiword spec such as "[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>" (in Ukrainian). "Segmented" here means it is broken up on <...> segments using parse_balanced_segment_run(text, "<", ">"), e.g. the above text would be passed in as {"[[медичний|меди́чна]]", "<+>", " [[сестра́]]", "<*,*#.pr>", ""}. The return value is a table of the form { word_specs = {``word_spec``, ``word_spec``, ...}, post_text = "``text-at-end``", post_text_no_links = "``text-at-end-no-links``", post_text_translit = "``manual-translit-of-text-at-end``" or nil (if no manual translit or respelling was specified in the post-text) } where ``word_spec`` describes an individual inflected word and "``text-at-end``" is any raw text that may occur after all inflected words. Individual words or linked text (including multiword text) may be given manual transliteration or respelling in languages that support this using ``text``//``translit`` or ``text``//``respelling``. Each ``word_spec`` is of the form returned by parse_indicator_spec(): { lemma = "``lemma``", before_text = "``text-before-word``", before_text_no_links = "``text-before-word-no-links``", before_text_translit = "``manual-translit-of-text-before-word``" or nil (if no manual translit or respelling was specified in the before-text) -- Fields as described in parse_indicator_spec() ... } For example, the return value for "[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>" is { word_specs = { { lemma = "[[медичний|меди́чна]]", overrides = {}, adj = true, before_text = "", before_text_no_links = "", forms = {}, }, { lemma = "[[сестра́]]", overrides = {}, stresses = { { reducible = true, genpl_reversed = false, }, { reducible = true, genpl_reversed = true, }, }, animacy = "pr", before_text = " ", before_text_no_links = " ", forms = {}, }, }, post_text = "", post_text_no_links = "", } ]=] local function parse_multiword_spec(segments, props, disable_allow_default_indicator) local multiword_spec = { word_specs = {} } if not disable_allow_default_indicator then if #segments == 1 then if props.allow_default_indicator then table.insert(segments, "<>") table.insert(segments, "") elseif props.angle_brackets_omittable then segments[1] = "<" .. segments[1] .. ">" table.insert(segments, 1, "") table.insert(segments, "") end end end -- Loop over every other segment. The even-numbered segments are angle-bracket specs while -- the odd-numbered segments are the text between them. for i = 2, #segments - 1, 2 do local before_text, before_text_translit, lemma = parse_before_or_post_text { props = props, text = segments[i - 1], text_index = i - 1, segments = segments, lemma_is_last = true } local base = props.parse_indicator_spec(segments[i], lemma) base.before_text = before_text base.before_text_no_links = m_links.remove_links(base.before_text) base.before_text_translit = before_text_translit base.lemma = base.lemma or lemma table.insert(multiword_spec.word_specs, base) end multiword_spec.post_text, multiword_spec.post_text_translit = parse_before_or_post_text { props = props, text = segments[#segments], text_index = #segments, segments = segments, lemma_is_last = false } multiword_spec.post_text_no_links = m_links.remove_links(multiword_spec.post_text) return multiword_spec end --[=[ Parse an alternant, e.g. "((родо́вий,родови́й))" or "((ру́син<pr>,руси́н<b.pr>))" (both in Ukrainian). The return value is a table of the form { alternants = {``multiword_spec``, ``multiword_spec``, ...} } where ``multiword_spec`` describes a given alternant and is as returned by parse_multiword_spec(). ]=] local function parse_alternant(alternant, props) local parsed_alternants = {} local alternant_text = rmatch(alternant, "^%(%((.*)%)%)$") local segments = put.parse_balanced_segment_run(alternant_text, "<", ">") local comma_separated_groups = put.split_alternating_runs(segments, "%s*,%s*") local alternant_spec = {alternants = {}} for _, comma_separated_group in ipairs(comma_separated_groups) do table.insert(alternant_spec.alternants, parse_multiword_spec(comma_separated_group, props)) end return alternant_spec end --[==[ Top-level parsing function. Parse text describing one or more inflected words. `text` is the inflected text to parse, which generally has `<...>` specs following words to be inflected, and may have alternants indicated using double parens. Examples: * {"[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>"} (Ukrainian, for {{m|uk|меди́чна сестра́||nurse|lit=medical sister}}); * {"((ру́син<pr>,руси́н<b.pr>))"} (Ukrainian, for {{m|uk|русин||Rusyn}}, with two possible stress patterns); * {"पंचायती//पंचाय*ती राज<M>"} (Hindi, for {{m|hi|पंचायती राज||village council}}, with phonetic respelling in the before-text component); * {"((<M>,<M.plstem:फ़तूह.dirpl:फ़तूह>))"} (Hindi, for {{m|hi|फ़तह||win, victory}} when used on that page, where the lemma is omitted and taken from the pagename); * {""} (for any number of Hindi adjectives, where the lemma is omitted and taken from the pagename, and the angle bracket spec <> is assumed); * {"काला<+>धन<M>"} (Hindi, for {{m|hi|कालाधन||black money}}, showing that closed compounds where each part is declined can be correctly handled). `props` is an object specifying properties used during parsing, as follows: ```{ parse_indicator_spec = __function__(``angle_bracket_spec``, ``lemma``) `''(required)''`, lang = __lang object__, transliterate_respelling = __function__(``respelling_or_translit``) `''(optional)''`, split_bracketed_runs_into_words = __function__(``bracket_split_runs``) `''(optional)''`, allow_default_indicator = __boolean__, angle_brackets_omittable = __boolean__, allow_blank_lemma = __boolean__, }``` `parse_indicator_spec` is a required function that takes two arguments, a string surrounded by angle brackets and the lemma, and should return an arbitrary object containing properties describing the indicators inside of the angle brackets). This object is often called a '''base''' and given the argument name `base` in inflection code. `lang` is the language object for the language in question; only needed if manual translit or respelling may be present using `//`. `transliterate_respelling` is a function that is only needed if respelling is allowed in place of manual translit after `//`. It takes one argument, the respelling or translit, and should return the transliteration of any respelling but return any translit unchanged. `split_bracketed_runs_into_words` is an optional function to split the passed-in text into words. It is used, for example, to determine what text constitutes a word when followed by an angle-bracket spec, i.e. what the lemma to be inflected is vs. surrounding fixed text. It takes one argument, the result of splitting the original text on brackets, and should return alternating runs of words and split characters, or nil to apply the default algorithm. Specifically, the value passed in is the result of calling `parse_balanced_segment_run(``text``, "[", "]")` from [[Module:parse utilities]] on the original text, and the default version of this function calls `split_alternating_runs(``bracketed_runs``, ``pattern``, "preserve splitchar")`, where ``bracketed_runs`` is the value passed in and ``pattern`` splits on either spaces or hyphens (unless the text begins with a hyphen, in which case splitting is only on spaces, so that suffixes can be inflected). `allow_default_indicator` should be {true} if an empty indicator in angle brackets `<>` can be omitted and should be automatically added at the end of the multiword text (if no alternants) or at the end of each alternant (if alternants present). `angle_brackets_omittable` should be {true} if angle brackets can be omitted around a non-empty indicator in the presence of a blank lemma. In this case, if the combined indicator spec has no angle brackets, they will be added around the indicator (or around all indicators, if alternants are present). This only makes sense when `allow_blank_lemma` is specified. `allow_blank_lemma` should be {true} of if a blank lemma is allowed; in such a case, the calling function should substitute a default lemma, typically taken from the pagename. The return value is a table referred to as an '''alternant multiword spec''', and is of the form ```{ alternant_or_word_specs = {``alternant_or_word_spec``, ``alternant_or_word_spec``, ...}, post_text = "``text_at_end``", post_text_no_links = "``text_at_end_no_links``", post_text_translit = "``translit_of_text_at_end``" `(or nil)`, }``` where `alternant_or_word_spec` is either an '''alternant spec''' as returned by `parse_alternant()` or a '''multiword spec''' as described in the comment above `parse_multiword_spec()`. An alternant spec looks as follows: ```{ alternants = {``multiword_spec``, ``multiword_spec``, ...}, before_text = "``text_before_alternant``", before_text_no_links = "``text_before_alternant``", before_text_translit = "``translit_of_text_before_alternant``" `(or nil)`, }``` i.e. it is like what is returned by `parse_alternant()` but has extra `before_text` and `before_text_no_links` fields. ]==] function export.parse_inflected_text(text, props) if props.angle_brackets_omittable and not props.allow_blank_lemma then error("If 'angle_brackets_omittable' is specified, so should 'allow_blank_lemma'") end local alternant_multiword_spec = {alternant_or_word_specs = {}} local alternant_segments = split(text, "(%(%(.-%)%))") local last_post_text, last_post_text_no_links, last_post_text_translit for i = 1, #alternant_segments do if i % 2 == 1 then local segments = put.parse_balanced_segment_run(alternant_segments[i], "<", ">") -- Disable allow_default_indicator if alternants are present and we're processing -- the non-alternant text. Otherwise we will try to treat the non-alternant text -- surrounding the alternants as an inflected word rather than as raw text. local multiword_spec = parse_multiword_spec(segments, props, #alternant_segments ~= 1) for _, word_spec in ipairs(multiword_spec.word_specs) do table.insert(alternant_multiword_spec.alternant_or_word_specs, word_spec) end last_post_text = multiword_spec.post_text last_post_text_no_links = multiword_spec.post_text_no_links last_post_text_translit = multiword_spec.post_text_translit else local alternant_spec = parse_alternant(alternant_segments[i], props) alternant_spec.before_text = last_post_text alternant_spec.before_text_no_links = last_post_text_no_links alternant_spec.before_text_translit = last_post_text_translit table.insert(alternant_multiword_spec.alternant_or_word_specs, alternant_spec) end end alternant_multiword_spec.post_text = last_post_text alternant_multiword_spec.post_text_no_links = last_post_text_no_links alternant_multiword_spec.post_text_translit = last_post_text_translit -- Save boolean properties from `props`. We need at least `allow_default_indicator` when implementing -- `reconstruct_original_spec()`. alternant_multiword_spec.allow_default_indicator = props.allow_default_indicator alternant_multiword_spec.angle_brackets_omittable = props.angle_brackets_omittable alternant_multiword_spec.allow_blank_lemma = props.allow_blank_lemma return alternant_multiword_spec end -- Inflect alternants in ``alternant_spec`` (an object as returned by parse_alternant()). -- This sets the form values in ```alternant_spec``.forms` for all slots. -- (If a given slot has no values, it will not be present in ```alternant_spec``.forms`). local function inflect_alternants(alternant_spec, props) alternant_spec.forms = {} for _, multiword_spec in ipairs(alternant_spec.alternants) do export.inflect_multiword_or_alternant_multiword_spec(multiword_spec, props) iterate_slot_list_or_table(props, function(slot) if not props.skip_slot or not props.skip_slot(slot) then export.insert_forms(alternant_spec.forms, slot, multiword_spec.forms[slot]) end end) end end --[=[ Subfunction of `inflect_multiword_or_alternant_multiword_spec()`. This is used in building up the inflections of multiword expressions. The basic purpose of this function is to append a set of forms representing the inflections of a given inflected term in a given slot onto the existing forms for that slot. Given a multiword expression potentially consisting of several inflected terms along with fixed text in between, we work iteratively from left to right, adding the new forms onto the existing ones. Normally, all combinations of new and existing forms are created, meaning if there are M existing forms and N new ones, we will end up with M*N forms. However, some of these combinations can be rejected using the variant mechanism (see the description of get_variants below). Specifically, `formtable` is a table of per-slot forms, where the key is a slot and the value is a list of form objects (objects of the form {form=``form``, translit=``manual_translit``, footnotes=``footnotes``}). `slot` is the slot in question. `forms` specifies the forms to be appended onto the existing forms, and is likewise a list of form objects. `props` is the same as in `inflect_multiword_or_alternant_multiword_spec()`. `before_text` is the fixed text that goes before the forms to be added. `before_text_no_links` is the same as `before_text` but with any links (i.e. hyperlinks of the form [[``term``]] or [[``term``|``display``]]) converted into raw terms using remove_links() in [[Module:links]], and `before_text_translit` is optional manual translit of `before_text_no_links`. Note that the value "?" in a form is "infectious" in that if either the existing or new form has the value "?", the resulting combination will also be "?". This allows "?" to be used to mean "unknown". ]=] local function append_forms(props, formtable, slot, forms, before_text, before_text_no_links, before_text_translit) if not forms then return end local old_forms = formtable[slot] or {{form = ""}} local ret_forms = {} for _, old_form in ipairs(old_forms) do for _, form in ipairs(forms) do local old_form_vars = props.get_variants and props.get_variants(old_form.form) or "" local form_vars = props.get_variants and props.get_variants(form.form) or "" if old_form_vars ~= "" and form_vars ~= "" and old_form_vars ~= form_vars then -- Reject combination due to non-matching variant codes. else local new_formval local new_translit if old_form.form == "?" or form.from == "?" then new_formval = "?" else new_formval = old_form.form .. before_text .. form.form if old_form.translit or before_text_translit or form.translit then if not props.lang then error("Internal error: If manual translit is given, 'props.lang' must be set") end if not before_text_translit then before_text_translit = props_transliterate(props, before_text_no_links) or "" end local old_translit = old_form.translit or props_transliterate(props, m_links.remove_links(old_form.form)) or "" local translit = form.translit or props_transliterate(props, m_links.remove_links(form.form)) or "" new_translit = old_translit .. before_text_translit .. translit end end local new_formobj local new_footnotes = export.combine_footnotes(old_form.footnotes, form.footnotes) if new_formval == form.form and new_translit == form.translit then -- Automatically preserve metadata when possible. new_formobj = m_table.shallowCopy(form) new_formobj.footnotes = new_footnotes else local new_footnotes = export.combine_footnotes(old_form.footnotes, form.footnotes) new_formobj = {form=new_formval, translit=new_translit, footnotes=new_footnotes} if props.combine_metadata then props.combine_metadata { slot = slot, dest_form = new_formobj, form1 = old_form, form2 = form, between_text = before_text, between_text_no_links = before_text_no_links, between_text_translit = before_text_translit, } end end table.insert(ret_forms, new_formobj) end end end formtable[slot] = ret_forms end --[==[ Top-level inflection function. Create the inflections of a noun, verb, adjective or similar. `alternant_multiword_spec` is as returned by `parse_inflected_text` and describes the properties of the term to be inflected, including all the user-provided inflection specifications (e.g. the number, gender, conjugation/declension/etc. of each word) and the surrounding text. `props` indicates how to do the actual inflection (see below). The resulting inflected forms are stored into the `.forms` property of `multiword_spec`. This property holds a table whose keys are slots (i.e. ID's of individual inflected forms, such as "pres_1sg" for the first-person singular present indicative tense of a verb) and whose values are lists of the form `{ form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``}`, where ``form`` is a string specifying the value of the form (e.g. "ouço" for the first-person singular present indicative of the Portuguese verb [[ouvir]]); ``manual_translit_or_nil`` is the corresponding manual transliteration if needed (i.e. if the form is in a non-Latin script and the automatic transliteration is incorrect or unavailable), otherwise nil; and ``footnote_list_or_nil`` is a list of footnotes to be attached to the form, or nil for no footnotes. Note that currently footnotes must be surrounded by brackets, e.g "[archaic]", and should not begin with a capital letter or end with a period. (Conversion from "[archaic]" to "Archaic." happens automatically.) This function has no return value, but modifies `multiword_spec` in-place, adding the `forms` table as described above. After calling this function, call show_forms() on the `forms` table to convert the forms and footnotes given in this table to strings suitable for display. `props` is an object specifying properties used during inflection, as follows: ```{ slot_list = {{"``slot``", "``accel``"}, {"``slot``", "``accel``"}, ...}, slot_table = {``slot`` = "``accel``", ``slot`` = "``accel``", ...}, skip_slot = nil `or` __function__(slot), lang = nil `or` __lang_object__, inflect_word_spec = __function__(base), get_variants = nil 'or` __function__(formval), combine_metadata = nil `or` __function__(data), include_user_specified_links = __boolean__, }``` `slot_list` is a list of two-element lists of slots and associated accelerator tags. ``slot`` is arbitrary but should correspond with slot names as generated by `inflect_word_spec`. ``accel`` is the corresponding accelerator tags; e.g. if ``slot`` is "pres_1sg", ``accel`` might be "1|s|pres|ind". ``accel`` is actually unused during inflection, but is used during `show_forms()`, which takes the same `slot_list` as a property upon input. `slot_table` is a table mapping slots to associated accelerator tags and serves the same function as `slot_list`. Only one of `slot_list` or `slot_table` must be given. For new code it is preferable to use `slot_list` because this allows you to control the order of processing slots, which may occasionally be important. `skip_slot` is a function of one argument, a slot name, and should return a boolean indicating whether to skip the given slot during inflection. It can be used, for example, to skip singular slots if the overall term being inflected is plural-only, and vice-versa. `lang` is a language object. This is only used to generate manual transliteration. If the language is written in the Latin script or manual transliteration cannot be specified in the input to parse_inflected_text(), this can be omitted. (Manual transliteration is allowed if the `lang` object is set in the `props` passed to parse_inflected_text().) `inflect_word_spec` is the function to do the actual inflection. It is passed a single argument, which is a ``word_spec`` object describing the word to be inflected and the user-provided inflection specifications. It is exactly the same as was returned by the `parse_indicator_spec` function provided in the `props` sent on input to `parse_inflected_text`, but has additional fields describing the word to be inflected and the surrounding text, as follows: ```{ lemma = "``lemma``", before_text = "``text-before-word``", before_text_no_links = "``text-before-word-no-links``", before_text_translit = "``manual-translit-of-text-before-word``" or nil (if no manual translit or respelling was specified in the before-text) -- Fields as described in parse_indicator_spec() ... }``` Here ``lemma`` is the word to be inflected as specified by the user (including any links if so given), and the `before_text*` fields describe the raw text preceding the word to be inflected. Any other fields in this object are as set by `parse_inflected_text`, and describe things like the gender, number, conjugation/declension, etc. as specified by the user in the <...> spec following the word to be inflected. `inflect_word_spec` should initialize the `.forms` property of the passed-in ``word_spec`` object to the inflected forms of the word in question. The value of this property is a table of the same format as the `.forms` property that is ultimately generated by inflect_multiword_or_alternant_multiword_spec() and described above near the top of this documentation: i.e. a table whose keys are slots and whose values are lists of the form `{ form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``}`. `get_variants` is either {nil} or a function of one argument (a string, a form value). The purpose of this function is to ensure that in a multiword term where a given slot has more than one possible variant, the final output has only parallel variants in it. For example, feminine nouns and adjectives in Russian have two possible endings, one typically in -ой (-oj) and the other in -ою (-oju). If we have a feminine adjective-noun combination (or a hyphenated feminine noun-noun combination, or similar), and we don't specify `get_variants`, we'll end up with four values for the instrumental singular: one where both adjective and noun end in -ой, one where both end in -ою, and two where one of the words ends in -ой and the other in -ою. In general if we have N words each with K variants, we'll end up with an explosion of N^K possibilities. `get_variants` avoids this by returning a variant code (an arbitary string) for each variant. If two words each have a non-empty variant code, and the variant codes disagree, the combination will be rejected. If `get_variants` is not provided, or either variant code is an empty string, or the variant codes agree, the combination is allowed. The recommended way to use `get_variants` is as follows: 1. During inflection in `inflect_word_spec`, add a special character or string to each of the variants generated for a given slot when there is more than one. (As an optimization, do this only when there is more than one word being inflected.) Special Unicode characters can be used for this purpose, e.g. U+FFF0, U+FFF1, ..., U+FFFD, which have no meaning in Unicode. 2. Specify `get_variants` as a function that pulls out and returns the special character(s) or string included in the variant forms. 3. When calling show_forms(), specify a `canonicalize` function that removes the variant code character(s) or string from each form before converting to the display form. See [[Module:hi-verb]] and [[Module:hi-common]] for an example of doing this in a generalized fashion. (Look for add_variant_codes(), get_variants() and remove_variant_codes().) `combine_metadata` is a function that is invoked when combining two form objects along along with in-between text and storing into a destination form object. When this happens, if the the form value and translit in the first form object is empty and the in-between text is likewise empty (which regularly happens when appending the form object describing the first word in a multiword expression to empty base text), the second form object is simply shallow-copied along with all of its metadata, and any footnotes are combined appropriately (normally the first form object is such a case won't have footnotes). Otherwise, a new form object is constructed by combining the form values, translit and footnotes from the two objects and in-between text, and calling `combine_metadata` to combine any other metadata. Leave this unspecified if there is no additional metadata or if you don't want any metadata carried over. (Examples of metadata that should generally not be carried over are glosses of individual words, sense ID's and similar word-level properties that can't easily be combined to generate a multiword equivalent. Examples of metadata that should be carried over and combined are qualifiers, labels and certain boolean properties such as an uncertainty flag indicating that a given form is uncertain. For some metadata, it is more complex; for example, if both source words have the same gender or part of speech, the destination should keep that value, but if they differ, it may be safest to leave the field blank.) This function, if specified, is called with a single argument as follows: ```{ slot = "__string__", dest_form = __formobj__, form1 = __formobj__, form2 = __formobj__, between_text = "__string__", between_text_no_links = "__string__", between_text_translit = "__string__" `or` nil }``` Here, `slot` is the slot whose forms are being constructed. `dest_form` is the destination form object into which the combined metadata should be written, and is pre-populated with appropriate `form`, `translit` and `footnotes` fields. `form1` and `form2` are the two source forms being combined, and `between_text` is the text to be inserted between the two source forms. `between_text_no_links` is the same as `between_text` but with double-bracket links removed, and `between_text_translit` is the manual transliteration of `between_text_no_links`, if specified. The function should return nothing, but should side-effect `dest_form` as appropriate. `include_user_specified_links`, if given, ensures that user-specified links in the raw text surrounding a given word are preserved in the output. If omitted or set to false, such links will be removed and the whole multiword expression will be linked. ]==] function export.inflect_multiword_or_alternant_multiword_spec(multiword_spec, props) multiword_spec.forms = {} local is_alternant_multiword = not not multiword_spec.alternant_or_word_specs for _, word_spec in ipairs(is_alternant_multiword and multiword_spec.alternant_or_word_specs or multiword_spec.word_specs) do if word_spec.alternants then inflect_alternants(word_spec, props) else props.inflect_word_spec(word_spec) end iterate_slot_list_or_table(props, function(slot) if not props.skip_slot or not props.skip_slot(slot) then append_forms(props, multiword_spec.forms, slot, word_spec.forms[slot], (rfind(slot, "linked") or props.include_user_specified_links) and word_spec.before_text or word_spec.before_text_no_links, word_spec.before_text_no_links, word_spec.before_text_translit ) end end) end if multiword_spec.post_text ~= "" then local pseudoform = {{form=""}} iterate_slot_list_or_table(props, function(slot) -- If slot is empty or should be skipped, don't try to append post-text. if (not props.skip_slot or not props.skip_slot(slot)) and multiword_spec.forms[slot] then append_forms(props, multiword_spec.forms, slot, pseudoform, (rfind(slot, "linked") or props.include_user_specified_links) and multiword_spec.post_text or multiword_spec.post_text_no_links, multiword_spec.post_text_no_links, multiword_spec.post_text_translit ) end end) end end function export.map_word_specs(alternant_multiword_spec, fun) for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do for _, word_spec in ipairs(multiword_spec.word_specs) do fun(word_spec) end end else fun(alternant_or_word_spec) end end end function export.create_footnote_obj() return { notes = {}, seen_notes = {}, noteindex = 1, seen_refs = {}, } end function export.get_footnote_text(footnotes, footnote_obj) if not footnotes then return "" end local link_indices = {} local all_refs = {} for _, footnote in ipairs(footnotes) do local refs footnote, refs = export.expand_footnote_or_references(footnote) if footnote then local this_noteindex = footnote_obj.seen_notes[footnote] if not this_noteindex then -- Generate a footnote index. this_noteindex = footnote_obj.noteindex footnote_obj.noteindex = footnote_obj.noteindex + 1 table.insert(footnote_obj.notes, '<sup style="color: var(--wikt-palette-red, red)">' .. this_noteindex .. '</sup>' .. footnote) footnote_obj.seen_notes[footnote] = this_noteindex end m_table.insertIfNot(link_indices, this_noteindex) end if refs then for _, ref in ipairs(refs) do if not ref.name then local this_refhash = footnote_obj.seen_refs[ref.text] if not this_refhash then -- Different text needs to have different auto-generated names, globally across the entire page, -- including across different invocations of {{it-verb}} or {{it-conj}}. The easiest way to accomplish -- this is to use a message-digest hashing function. It does not have to be cryptographically secure -- (MD5 is insecure); it just needs to have low probability of collisions. this_refhash = mw.hash.hashValue("md5", ref.text) footnote_obj.seen_refs[ref.text] = this_refhash end ref.autoname = this_refhash end -- I considered using "n" as the default group rather than nothing, to more clearly distinguish regular -- footnotes from references, but this requires referencing group "n" as <references group="n"> below, -- which is non-obvious. m_table.insertIfNot(all_refs, ref) end end end table.sort(link_indices) local function sort_refs(r1, r2) -- FIXME, we are now sorting on an arbitrary hash. Should we keep track of the order we -- saw the autonamed references and sort on that? if r1.autoname and r2.name then return true elseif r1.name and r2.autoname then return false elseif r1.name and r2.name then return r1.name < r2.name else return r1.autoname < r2.autoname end end table.sort(all_refs, sort_refs) for i, ref in ipairs(all_refs) do local refargs = {name = ref.name or ref.autoname, group = ref.group} all_refs[i] = mw.getCurrentFrame():extensionTag("ref", ref.text, refargs) end local link_text if #link_indices > 0 then link_text = '<sup style="color: var(--wikt-palette-red, red)">' .. table.concat(link_indices, ",") .. '</sup>' else link_text = "" end local ref_text = table.concat(all_refs) if link_text ~= "" and ref_text ~= "" then return link_text .. "<sup>,</sup>" .. ref_text else return link_text .. ref_text end end --[==[ Add links around words in a term. If multiword_only, do it only in multiword terms. ]==] function export.add_links(form, multiword_only) if form == "" or form == " " then return form end if not form:find("%[%[") then if rfind(form, "[%s%p]") then --optimization to avoid loading [[Module:headword]] on single-word forms local m_headword = require("Module:headword") --W if m_headword.head_is_multiword(form) then form = m_headword.add_multiword_links(form) end end if not multiword_only and not form:find("%[%[") then form = "[[" .. form .. "]]" end end return form end --[==[ Remove redundant link surrounding entire term. ]==] function export.remove_redundant_links(term) return rsub(term, "^%[%[([^%[%]|]*)%]%]$", "%1") end --[==[ Add links to all before and after text; for use in inflection modules that preserve links in multiword lemmas and include links in non-lemma forms rather than allowing the entire form to be a link. If `remember_original`, remember the original user-specified before/after text so we can reconstruct the original spec later. `add_links` is a function of one argument to add links to a given piece of text; if unspecified, it defaults to `export.add_links`. ]==] function export.add_links_to_before_and_after_text(alternant_multiword_spec, remember_original, add_links) add_links = add_links or export.add_links local function add_links_remember_original(object, field) if remember_original then object["user_specified_" .. field] = object[field] end object[field] = add_links(object[field]) end for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do add_links_remember_original(alternant_or_word_spec, "before_text") if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do for _, word_spec in ipairs(multiword_spec.word_specs) do add_links_remember_original(word_spec, "before_text") end add_links_remember_original(multiword_spec, "post_text") end end end add_links_remember_original(alternant_multiword_spec, "post_text") end --[==[ Reconstruct the original overall spec from the output of parse_inflected_text(), so we can use it in the language-specific acceleration module in the implementation of {{tl|pt-verb form of}} and the like. `props` is an optional table of properties. Currently only `preprocess_angle_bracket_spec` is recognized, and is an optional function of one argument that is called to process an angle-bracket spec before inserting into the reconstructed spec. ]==] function export.reconstruct_original_spec(alternant_multiword_spec, props) local parts = {} props = props or {} local function ins(txt) table.insert(parts, txt) end local function insert_angle_bracket_spec(spec) if props.preprocess_angle_bracket_spec then spec = props.preprocess_angle_bracket_spec(spec) end ins(spec) end for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do ins(alternant_or_word_spec.user_specified_before_text) if alternant_or_word_spec.alternants then ins("((") for i, multiword_spec in ipairs(alternant_or_word_spec.alternants) do if i > 1 then ins(",") end for _, word_spec in ipairs(multiword_spec.word_specs) do ins(word_spec.user_specified_before_text) ins(word_spec.user_specified_lemma) insert_angle_bracket_spec(word_spec.angle_bracket_spec) end ins(multiword_spec.user_specified_post_text) end ins("))") else ins(alternant_or_word_spec.user_specified_lemma) insert_angle_bracket_spec(alternant_or_word_spec.angle_bracket_spec) end end ins(alternant_multiword_spec.user_specified_post_text) local retval = table.concat(parts) if alternant_multiword_spec.allow_default_indicator then -- As a special case, if we see e.g. "amar<>", remove the <>. Don't do this if there are spaces or alternants. if not retval:find(" ") and not retval:find("%(%(") then local retval_no_angle_brackets = retval:match("^(.*)<>$") if retval_no_angle_brackets then return retval_no_angle_brackets end end end return retval end --[==[ Convert the forms in ``formtable`` (a form table, whose keys are slots and whose values are lists of form objects, each of which is a table of the form `form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``, no_accel = ``true_to_suppress_accelerators``, ... `) into strings. The form table is side-effected. Each form list turns into a string consisting of a comma-separated list of linked forms, with accelerators (unless `no_accel` is set in a given form object). If `include_translit` is specified, each string consists of a comma-separated list of form values (each formatted as a link), an HTML `&lt;br/>`, and a comma-separated list of transliterations. `props` is a table used in generating the strings, as follows: ```{ lang = __lang_object__, lemmas = {"``lemma``", "``lemma``", ...}, slot_list = {{"``slot``", "``accel``"}, {"``slot``", "``accel``"}, ...}, slot_table = {``slot`` = "``accel``", ``slot`` = "``accel``", ...}, include_translit = __boolean__, create_footnote_obj = nil `or` __function__(), canonicalize = nil or __function__(formval), preprocess_forms = nil `or` __function__(data), no_deduplicate_forms = __boolean__, combine_metadata_during_dedup = nil `or` __function__(data), transform_accel_obj = nil `or` __function__(slot, form, accel_obj), format_forms = nil `or` __function__(data), generate_link = nil `or` __function__(data), format_tr = nil `or` __function__(data), join_spans = nil `or` __function__(data), allow_footnote_symbols = __boolean__, footnotes = nil or {"``extra_footnote``", "``extra_footnote``", ...}, }``` `lemmas` is the list of lemmas, used in the accelerators. `slot_list` is a list of two-element lists of slots and associated accelerator tag sets. ``slot`` should correspond to slots generated during `inflect_multiword_or_alternant_multiword_spec()`. ``accel`` is the corresponding accelerator tag set; e.g. if ``slot`` is "pres_1sg", ``accel`` might be "1|s|pres|ind". ``accel`` is used in generating entries for accelerator support (see [[WT:ACCEL]]). `slot_table` is a table mapping slots to associated accelerator tag sets and serves the same function as `slot_list`. Only one of `slot_list` or `slot_table` must be given. For new code it is preferable to use `slot_list` because this allows you to control the order of processing slots, which may occasionally be important. `include_translit`, if given, causes transliteration to be included in the generated strings. The function works as follows: # Create an object to hold footnotes (customizable using `create_footnote_obj`). # Generate the comma-separated lemma form values and store in `.lemma` in the form table. # Loop over the slots specified using `slot_list` or `slot_table`. For each slot: ## Canonicalize the form values (customizable using `canonicalize`; by default does nothing). ## Preprocess the forms (customizable using `preprocess_forms`; by default does nothing). ## Unless `no_deduplicate_forms` is set, deduplicate forms in a slot sharing the same form value but possibly different transliteration. (This happens e.g. in Russian, where it is relatively common for a given form to have two possible transliterations, one reflecting a more nativized pronunciation where Cyrillic е triggers palatalization of the preceding consonant, and one reflecting a more "foreign" pronunciation where this palatalization does not happen. In such a case, the automatic transliteration would normally suffice for the more nativized pronunciation but the more "foreign" pronunciation will need manual transliteration.) As part of deduplication, footnotes will be combined using `combine_footnotes`; distinct manual transliterations will be combined into a list (meaning the `translit` field of form objects in some subsequent `props` functions may hold a list; this will be noted when possible); and any remaining metadata will be combined using the `combine_metadata_during_dedup` method, if provided. ## Add acceleration to all forms. The acceleration tag set associated with a given form comes from `slot_list` or `slot_table`, i.e. all forms in a given slot have the same tag set. However, different forms will have different associated transliterations stored into the accelerator object associated with the form, as well as possibly different lemmas. In particular, when there are multiple lemma forms, this is often due to alternative ways to pronounce the lemma (e.g. alternative stress positions or vowel lengths), and there are often associated non-lemma forms that match each lemma. An example given in the introduction is {{m+|uk|русин||Rusyn}}, stressed in the lemma as ''ру́син'' or ''руси́н'' with associated genitive singulars ''ру́сина'' and ''русина́''. We would like the auto-generated accelerator entry for {{m|uk|русина}} to show the variant ''ру́сина'' as having lemma ''ру́син'' and the variant ''русина́'' as having the lemma ''руси́н'', rather than showing both variants as having both lemmas, which is less accurate. As a result, the code that generates acceleration objects for forms matches up forms and lemmas one-to-one if possible. If this is not possible, the matching is usually one lemma to many forms, as in {{m+|uk|міст||bridge}} with genitive singular ''мо́сту'' or ''моста́'' (in which case all forms get the same lemma), or many lemmas to one form, as in {{m+|uk|черга||turn, queue}} stressed either ''че́рга'' or ''черга́'' with nominative singular only ''че́рги'' (in which case the single form gets assicated all lemmas). If there are multiple lemmas and multiple forms, the algorithm attempts to align them as evenly as possible (e.g. two lemma variants to four forms means the first two forms get assigned the first lemma variant and the last two forms get assigned the second lemma variant); this is often going to be incorrect, but (a) there's unlikely to be a single algorithm that works in all such circumstances, and (b) these cases are very rare. Finally, note the following: ##* No acceleration is assigned to a form if any of the following apply: (a) there are no lemmas given in `props.lemmas`; (b) the `no_accel` key in the form object has a non-falsy value; (c) the form value of the form is {"?"} or an em-dash ({"—"}); (d) the accelerator tag set is given as a hyphen {"-"}); or (e) the form value contains an internal link. ##* The accelerator code sets the `formval_for_link` key in each form object to the version of the form value that should be passed to `full_link()` in [[Module:links]]. This is usually the same as the passed-in form value, but differs when `props.allow_footnote_symbols` is specified and an old-style footnote symbol is attached to the form (the removed footnote symbol is stored in the `formval_old_style_footnote_symbol` key), and also differs when the entire form value is surrounded with a redundant internal link (which is removed). ##* The resulting accelerator object can be modified (or replaced entirely) by the `transform_accel_obj` function. This is used, for example, in [[Module:es-verb]], [[Module:pt-verb]] and other Romance-language verb conjugation modules (likewise [[Module:ar-verb]]) to replace the tag set with the original verb spec used to generate the verb, so that the accelerator code can generate the appropriate call to {{tl|es-verb form of}}, {{tl|pt-verb form of}} or the like, which computes the inflections, instead of directly listing the inflections. ## Format the forms into strings. The entire default process can be replaced using `format_forms`; otherwise the default algorithm works as follows: ### Generate the '''form value spans''', with one entry (a linked HTML-ized version of the form value) per form. This can be customized using `generate_link`. (Various modules do this. For example, the Arabic verb module includes qualifiers, labels, ID's and the like that can be specified by the user; the Portuguese and reintegrated Galician verb modules italicize certain superseded or otherwise less-desirable forms instead of linking them normally; the German verb module adds {{m|de|dass}} to subjunctive forms and optional pronouns to imperative forms; and the German adjective module adds articles to adjective forms normally accompanied by articles and the equivalent of "he/she is" etc. to predicate forms.) The default uses `full_link()` in [[Module:links]] (with transliteration generation disabled) concatenated with the appropriate footnote symbol(s) (if any). ### Generate the '''transliteration spans''', with one entry per distinct translit, auto-generated if manual translit isn't available. Note that, due to the earlier form value deduplication step, there may be multiple translits per form object. These translits are themselves deduplicated to get the list of spans. (Such duplication can happen, for example, in Arabic with terms containing a glottal stop in them; there may be multiple ways of spelling the glottal stop or ''hamza'' in Arabic, but only one way of transliterating it.) Each span consists of an object specifying the translit minus any attached old-style footnote symbols (which are only allowed if `props.allow_footnote_symbols` is set); the attached old-style footnote symbol, which is always an empty string when `props.allow_footnote_symbols` is not set; and the list of (new-style) footnotes. These objects are then converted to formatted strings, either using `format_tr` if supplied or else calling `tag_translit()` in [[Module:script utilities]] and concatenating the appropriate footnote symbol(s) (if any). ### Combine the form value and transliteration spans. If `join_spans` is supplied, use it; otherwise, concatenate the form value spans (comma-separated) and (if available) transliteration spans (comma-separated), and (if appropriate) combine them using {<br />}. `create_footnote_obj` is an optional function of no arguments to create the footnote object used to track footnotes; see `create_footnote_obj()`. Customizing it is useful to prepopulate the footnote table using `get_footnote_text()`. `canonicalize` is an optional function of one argument (a form value) to canonicalize each form before processing; it can return nil for no change. The most common purpose of this function is to remove variant codes from the form value. See the documentation for `inflect_multiword_or_alternant_multiword_spec()` for a description of variant codes and their purpose. `preprocess_forms` is an optional function of one argument (a table of properties) to preprocess the form objects as a whole. It runs after `canonicalize` (meaning that the form values passed in are canonicalized) and before deduplication and the addition of acceleration info. The property table passed in has the following properties: * `slot`: The slot being processed. * `forms`: The list of form objects for this slot. * `accel_tag_set`: The accelerator tag set for this slot, taken from `slot_list` or `slot_table`. * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. `preprocess_forms` should return a list of preprocessed form objects, or {nil} to use the passed-in `forms`. If this function does deduplication, you should set `no_deduplicate_forms` to disable the default deduplication process. `no_deduplicate_forms`, if set, disables the deduplication step (see above). `combine_metadata_during_dedup` is an optional function of one argument (a table of properties) to combine the metadata of deduplicated form objects. The property table passed in has the following properties: * `slot`: The slot being processed. * `existing_form`: The existing form object into which a duplicated form is being combined. * `dup_form`: The duplicated form being combined into `existing_form`. * `existing_form_pos`: The one-based position of the existing form in the deduplicated form list (not necessarily its original position). * `dup_form_pos`: The one-based position of the duplicated form in its original list. The following should be noted about the form objects passed in: # The form values in `.form` have been canonicalized using `.canonicalize`, if provided. # The form values in `existing_form` and `dup_form` are always the same. # The footnotes in `existing_form` have already been combined with those in `dup_form`. # If there was manual translit either in `existing_form` (prior to deduplication) or in `dup_form`, there will be manual translit in `existing_form.translit` that is a list and combines any previous accumulated translits in `existing_form` as well as the translit in `dup_form` (even if one of them was specified as {nil} indicating an automatic translit). This means that the translit in `existing_form.translit` is always either {nil} or a list of strings (and the same applies to `dup_form.translit`). `transform_accel_obj` is an optional function of three arguments (``slot``, ``formobj``, ``accel_obj``) to transform the default constructed accelerator object in ``accel_obj`` into an object that should be passed to `full_link()` in [[Module:links]]. It should return the new accelerator object, or {nil} for no acceleration. (If {nil} is returned, the corresponding form has no acceleration; this is unlike most customization functions, where returning {nil} causes the default algorithm to be invoked.) The function can destructively modify the accelerator object passed in. '''NOTE''': This is called even when the passed-in ``accel_obj`` is {nil} (see the (a) through (e) reasons above why no acceleration may be assigned to a form). Thus, your code needs to do something sensible in this case. The description above of how `show_forms()` works inclues various examples of modules that supply a `transform_accel_obj` function and the reasons for doing so. `format_forms`, if supplied, is a function that entirely replaces the formatting portion of `show_forms()`. An example of why you might want to do this is to get a different layout than the default, e.g. one where translit is displayed next to each form value instead of the form values and translits grouped and displayed on separate lines. Under normal circumstances, you should not do this, but instead customize the functions that replace specific parts of the default formatting algorithm (see below). This function is passed one argument (a table of properties) and should return a string (the formatted forms, ready to store into the slot in the form table) or {nil} to proceed with the default algorithm (see above). The property table passed in has the following properties: * `slot`: The slot being processed. * `forms`: The list of form objects, deduplicated and with accelerator info added. * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. The following should be noted about the form objects in `forms`: # There are extra fields `formval_for_link`, `formval_old_style_footnote_symbol` and `accel_obj`. The first two are as described above under the paragraph beginning "Add acceleration to all forms" under "The function works as follows". The third one is the accelerator object in the format expected by [[Module:links]]. # The `translit` field, if non-{nil}, is a list of transliterations rather than a single transliteration; this is due to the form value deduplication step. `generate_link` is an optional function to generate the link text for a given form value. It is passed a single argument (a table of properties) and should return a string, the formatted link. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following properties: * `slot`: The slot being processed. * `form`: The form to be converted to a formatted link. As with the `format_forms` function described above, the form objects passed in contain extra fields `formval_for_link`, `formval_old_style_footnote_symbol` and `accel_obj` (all of which will normally be used), and the `translit` field, if non-{nil}, is a list. * `pos`: The one-based position of the form being processed, in the list of form value spans. Rarely used. * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. Normally used in order to get the (new-style) footnote symbol associated with any footnotes in `footnotes`. The description above of how `show_forms()` works inclues various examples of modules that supply a `generate_link` function and the reasons for doing so. `format_tr` is an optional function to generate the formatted text for a given transliteration. It is passed a single argument (a table of properties) and should return a string, the formatted transliteration text. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following properties: * `slot`: The slot being processed. * `tr_for_tag`: The transliteration to process, where old-style footnote symbols have been removed. * `old_style_footnote_symbol`: The removed old-style footnote symbol, or a blank string if no symbol was removed. * `pos`: The one-based position of the transliteration being processed, in the list of transliteration spans. Rarely used. * `footnotes`: The list of footnotes associated with all form objects with this transliteration. (If there were multiple form objects with the same transliteration, the list of footnotes will have been generated using `combine_footnotes()`.) * `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default `create_footnote_obj()` function. Normally used in order to get the (new-style) footnote symbol associated with any footnotes in `footnotes`. `join_spans` is an optional function to join the processed form value and transliteration spans into a formatted string. It is passed a single argument (a table of properties) and should return the final string to store into the form table slot. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following properties: * `slot`: The slot being processed. * `formval_spans`: A list of strings, the formatted form value spans. * `tr_spans`: A list of strings, the formatted transliteration spans. If there is no transliteration, this will be an empty list. A custom `join_spans` is provided by [[Module:de-verb]], which concatenates the form value spans vertically (using {"<br />"}) instead of horizontally using a comma, as is normal; this is because there is no translit and the form values are often long, containing extra words attached during `generate_link()`. The only exception is the `aux` slot holding the auxiliaries, which is concatenated horizontally using {" or "}. [[Module:de-adjective]] similarly provides a custom `join_spans` function that concatenates the form value spans vertically. `allow_footnote_symbols`, if given, causes any old-style footnote symbols attached to forms (e.g. numbers, asterisk) to be separated off, placed outside the links, and superscripted. In this case, `footnotes` should be a list of footnotes (preceded by footnote symbols, which are superscripted). These footnotes are combined with any footnotes found in the forms and placed into `forms.footnotes`. This mechanism of specifying footnotes is provided for backward compatibility with certain existing inflection modules and should not be used for new modules. Instead, use the regular footnote mechanism specified using the `footnotes` property attached to each form object. ]==] function export.show_forms(formtable, props) local footnote_obj = props.create_footnote_obj and props.create_footnote_obj() or export.create_footnote_obj() local function fetch_formval_and_translit(entry, remove_links) local formval, translit if type(entry) == "table" then formval, translit = entry.form, entry.translit else formval = entry end if remove_links then formval = m_links.remove_links(formval) end return formval, translit end local lemma_formvals = {} for _, lemma in ipairs(props.lemmas) do local lemma_formval, _ = fetch_formval_and_translit(lemma) m_table.insertIfNot(lemma_formvals, lemma_formval) end formtable.lemma = #lemma_formvals > 0 and table.concat(lemma_formvals, "، ") or mw.loadData(headword_data_module).pagename -- For safety, since we in-place modify `lemmas` usually before processing a given slot, make a copy. local props_lemmas = m_table.shallowCopy(props.lemmas) for i, lemma in ipairs(props_lemmas) do props_lemmas[i] = m_table.shallowCopy(lemma) end local function do_slot(slot, accel_tag_set) local formobjs = formtable[slot] if formobjs then if type(formobjs) ~= "table" then error("Internal error: For slot '" .. slot .. "', expected table but saw " .. dump(formobjs)) end -- Maybe canonicalize the form values (e.g. remove variant codes and monosyllabic accents). if props.canonicalize then for _, form in ipairs(formobjs) do form.form = props.canonicalize(form.form) or form.form end end -- Preprocess the forms as a whole if called for. if props.preprocess_forms then formobjs = props.preprocess_forms { slot = slot, forms = formobjs, accel_tag_set = accel_tag_set, footnote_obj = footnote_obj, } or formobjs end -- Maybe deduplicate form values (happens e.g. in Russian with two terms with the same Russian form but -- different translits). if not props.no_deduplicate_forms then local deduped_formobjs = {} for i, form in ipairs(formobjs) do local function combine_forms(existing_form, dup_form, pos) assert(existing_form.form == dup_form.form) -- Combine footnotes. existing_form.footnotes = export.combine_footnotes(existing_form.footnotes, dup_form.footnotes) -- If translit is being generated, and there's manual translit associated with either form, we -- need to generate any missing translits and combine them, taking into account the fact that a -- translit value may actually be a list of translits (particularly with the existing form if we -- already combined an item with manual translit into it). if props.include_translit and form_value_transliterable(existing_form.form) and ( existing_form.translit or dup_form.translit) then local combined_translit if not existing_form.translit then combined_translit = { props_transliterate(props, m_links.remove_links(existing_form.form)) } elseif type(existing_form.translit) == "string" then combined_translit = {existing_form.translit} else combined_translit = existing_form.translit end local dup_form_translit = dup_form.translit if not dup_form_translit then -- dup_form.form is the same as existing_form.form (see assert above), but this is -- defensive programming in case that changes dup_form_translit = {props_transliterate(props, m_links.remove_links(dup_form.form))} elseif type(dup_form_translit) == "string" then dup_form_translit = {dup_form_translit} end for _, translit in ipairs(dup_form_translit) do m_table.insertIfNot(combined_translit, translit) end existing_form.translit = combined_translit end if props.combine_metadata_during_dedup then props.combine_metadata_during_dedup { slot = slot, existing_form = existing_form, existing_form_pos = pos, dup_form = dup_form, dup_form_pos = i, } end end m_table.insertIfNot(deduped_formobjs, form, { key = function(form) return form.form end, combine = combine_forms, }) end formobjs = deduped_formobjs end -- Add acceleration info to form objects. for i, form in ipairs(formobjs) do local formval = form.form if not form_value_transliterable(formval) then form.formval_for_link = formval form.formval_old_style_footnote_symbol = "" else local formval_for_link, formval_old_style_footnote_symbol if props.allow_footnote_symbols then formval_for_link, formval_old_style_footnote_symbol = require(table_tools_module).get_notes(formval) if formval_old_style_footnote_symbol ~= "" then track("old-style-footnote-symbol") end else formval_for_link = formval formval_old_style_footnote_symbol = "" end -- remove redundant link surrounding entire form formval_for_link = export.remove_redundant_links(formval_for_link) form.formval_for_link = formval_for_link form.formval_old_style_footnote_symbol = formval_old_style_footnote_symbol -------------------- Compute the accelerator object. ----------------- local accel_obj -- Check if form still has links; if so, don't add accelerators because the resulting entries will -- be wrong. if props_lemmas[1] and not form.no_accel and accel_tag_set ~= "-" and not rfind(formval_for_link, "%[%[") then -- If there is more than one form or more than one lemma, things get tricky. Often, there are -- the same number of forms as lemmas, e.g. for Ukrainian [[зимовий]] "wintry; winter (rel.)", -- which can be stressed зимо́вий or зимови́й with corresponding masculine/neuter genitive -- singulars зимо́вого or зимово́го etc. In this case, usually the forms and lemmas match up so -- we do this. If there are different numbers of forms than lemmas, it's usually one lemma -- against several forms e.g. Ukrainian [[міст]] "bridge" with genitive singular мо́сту or моста́ -- (accent patterns b or c) or [[ложка|ло́жка]] "spoon" with nominative plural ло́жки or ложки́ -- (accent patterns a or c). Here, we should assign the same lemma to both forms. The opposite -- can happen, e.g. [[черга]] "turn, queue" stressed че́рга or черга́ with nominative plural only -- че́рги (accent patterns a or d). Here we should assign both lemmas to the same form. In more -- complicated cases, with more than one lemma and form and different numbers of each, we try -- to align them as much as possible, e.g. if there are somehow eight forms and three lemmas, -- we assign lemma 1 to forms 1-3, lemma 2 to forms 4-6 and lemma 3 to forms 7 and 8, and -- conversely if there are somehow three forms and eight lemmas. This is likely to be wrong, but -- (a) there's unlikely to be a single algorithm that works in all such circumstances, and (b) -- these cases are vanishingly rare or nonexistent. Properly we should try to remember which -- form was generated by which lemma, but that is significant extra work for little gain. local first_lemma, last_lemma if #formobjs >= #props_lemmas then -- More forms than lemmas. Try to even out the forms assigned per lemma. local forms_per_lemma = math.ceil(#formobjs / #props_lemmas) first_lemma = math.floor((i - 1) / forms_per_lemma) + 1 last_lemma = first_lemma else -- More lemmas than forms. Try to even out the lemmas assigned per form. local lemmas_per_form = math.ceil(#props_lemmas / #formobjs) first_lemma = (i - 1) * lemmas_per_form + 1 last_lemma = math.min(first_lemma + lemmas_per_form - 1, #props_lemmas) end local accel_lemma, accel_lemma_translit if first_lemma == last_lemma then accel_lemma, accel_lemma_translit = fetch_formval_and_translit(props_lemmas[first_lemma], "remove links") else accel_lemma = {} accel_lemma_translit = {} for j=first_lemma, last_lemma do local this_lemma = props_lemmas[j] local this_accel_lemma, this_accel_lemma_translit = fetch_formval_and_translit(props_lemmas[j], "remove links") -- Do not use table.insert() especially for the translit because it may be nil and in -- that case we want gaps in the array. accel_lemma[j - first_lemma + 1] = this_accel_lemma accel_lemma_translit[j - first_lemma + 1] = this_accel_lemma_translit end end local accel_translit if props.include_translit and form.translit then if type(form.translit) == "table" then accel_translit = table.concat(form.translit, ", ") elseif type(form.translit) == "string" then accel_translit = form.translit else error(("Internal error: For slot '%s', form translit is not a table or string: %s"): format(slot, dump(accel_translit))) end end accel_obj = { form = accel_tag_set, translit = accel_translit, lemma = accel_lemma, lemma_translit = props.include_translit and accel_lemma_translit or nil, } end -- Postprocess if requested. if props.transform_accel_obj then accel_obj = props.transform_accel_obj(slot, form, accel_obj) end form.accel_obj = accel_obj end end -- Format the form objects into a string for insertion into the table. local formatted_forms if props.format_forms then formatted_forms = props.format_forms { slot = slot, forms = forms, footnote_obj = footnote_obj, } end if not formatted_forms then -- Default algorithm: Separate form values and translits and concatenate on separate lines. -- Form values have already been deduplicated but we may need to deduplicate translits (this happens -- e.g. in Arabic where there may be multiple ways of spelling a hamza in the Arabic script but only -- one way in transliteration). local formval_spans = {} local tr_spans = {} for i, form in ipairs(formobjs) do local link if props.generate_link then link = props.generate_link { slot = slot, pos = i, form = form, footnote_obj = footnote_obj, } end if not link then link = m_links.full_link { lang = props.lang, term = form.formval_for_link, tr = "-", accel = form.accel_obj } .. form.formval_old_style_footnote_symbol .. export.get_footnote_text(form.footnotes, footnote_obj) end formval_spans[i] = link if props.include_translit then -- Note that if there is an attached old-style footnote symbol, we transliterate it. local translits = form.translit or props_transliterate(props, m_links.remove_links(form.form)) if type(translits) == "string" then translits = {translits} end for _, tr in ipairs(translits) do local tr_for_tag, tr_old_style_footnote_symbol if props.allow_footnote_symbols then tr_for_tag, tr_old_style_footnote_symbol = require(table_tools_module).get_notes(tr) if tr_old_style_footnote_symbol ~= "" then track("old-style-footnote-symbol") end else tr_for_tag = tr tr_old_style_footnote_symbol = "" end m_table.insertIfNot(tr_spans, { tr_for_tag = tr_for_tag, old_style_footnote_symbol = tr_old_style_footnote_symbol, footnotes = form.footnotes, }, { key = function(trobj) return trobj.tr_for_tag end, combine = function(tr, newtr) -- Combine footnotes. tr.footnotes = export.combine_footnotes(tr.footnotes, newtr.footnotes) tr.old_style_footnote_symbol = tr.old_style_footnote_symbol .. newtr.old_style_footnote_symbol end, }) end end end for i, tr_span in ipairs(tr_spans) do local formatted_tr if props.format_tr then formatted_tr = props.format_tr { slot = slot, pos = i, tr_for_tag = tr_span.tr_for_tag, old_style_footnote_symbol = tr_span.old_style_footnote_symbol, footnotes = tr_span.footnotes, footnote_obj = footnote_obj, } end if not formatted_tr then formatted_tr = require(script_utilities_module).tag_translit(tr_span.tr_for_tag, props.lang, "default", " style=\"color: var(--wikt-palette-grey-8,#888);\"") .. tr_span.old_style_footnote_symbol .. export.get_footnote_text(tr_span.footnotes, footnote_obj) end tr_spans[i] = formatted_tr end if props.join_spans then formatted_forms = props.join_spans { slot = slot, formval_spans = formval_spans, tr_spans = tr_spans, } end if not formatted_forms then local formval_span = table.concat(formval_spans, "، ") local tr_span if #tr_spans > 0 then tr_span = table.concat(tr_spans, "، ") end if tr_span then formatted_forms = formval_span .. "<br />" .. tr_span else formatted_forms = formval_span end end end formtable[slot] = formatted_forms else formtable[slot] = "—" end end iterate_slot_list_or_table(props, do_slot) local all_notes = footnote_obj.notes if props.footnotes then for _, note in ipairs(props.footnotes) do track("old-style-footnote-symbol") local symbol, entry = require(table_tools_module).get_initial_notes(note) table.insert(all_notes, symbol .. entry) end end formtable.footnote = table.concat(all_notes, "<br />") end --[==[ Given a list of forms (each of which is a table of the form `{form=``form``, translit=``manual_translit``, footnotes=``footnotes``}`), concatenate into a `"``slot``=``form``//``translit``,``form``//``translit``,..."` string (or `"``slot``=``form``,``form``,..."` if no translit), replacing embedded `|` signs with `<!>`. '''NOTE:''' This function is deprecated. Use an argument {{para|json|1}} to return a JSON encoding of the alternant multiword spec (including any forms) instead. ]==] function export.concat_forms_in_slot(forms) if forms then local new_vals = {} for _, v in ipairs(forms) do local form = v.form if v.translit then form = form .. "//" .. v.translit end table.insert(new_vals, rsub(form, "|", "<!>")) end return table.concat(new_vals, ",") else return nil end end return export ndlvk07gucxwlvi6on3gr5v6498l707 وحدة:maintenance category 828 230979 1097590 1003331 2026-07-26T19:30:53Z ForzaGreen 28665 تحديث الاسم بعد نقل [[وحدة:pages/ملعب]] إلى [[وحدة:pages/2]] 1097590 Scribunto text/plain local export = {} -- W: --[[local pages_module = "Module:pages"--]] -- إلى حين التأكد أن النسخة الجديدة من الوحدة شغالة مع ما هو موجود local pages_module = "وحدة:pages/2" local parameters_module = "Module:parameters" local utilities_module = "Module:utilities" local new_title = mw.title.new local uses_hidden_category -- Defined below. --[==[ Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==] local function format_categories(...) format_categories = require(utilities_module).format_categories return format_categories(...) end local function is_sandbox(...) local is_sandbox = require(pages_module).is_sandbox return is_sandbox(...) end local function is_testcase_page(...) is_testcase_page = require(pages_module).is_testcase_page return is_testcase_page(...) end local function process_params(...) process_params = require(parameters_module).process return process_params(...) end --[==[ Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==] local current_title local function get_current_title() current_title, get_current_title = mw.title.getCurrentTitle(), nil return current_title end function export.uses_hidden_category(title) local namespace = title.namespace -- Thread: and Summary: pages are named "Thread:PAGE" or "Summary:PAGE", -- where PAGE is the page they relate to. How we treat them therefore -- depends on what that page is. while namespace == 90 or namespace == 92 do title = new_title(title.text) namespace = title.namespace end -- User: and all talk namespaces, as well as all sandboxes and testcase pages (including their documentation pages). --W: --[[return ( namespace == 2 or title.isTalkPage or is_sandbox(title, "include documentation") or is_testcase_page(title, "include documentation") ) and true or false--]] return ( namespace == 2 or title.isTalkPage or is_sandbox(title) or is_testcase_page(title) ) and true or false end uses_hidden_category = export.uses_hidden_category function export.get_category(name, cat) if uses_hidden_category(current_title or get_current_title()) then name = name .. "/hidden" end return cat and format_categories(name, nil, "-", nil, true) or name end function export.template(frame) local args = process_params(frame:getParent().args, { [1] = {required = true, default = ""}, ["cat"] = {type = "boolean", default = false} }) return export.get_category(args[1], args.cat) end return export 5u09w9lttv8syn3fdxxfh8hkwux1ua7 وحدة:pages/2 828 230986 1097587 1003273 2026-07-26T19:30:21Z ForzaGreen 28665 نقل ForzaGreen صفحة [[وحدة:pages/ملعب]] إلى [[وحدة:pages/2]]: إعادة تسمية: ليست صفحة ملعب بل نسخة ثانية مستعملة فعليًّا من [[وحدة:pages]] 1003273 Scribunto text/plain local export = {} local string_utilities_module = "Module:string utilities" local concat = table.concat local find = string.find local format = string.format local getmetatable = getmetatable local get_current_section -- defined below local get_namespace_shortcut -- defined below local get_pagetype -- defined below local gsub = string.gsub local insert = table.insert local is_internal_title -- defined below local is_title -- defined below local lower = string.lower local match = string.match local new_title = mw.title.new local require = require local sub = string.sub local title_equals = mw.title.equals local tonumber = tonumber local type = type local ufind = mw.ustring.find local unstrip_nowiki = mw.text.unstripNoWiki --[==[ Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==] local function decode_entities(...) decode_entities = require(string_utilities_module).decode_entities return decode_entities(...) end local function ulower(...) ulower = require(string_utilities_module).lower return ulower(...) end local function trim(...) trim = require(string_utilities_module).trim return trim(...) end --[==[ Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==] local current_frame local function get_current_frame() current_frame, get_current_frame = mw.getCurrentFrame(), nil return current_frame end local parent_frame local function get_parent_frame() parent_frame, get_parent_frame = (current_frame or get_current_frame()):getParent(), nil return parent_frame end local namespace_shortcuts local function get_namespace_shortcuts() namespace_shortcuts, get_namespace_shortcuts = { [4] = "WT", [10] = "T", [14] = "CAT", [100] = "AP", [110] = "WS", [118] = "RC", [828] = "MOD", }, nil return namespace_shortcuts end do local transcluded --[==[ Returns {true} if the current {{tl|#invoke:}} is being transcluded, or {false} if not. If the current {{tl|#invoke:}} is part of a template, for instance, this template will therefore return {true}. Note that if a template containing an {{tl|#invoke:}} is used on its own page (e.g. to display a demonstration), this function is still able to detect that this is transclusion. This is an improvement over the other method for detecting transclusion, which is to check the parent frame title against the current page title, which fails to detect transclusion in that instance.]==] function export.is_transcluded() if transcluded == nil then transcluded = (parent_frame or get_parent_frame()) and parent_frame:preprocess("<includeonly>1</includeonly>") == "1" or false end return transcluded end end do local preview --[==[ Returns {true} if the page is currently being viewed in preview, or {false} if not.]==] function export.is_preview() if preview == nil then preview = (current_frame or get_current_frame()):preprocess("{{REVISIONID}}") == "" end return preview end end --[==[ Returns {true} if the input is a title object, or {false} if not. This therefore '''includes''' external title objects (i.e. those for pages on other wikis), such as [[w:Example]], unlike `is_internal_title` below.]==] function export.is_title(val) if not (val and type(val) == "table") then return false end local mt = getmetatable(val) -- There's no foolproof method for checking for a title object, but the -- __eq metamethod should be mw.title.equals unless the object has been -- seriously messed around with. return mt and type(mt) == "table" and getmetatable(mt) == nil and mt.__eq == title_equals and true or false end is_title = export.is_title --[==[ Returns {true} if the input is an internal title object, or {false} if not. An internal title object is a title object for a page on this wiki, such as [[example]]. This therefore '''excludes''' external title objects (i.e. those for pages on other wikis), such as [[w:Example]], unlike `is_title` above.]==] function export.is_internal_title(title) -- Note: Mainspace titles starting with "#" should be invalid, but a bug in mw.title.new and mw.title.makeTitle means a title object is returned that has the empty string for prefixedText, so they need to be filtered out. return is_title(title) and #title.prefixedText > 0 and #title.interwiki == 0 end is_internal_title = export.is_internal_title --[==[ Returns {true} if the input string is a valid link target, or {false} if not. This therefore '''includes''' link targets to other wikis, such as [[w:Example]], unlike `is_valid_page_name` below.]==] function export.is_valid_link_target(target) local target_type = type(target) if target_type == "string" then return is_title(new_title(target)) end error(format("bad argument #1 to 'is_valid_link_target' (string expected, got %s)", target_type), 2) end --[==[ Returns {true} if the input string is a valid page name on this wiki, or {false} if not. This therefore '''excludes''' page names on other wikis, such as [[w:Example]], unlike `is_valid_link_target` above.]==] function export.is_valid_page_name(name) local name_type = type(name) if name_type == "string" then return is_internal_title(new_title(name)) end error(format("bad argument #1 to 'is_valid_page_name' (string expected, got %s)", name_type), 2) end --[==[ Given a title object, returns a full link target which will always unambiguously link to it. For instance, the input {"foo"} (for the page [[foo]]) returns {":foo"}, as a leading colon always refers to mainspace, even when other namespaces might be assumed (e.g. when transcluding using `{{ }}` syntax). If `shortcut` is set, then the returned target will use the namespace shortcut, if any; for example, the title for `Template:foo` would return {"T:foo"} instead of {"Template:foo"}.]==] function export.get_link_target(title, shortcut) if not is_title(title) then error(format("bad argument #1 to 'is_valid_link_target' (title object expected, got %s)", type(title))) elseif title.interwiki ~= "" then return title.fullText elseif shortcut then local fragment = title.fragment if fragment == "" then return get_namespace_shortcut(title) .. ":" .. title.text end return get_namespace_shortcut(title) .. ":" .. title.text .. "#" .. fragment elseif title.namespace == 0 then return ":" .. title.fullText end return title.fullText end do local function find_sandbox(text) return find(text, "^User:.") or find(lower(text), "sandbox", 1, true) end local function get_transclusion_subtypes(title, main_type, documentation, page_suffix) local text, subtypes = title.text, {main_type} -- Any template/module with "sandbox" in the title. These are impossible -- to screen for more accurately, as there's no consistent pattern. Also -- any user sandboxes in the form (e.g.) "Template:User:...". local sandbox = find_sandbox(text) if sandbox then insert(subtypes, "sandbox") end -- Any template/module testcases (which can be labelled and/or followed -- by further subpages). local testcase = find(text, "./[Tt]estcases?%f[%L]") if testcase then -- Order "testcase" and "sandbox" based on where the patterns occur -- in the title. local n = sandbox and sandbox < testcase and 3 or 2 insert(subtypes, n, "testcase") end -- Any template/module documentation pages. if documentation then insert(subtypes, "documentation") end local final = subtypes[#subtypes] if not (final == main_type and not page_suffix or final == "sandbox") then insert(subtypes, "page") end return concat(subtypes, " ") end local function get_snippet_subtypes(title, main_type, documentation) local ns = title.namespace return get_transclusion_subtypes(title, ( ns == 2 and "user " or ns == 8 and match(title.text, "^Gadget-.") and "gadget " or "" ) .. main_type, documentation) end --[==[ Returns the page type of the input title object in a format which can be used in running text.]==] function export.get_pagetype(title) if not is_internal_title(title) then error(mw.dumpObject(title.fullText) .. " is not a valid page name.") end -- If possibly a documentation page, get the base title and set the -- `documentation` flag. local content_model, text, documentation = title.contentModel if content_model == "wikitext" then text = title.text if title.isSubpage and title.subpageText == "documentation" then local base_title = title.basePageTitle if base_title then title, content_model, text, documentation = base_title, base_title.contentModel, base_title.text, true end end end -- Content models have overriding priority, as they can appear in -- nonstandard places due to page content model changes. if content_model == "css" or content_model == "sanitized-css" then return get_snippet_subtypes(title, "stylesheet", documentation) elseif content_model == "javascript" then return get_snippet_subtypes(title, "script", documentation) elseif content_model == "json" then return get_snippet_subtypes(title, "JSON data", documentation) elseif content_model == "MassMessageListContent" then return get_snippet_subtypes(title, "mass message delivery list", documentation) -- Modules. elseif content_model == "Scribunto" then return get_transclusion_subtypes(title, "module", documentation, false) elseif content_model == "text" then return "page" -- ??? -- Otherwise, the content model is "wikitext", so check namespaces. elseif title.isTalkPage then return "talk page" end local ns = title.namespace -- Main namespace. if ns == 0 then return "entry" -- Wiktionary: elseif ns == 4 then return find_sandbox(title.text) and "sandbox" or "project page" -- Template: elseif ns == 10 then return get_transclusion_subtypes(title, "template", documentation, false) end -- Convert the namespace to lowercase, unless it contains a capital -- letter after the initial letter (e.g. MediaWiki, TimedText). Also -- normalize any underscores. local ns_text = gsub(title.nsText, "_", " ") if ufind(ns_text, "^%U*$", 2) then ns_text = ulower(ns_text) end -- User: if ns == 2 then return ns_text .. " " .. (title.isSubpage and "subpage" or "page") -- Category: and Appendix: elseif ns == 14 or ns == 100 then return ns_text -- Thesaurus: and Reconstruction: elseif ns == 110 or ns == 118 then return ns_text .. " entry" end return ns_text .. " page" end get_pagetype = export.get_pagetype end --[==[ Returns {true} if the input title object is for a content page, or {false} if not. A content page is a page that is considered part of the dictionary itself, and excludes pages for discussion, administration, maintenance etc.]==] function export.is_content_page(title) if not is_internal_title(title) then error(mw.dumpObject(title.fullText) .. " is not a valid page name.") end local ns = title.namespace -- (main), Appendix, Thesaurus, Citations, Reconstruction. return (ns == 0 or ns == 100 or ns == 110 or ns == 114 or ns == 118) and title.contentModel == "wikitext" end --[==[ Returns {true} if the input title object is for a documentation page, or {false} if not.]==] function export.is_documentation(title) return match(get_pagetype(title), "%f[%w]documentation%f[%W]") and true or false end --[==[ Returns {true} if the input title object is for a sandbox, or {false} if not. By default, sandbox documentation pages are excluded, but this can be overridden with the `include_documentation` parameter.]==] function export.is_sandbox(title, include_documentation) local pagetype = get_pagetype(title) return match(pagetype, "%f[%w]sandbox%f[%W]") and ( include_documentation or not match(pagetype, "%f[%w]documentation%f[%W]") ) and true or false end --[==[ Returns {true} if the input title object is for a testcase page, or {false} if not. By default, testcase documentation pages are excluded, but this can be overridden with the `include_documentation` parameter.]==] function export.is_testcase_page(title, include_documentation) local pagetype = get_pagetype(title) return match(pagetype, "%f[%w]testcase%f[%W]") and ( include_documentation or not match(pagetype, "%f[%w]documentation%f[%W]") ) and true or false end --[==[ Returns the namespace shortcut for the input title object, or else the namespace text. For example, a `Template:` title returns {"T"}, a `Module:` title returns {"MOD"}, and a `User:` title returns {"User"}.]==] function export.get_namespace_shortcut(title) return (namespace_shortcuts or get_namespace_shortcuts())[title.namespace] or title.nsText end get_namespace_shortcut = export.get_namespace_shortcut do local function check_level(lvl) if type(lvl) ~= "number" then error("Heading levels must be numbers.") elseif lvl < 1 or lvl > 6 or lvl % 1 ~= 0 then error("Heading levels must be integers between 1 and 6.") end return lvl end --[==[ A helper function which iterates over the headings in `text`, which should be the content of a page or (main) section. Each iteration returns three values: `sec` (the section title), `lvl` (the section level) and `loc` (the index of the section in the given text, from the first equals sign). The section title will be automatically trimmed, and any HTML entities will be resolved. The optional parameter `a` (which should be an integer between 1 and 6) can be used to ensure that only headings of the specified level are iterated over. If `b` is also given, then they are treated as a range. The optional parameters `a` and `b` can be used to specify a range, so that only headings with levels in that range are returned.]==] local function find_headings(text, a, b) a = a and check_level(a) or nil b = b and check_level(b) or a or nil local start, loc, lvl, sec = 1 return function() repeat loc, lvl, sec, start = match(text, "()%f[^%z\n](==?=?=?=?=?)([^\n]+)%2[\t ]*%f[%z\n]()", start) lvl = lvl and #lvl until not (sec and a) or (lvl >= a and lvl <= b) return sec and trim(decode_entities(sec)) or nil, lvl, loc end end local function _get_section(content, name, level) if not (content and name) then return nil elseif find(name, "\n", 1, true) then error("Heading name cannot contain a newline.") end level = level and check_level(level) or nil name = trim(decode_entities(name)) local start for sec, lvl, loc in find_headings(content, level and 1 or nil, level) do if start and lvl <= level then return sub(content, start, loc - 1) elseif not start and (not level or lvl == level) and sec == name then start, level = loc, lvl end end return start and sub(content, start) end --[==[ A helper function to return the content of a page section. `content` is raw wikitext, `name` is the requested section, and `level` is an optional parameter that specifies the required section heading level. If `level` is not supplied, then the first section called `name` is returned. `name` can either be a string or table of section names. If a table, each name represents a section that has the next as a subsection. For example, { {"Spanish", "Noun"}} will return the first matching section called "Noun" under a section called "Spanish". These do not have to be at adjacent levels ("Noun" might be L4, while "Spanish" is L2). If `level` is given, it refers to the last name in the table (i.e. the name of the section to be returned). The returned section includes all of its subsections. If no matching section is found, return {nil}.]==] function export.get_section(content, names, level) if type(names) ~= "table" then return _get_section(content, names, level) end local i = 1 local name = names[i] if not name then error("Must specify at least 1 section.") end while true do local nxt_i = i + 1 local nxt = names[nxt_i] if nxt == nil then return _get_section(content, name, level) end content = _get_section(content, name) if content == nil then return nil elseif i == 6 then error("Not possible specify more than 6 sections: headings only go up to level 6.") end i = nxt_i name = names[i] end return content end end do local current_section --[==[ A function which returns the number of the page section which contains the current {#invoke}.]==] function export.get_current_section() if current_section ~= nil then return current_section end local extension_tag = (current_frame or get_current_frame()).extensionTag -- We determine the section via the heading strip marker count, since they're numbered sequentially, but the only way to do this is to generate a fake heading via frame:preprocess(). The native parser assigns each heading a unique marker, but frame:preprocess() will return copies of older markers if the heading is identical to one further up the page, so the fake heading has to be unique to the page. The best way to do this is to feed it a heading containing a nowiki marker (which we will need later), since those are always unique. local nowiki_marker = extension_tag(current_frame, "nowiki") -- Note: heading strip markers have a different syntax to the ones used for tags. local h = tonumber(match( current_frame:preprocess("=" .. nowiki_marker .. "="), "\127'\"`UNIQ%-%-h%-(%d+)%-%-QINU`\"'\127" )) -- For some reason, [[Special:ExpandTemplates]] doesn't generate a heading strip marker, so if that happens we simply abort early. if not h then return 0 end -- The only way to get the section number is to increment the heading count, so we store the offset in nowiki strip markers which can be retrieved by procedurally unstripping nowiki markers, counting backwards until we find a match. local n, offset = tonumber(match( nowiki_marker, "\127'\"`UNIQ%-%-nowiki%-([%dA-F]+)%-QINU`\"'\127" ), 16) while not offset and n > 0 do n = n - 1 offset = match( unstrip_nowiki(format("\127'\"`UNIQ--nowiki-%08X-QINU`\"'\127", n)), "^HEADING\1(%d+)" -- Prefix "HEADING\1" prevents collisions. ) end offset = offset and (offset + 1) or 0 extension_tag(current_frame, "nowiki", "HEADING\1" .. offset) current_section = h - offset return current_section end get_current_section = export.get_current_section end do local L2_sections local function get_L2_sections() L2_sections, get_L2_sections = mw.loadData("Module:headword/data").page.L2_sections, nil return L2_sections end --[==[ A function which returns the name of the L2 language section which contains the current {#invoke}.]==] function export.get_current_L2() local section = get_current_section() if section == 0 then return end while section > 0 do local L2 = (L2_sections or get_L2_sections())[section] if L2 then return L2 end section = section - 1 end end end return export fahzb5v4u71j961eqb048ciy8nk4h06 وحدة:links/ملعب2 828 230988 1097575 1003448 2026-07-26T18:30:06Z ForzaGreen 28665 إفراغ وإدراج للحذف السريع: نسخة أقدم من [[وحدة:links]]، لم يعد يستعملها شيء. 1097575 Scribunto text/plain -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش الوحدة:links/ملعب2]]. -- -- نسخة أقدم من [[وحدة:links]]، لم يعد يستعملها شيء. -- (2026-07-26) k4kiirz2bbv09ufkhq57ophklp55nkd 1097584 1097575 2026-07-26T18:50:28Z ForzaGreen 28665 إدراج للحذف السريع بقالب شطب 1097584 Scribunto text/plain -- {{شطب|نسخة أقدم من [[وحدة:links]] لم تختلف عنها إلا بإعادة توجيه أسماء الوحدات، ولم يعد يستعملها شيء.}} -- -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع (2026-07-26). -- نسخة أقدم من [[وحدة:links]] لم تختلف عنها إلا بإعادة توجيه أسماء الوحدات، ولم يعد يستعملها شيء. -- -- ملاحظة: نموذج محتوى صفحات الوحدات هو Scribunto، فلا يُصيَّر قالب {{شطب}} -- هنا ولا تدخل الصفحة في تصنيف الحذف السريع؛ القالب الفعّال في [[نقاش الوحدة:links/ملعب2]]. rgin8ibmcny7tips9ntazd5zxrwnamt وحدة:scripts/ملعب2 828 231061 1097579 1003427 2026-07-26T18:30:46Z ForzaGreen 28665 إفراغ وإدراج للحذف السريع: نسخة مطابقة حرفيًّا لـ[[وحدة:scripts]]، لم يعد يستعملها شيء. 1097579 Scribunto text/plain -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش الوحدة:scripts/ملعب2]]. -- -- نسخة مطابقة حرفيًّا لـ[[وحدة:scripts]]، لم يعد يستعملها شيء. -- (2026-07-26) 47pejzd6a1y85q1iv13ktgvx1qp9ukf 1097586 1097579 2026-07-26T18:50:48Z ForzaGreen 28665 إدراج للحذف السريع بقالب شطب 1097586 Scribunto text/plain -- {{شطب|نسخة مطابقة حرفيًّا لـ[[وحدة:scripts]]، ولم يعد يستعملها شيء.}} -- -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع (2026-07-26). -- نسخة مطابقة حرفيًّا لـ[[وحدة:scripts]]، ولم يعد يستعملها شيء. -- -- ملاحظة: نموذج محتوى صفحات الوحدات هو Scribunto، فلا يُصيَّر قالب {{شطب}} -- هنا ولا تدخل الصفحة في تصنيف الحذف السريع؛ القالب الفعّال في [[نقاش الوحدة:scripts/ملعب2]]. 3kpalheu35000epqwwrl3k9p2vplca1 وحدة:headword/ملعب2 828 231094 1097573 1003476 2026-07-26T18:29:46Z ForzaGreen 28665 إفراغ وإدراج للحذف السريع: الدالتان المستعملتان منها مطابقتان لما في [[وحدة:headword]]، وقد حُوِّلت [[وحدة:inflection utilities]] إليها. 1097573 Scribunto text/plain -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش الوحدة:headword/ملعب2]]. -- -- الدالتان المستعملتان منها مطابقتان لما في [[وحدة:headword]]، وقد حُوِّلت [[وحدة:inflection utilities]] إليها. -- (2026-07-26) 0tizuwla8y58b8cve1tfiy3wfj4ynv1 1097583 1097573 2026-07-26T18:50:18Z ForzaGreen 28665 إدراج للحذف السريع بقالب شطب 1097583 Scribunto text/plain -- {{شطب|الدالتان المستعملتان منها مطابقتان لما في [[وحدة:headword]]، وقد حُوِّلت [[وحدة:inflection utilities]] إليها.}} -- -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع (2026-07-26). -- الدالتان المستعملتان منها مطابقتان لما في [[وحدة:headword]]، وقد حُوِّلت [[وحدة:inflection utilities]] إليها. -- -- ملاحظة: نموذج محتوى صفحات الوحدات هو Scribunto، فلا يُصيَّر قالب {{شطب}} -- هنا ولا تدخل الصفحة في تصنيف الحذف السريع؛ القالب الفعّال في [[نقاش الوحدة:headword/ملعب2]]. q6zftd6gt2fqex0fixj8wam6urzqgrh وحدة:ar-verb/مختبر 828 231313 1097564 1096875 2026-07-26T17:40:43Z ForzaGreen 28665 توسيع الاختبارات: تغطية الأوزان الـ22 والضمائر والمجهول والأفعال الشاذّة وحواشي الحركات (235 تحقّقًا) 1097564 Scribunto text/plain -- Unit tests for [[Module:ar-verb]]. Click talk page to run tests. -- اختبارات الوحدة. انقر على صفحة النقاش لعرض النتائج -- 🚧 Work In Progress !! local tests = require("Module:UnitTests") local m_ar_verb = require("Module:ar-verb") -- ═══════════════════════════════════════════════════════════ -- Utilities required for some tests -- ═══════════════════════════════════════════════════════════ local m_string_utilities = require("Module:string utilities") local u = m_string_utilities.char -- ═══════════════════════════════════════════════════════════ function tests:test_conj_form_I() local result = m_ar_verb.do_generate_forms({["جذر"] = "كتب", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("كتب، فعَل يفعُل، هو، الماضي", "كَتَبَ", result.forms.past_3ms[1].form) self:equals("كتب، فعَل يفعُل، هو، المضارع", "يَكْتُبُ", result.forms.ind_3ms[1].form) self:equals("كتب، فعَل يفعُل، أنتَ، الأمر", "اُكْتُبْ", result.forms.imp_2ms[1].form) self:equals("كتب، فعَل يفعُل، اسم الفاعل", "كَاتِب", result.forms.ap[1].form) self:equals("كتب، فعَل يفعُل، اسم المفعول", "مَكْتُوب", result.forms.pp[1].form) self:equals("كتب، فعَل يفعُل، له معلوم", true, result.has_active) self:equals("كتب، فعَل يفعُل، له مجهول", true, result.has_passive) self:equals("كتب، فعَل يفعُل، الوزن I", "I", result.verb_forms[1]) end function tests:test_conj_form_II() local result = m_ar_verb.do_generate_forms({["جذر"] = "درس", ["وزن"] = "فعّل"}, "ar-conj") self:equals("درس، فعّل، هو، الماضي", "دَرَّسَ", result.forms.past_3ms[1].form) self:equals("درس، فعّل، هو، المضارع", "يُدَرِّسُ", result.forms.ind_3ms[1].form) self:equals("درس، فعّل، أنتَ، الأمر", "دَرِّسْ", result.forms.imp_2ms[1].form) self:equals("درس، فعّل، اسم الفاعل", "مُدَرِّس", result.forms.ap[1].form) self:equals("درس، فعّل، اسم المفعول", "مُدَرَّس", result.forms.pp[1].form) self:equals("درس، فعّل، المصدر", "تَدْرِيس", result.forms.vn[1].form) end function tests:test_conj_form_III() local result = m_ar_verb.do_generate_forms({["جذر"] = "قتل", ["وزن"] = "فاعل"}, "ar-conj") self:equals("قتل، فاعل، هو، الماضي", "قَاتَلَ", result.forms.past_3ms[1].form) self:equals("قتل، فاعل، هو، المضارع", "يُقَاتِلُ", result.forms.ind_3ms[1].form) self:equals("قتل، فاعل، أنتَ، الأمر", "قَاتِلْ", result.forms.imp_2ms[1].form) self:equals("قتل، فاعل، اسم الفاعل", "مُقَاتِل", result.forms.ap[1].form) self:equals("قتل، فاعل، اسم المفعول", "مُقَاتَل", result.forms.pp[1].form) end function tests:test_conj_form_IV() local result = m_ar_verb.do_generate_forms({["جذر"] = "رسل", ["وزن"] = "أفعل"}, "ar-conj") self:equals("رسل، أفعل، هو، الماضي", "أَرْسَلَ", result.forms.past_3ms[1].form) self:equals("رسل، أفعل، هو، المضارع", "يُرْسِلُ", result.forms.ind_3ms[1].form) self:equals("رسل، أفعل، أنتَ، الأمر", "أَرْسِلْ", result.forms.imp_2ms[1].form) self:equals("رسل، أفعل، اسم الفاعل", "مُرْسِل", result.forms.ap[1].form) self:equals("رسل، أفعل، اسم المفعول", "مُرْسَل", result.forms.pp[1].form) end -- وزن «أفعل» من الأجوف: تُعَلّ العين ألفًا (أَفَالَ) في الواوي واليائي معًا، -- تبعًا لإعلالها في الثلاثي المجرد (معجم متن اللغة). function tests:test_conj_form_IV_hollow() -- أجوف واوي local qwl = m_ar_verb.do_generate_forms({["جذر"] = "قول", ["وزن"] = "أفعل"}, "ar-conj") self:equals("قول، أفعل، هو، الماضي", "أَقَالَ", qwl.forms.past_3ms[1].form) -- أجوف يائي: يُعَلّ كنظيره الواوي local yaa_cases = { {"تيس", "أَتَاسَ"}, {"طيب", "أَطَابَ"}, {"بين", "أَبَانَ"}, {"ضيع", "أَضَاعَ"}, {"طيح", "أَطَاحَ"}, } for _, case in ipairs(yaa_cases) do local root, expected = case[1], case[2] local result = m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = "أفعل"}, "ar-conj") self:equals(root .. "، أفعل، هو، الماضي", expected, result.forms.past_3ms[1].form) end -- الاستثناء يخصّ «أفعل» وحده؛ الأوزان الأخرى من الأجوف اليائي تبقى معلّة local tys_x = m_ar_verb.do_generate_forms({["جذر"] = "تيس", ["وزن"] = "استفعل"}, "ar-conj") self:equals("تيس، استفعل، هو، الماضي", "اِسْتَتَاسَ", tys_x.forms.past_3ms[1].form) end -- 7 جذور يائية شاذّة يبقى وزن «أفعل» منها صحيحًا (أَفْيَلَ) — أفعال مسموعة موثّقة، -- لا الصفة المشبهة (مثل أَخْيَف/خَيْفاء). انظر SOUND_FORM_IV_ROOTS في الوحدة. function tests:test_conj_form_IV_sound_exceptions() local cases = { {"خيل", "أَخْيَلَ"}, -- أخيلت السماء — المعجم الوسيط 1/266 {"غيل", "أَغْيَلَ"}, -- أغيلته سقته الغَيْل — لسان العرب 11/111 {"حيج", "أَحْيَجَ"}, -- أحيجت الأرض — تاج العروس 5/500 {"حين", "أَحْيَنَ"}, -- أحين القوم — المعجم الوسيط 1/212 {"خيف", "أَخْيَفَ"}, -- أخيف الحاج — كتاب الأفعال لابن القوطية 35 {"ريف", "أَرْيَفَ"}, -- أريفت الأرض — المحيط في اللغة 10/256 {"زين", "أَزْيَنَ"}, -- أزينت الأرض — مقاييس اللغة 3/41 } for _, case in ipairs(cases) do local root, expected = case[1], case[2] local result = m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = "أفعل"}, "ar-conj") self:equals(root .. "، أفعل، هو، الماضي (شاذّ صحيح)", expected, result.forms.past_3ms[1].form) end end function tests:test_conj_weak() local result = m_ar_verb.do_generate_forms({["جذر"] = "قول", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("قول، فعَل يفعُل، هو، الماضي", "قَالَ", result.forms.past_3ms[1].form) self:equals("قول، فعَل يفعُل، هو، المضارع", "يَقُولُ", result.forms.ind_3ms[1].form) self:equals("قول، فعَل يفعُل، أنتَ، الأمر", "قُلْ", result.forms.imp_2ms[1].form) local result = m_ar_verb.do_generate_forms({["جذر"] = "رمي", ["وزن"] = "فعَل يفعِل"}, "ar-conj") self:equals("رمي، فعَل يفعِل، هو، الماضي", "رَمَى", result.forms.past_3ms[1].form) self:equals("رمي، فعَل يفعِل، هو، المضارع", "يَرْمِي", result.forms.ind_3ms[1].form) self:equals("رمي، فعَل يفعِل، أنتَ، الأمر", "اِرْمِ", result.forms.imp_2ms[1].form) self:equals("رمي، فعَل يفعِل، اسم الفاعل", "رَامٍ", result.forms.ap[1].form) local result = m_ar_verb.do_generate_forms({["جذر"] = "م_د_د", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("مدد، فعَل يفعُل، مضعَّف، هو، الماضي", "مَدَّ", result.forms.past_3ms[1].form) local result = m_ar_verb.do_generate_forms({["جذر"] = "وصل", ["وزن"] = "فعَل يفعِل"}, "ar-conj") self:equals("وصل، فعَل يفعِل، مثال، هو، الماضي", "وَصَلَ", result.forms.past_3ms[1].form) self:equals("وصل، فعَل يفعِل، مثال، هو، المضارع", "يَصِلُ", result.forms.ind_3ms[1].form) self:equals("وصل، فعَل يفعِل، مثال، أنت، الأمر", "صِلْ", result.forms.imp_2ms[1].form) end function tests:test_conj_with_underscores() local result = m_ar_verb.do_generate_forms({["جذر"] = "ك_ت_ب", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("كتب، فعَل يفعُل، هو، الماضي", "كَتَبَ", result.forms.past_3ms[1].form) self:equals("كتب، فعَل يفعُل، هو، المضارع", "يَكْتُبُ", result.forms.ind_3ms[1].form) end function tests:test_conj_with_spaces() local result = m_ar_verb.do_generate_forms({["جذر"] = "ك ت ب", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("كتب، فعَل يفعُل، هو، الماضي", "كَتَبَ", result.forms.past_3ms[1].form) self:equals("كتب، فعَل يفعُل، هو، المضارع", "يَكْتُبُ", result.forms.ind_3ms[1].form) end function tests:test_conj_passive_types() local result_pass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.pass>"}, "ar-conj") self:equals("كتب، مبني للمجهول فقط", "pass", result_pass.passive) local result_ipass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.ipass>"}, "ar-conj") self:equals("كتب، مجهول غير شخصي", "ipass", result_ipass.passive) local result_nopass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.nopass>"}, "ar-conj") self:equals("كتب، بدون مجهول", "nopass", result_nopass.passive) self:equals("كتب، بدون مجهول، has_passive خطأ", false, result_nopass.has_passive) end function tests:test_conj_overrides() local result = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.vn:كِتَابَة>"}, "ar-conj") self:equals("كتب، تجاوز المصدر", "كِتَابَة", result.forms.vn[1].form) end function tests:test_infer_radicals() local result = m_ar_verb.infer_radicals({ headword = "كتب", vform = "I" }) self:equals("كتب، نوع الضعف: صحيح", "sound", result.weakness) self:equals("كتب، حرف الجذر الأول: ك", "ك", result.rad1) self:equals("كتب، حرف الجذر الثاني: ت", "ت", result.rad2) self:equals("كتب، حرف الجذر الثالث: ب", "ب", result.rad3) local U = u(0x064F) -- ḍamma local result = m_ar_verb.infer_radicals({ headword = "قال", vform = "I", nonpast_vowel = U }) self:equals("قال، نوع الضعف: أجوف", "hollow", result.weakness) self:equals("قال، حرف الجذر الأول: ق", "ق", result.rad1) self:equals("قال، حرف الجذر الثاني: و (مستنتج)", "و", result.rad2) self:equals("قال، حرف الجذر الثالث: ل", "ل", result.rad3) local result = m_ar_verb.infer_radicals({ headword = "رمى", vform = "I" }) self:equals("رمى، نوع الضعف: ناقص", "final-weak", result.weakness) self:equals("رمى، حرف الجذر الأول: ر", "ر", result.rad1) self:equals("رمى، حرف الجذر الثاني: م", "م", result.rad2) self:equals("رمى، حرف الجذر الثالث: ي", "ي", result.rad3) end function tests:test_infer_participle_vocalization() local result = m_ar_verb.infer_participle_vocalization("كاتب", "I", "sound", true) self:equals("كاتب، تشكيل اسم الفاعل", "كَاتِب", result) local result = m_ar_verb.infer_participle_vocalization("مكتوب", "I", "sound", false) self:equals("مكتوب، تشكيل اسم المفعول", "مَكْتُوب", result) local result = m_ar_verb.infer_participle_vocalization("مدرس", "II", "sound", true) self:equals("مدرس، فعّل، تشكيل اسم الفاعل", "مُدَرِّس", result) end -- ═══════════════════════════════════════════════════════════ -- Tests for classify_triliteral_verb function -- ═══════════════════════════════════════════════════════════ -- الصحيح function tests:test_verb_classify_sound() self:equals("كتب", "صحيح سالم", m_ar_verb.classify_triliteral_verb("ك", "ت", "ب")) self:equals("ذهب", "صحيح سالم", m_ar_verb.classify_triliteral_verb("ذ", "ه", "ب")) self:equals("فعل", "صحيح سالم", m_ar_verb.classify_triliteral_verb("ف", "ع", "ل")) self:equals("مدّ", "صحيح مُضعَّف", m_ar_verb.classify_triliteral_verb("م", "د", "د")) self:equals("شدّ", "صحيح مُضعَّف", m_ar_verb.classify_triliteral_verb("ش", "د", "د")) self:equals("عدّ", "صحيح مُضعَّف", m_ar_verb.classify_triliteral_verb("ع", "د", "د")) self:equals("أخذ", "صحيح مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "خ", "ذ")) self:equals("أكل", "صحيح مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "ك", "ل")) self:equals("سأل", "صحيح مهموز العين", m_ar_verb.classify_triliteral_verb("س", "أ", "ل")) self:equals("قرأ", "صحيح مهموز اللام", m_ar_verb.classify_triliteral_verb("ق", "ر", "أ")) self:equals("أبأ", "صحيح مهموز الفاء واللام", m_ar_verb.classify_triliteral_verb("أ", "ب", "أ")) end -- المعتل function tests:test_verb_classify_weak() self:equals("وجد", "معتل مثال واوي", m_ar_verb.classify_triliteral_verb("و", "ج", "د")) self:equals("وقف", "معتل مثال واوي", m_ar_verb.classify_triliteral_verb("و", "ق", "ف")) self:equals("وضأ", "معتل مثال واوي مهموز اللام", m_ar_verb.classify_triliteral_verb("و", "ض", "أ")) self:equals("يسر", "معتل مثال يائي", m_ar_verb.classify_triliteral_verb("ي", "س", "ر")) self:equals("يئس", "معتل مثال يائي مهموز العين", m_ar_verb.classify_triliteral_verb("ي", "أ", "س")) self:equals("قال", "معتل أجوف واوي", m_ar_verb.classify_triliteral_verb("ق", "و", "ل")) self:equals("صام", "معتل أجوف واوي", m_ar_verb.classify_triliteral_verb("ص", "و", "م")) self:equals("باع", "معتل أجوف يائي", m_ar_verb.classify_triliteral_verb("ب", "ي", "ع")) self:equals("نام", "معتل أجوف واوي", m_ar_verb.classify_triliteral_verb("ن", "و", "م")) self:equals("دعا", "معتل ناقص واوي", m_ar_verb.classify_triliteral_verb("د", "ع", "و")) self:equals("سما", "معتل ناقص واوي", m_ar_verb.classify_triliteral_verb("س", "م", "و")) self:equals("رمى", "معتل ناقص يائي", m_ar_verb.classify_triliteral_verb("ر", "م", "ي")) self:equals("بكى", "معتل ناقص يائي", m_ar_verb.classify_triliteral_verb("ب", "ك", "ي")) -- لفيف مقرون (adjacent weak letters) self:equals("ويّ", "معتل لفيف مقرون", m_ar_verb.classify_triliteral_verb("و", "ي", "ي")) self:equals("أوى", "معتل لفيف مقرون مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "و", "ي")) -- لفيف مفروق (separated weak letters) self:equals("وقى", "معتل لفيف مفروق", m_ar_verb.classify_triliteral_verb("و", "ق", "ي")) self:equals("وأى", "معتل لفيف مفروق مهموز العين", m_ar_verb.classify_triliteral_verb("و", "أ", "ي")) -- Combined weaknesses self:equals("أول", "معتل أجوف واوي مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "و", "ل")) self:equals("قوأ", "معتل أجوف واوي مهموز اللام", m_ar_verb.classify_triliteral_verb("ق", "و", "أ")) self:equals("أيب", "معتل أجوف يائي مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "ي", "ب")) self:equals("جيأ", "معتل أجوف يائي مهموز اللام", m_ar_verb.classify_triliteral_verb("ج", "ي", "أ")) self:equals("رأو", "معتل ناقص واوي مهموز العين", m_ar_verb.classify_triliteral_verb("ر", "أ", "و")) self:equals("شأي", "معتل ناقص يائي مهموز العين", m_ar_verb.classify_triliteral_verb("ش", "أ", "ي")) self:equals("ودد", "معتل مثال واوي مُضعَّف", m_ar_verb.classify_triliteral_verb("و", "د", "د")) self:equals("ينن", "معتل مثال يائي مُضعَّف", m_ar_verb.classify_triliteral_verb("ي", "ن", "ن")) self:equals("وأب", "معتل مثال واوي مهموز العين", m_ar_verb.classify_triliteral_verb("و", "أ", "ب")) self:equals("أبب", "صحيح مُضعَّف مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "ب", "ب")) self:equals("أتى", "معتل ناقص يائي مهموز الفاء", m_ar_verb.classify_triliteral_verb("أ", "ت", "ي")) self:equals("قلا", "معتل ناقص واوي", m_ar_verb.classify_triliteral_verb("ق", "ل", "و")) self:equals("نما (و)", "معتل ناقص واوي", m_ar_verb.classify_triliteral_verb("ن", "م", "و")) self:equals("نمى (ي)", "معتل ناقص يائي", m_ar_verb.classify_triliteral_verb("ن", "م", "ي")) self:equals("يئس", "معتل مثال يائي مهموز العين", m_ar_verb.classify_triliteral_verb("ي", "أ", "س")) end -- ═══════════════════════════════════════════════════════════ -- تغطية كل الأوزان — فعل شاهد لكل وزن -- ═══════════════════════════════════════════════════════════ -- مختصر: يولّد التصريف ويعيد صيغة الخانة المطلوبة local function conj(root, pattern) return m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = pattern}, "ar-conj") end local function slot(result, name) local forms = result.forms[name] if not forms then return nil end local out = {} for i, f in ipairs(forms) do out[i] = f.form end return table.concat(out, "/") end -- الثلاثي المجرد: الأبواب الستة function tests:test_conj_form_I_all_vowel_patterns() local cases = { -- جذر، وزن، الماضي، المضارع، الأمر {"كتب", "فعَل يفعُل", "كَتَبَ", "يَكْتُبُ", "اُكْتُبْ"}, {"ضرب", "فعَل يفعِل", "ضَرَبَ", "يَضْرِبُ", "اِضْرِبْ"}, {"فتح", "فعَل يفعَل", "فَتَحَ", "يَفْتَحُ", "اِفْتَحْ"}, {"علم", "فعِل يفعَل", "عَلِمَ", "يَعْلَمُ", "اِعْلَمْ"}, {"كرم", "فعُل يفعُل", "كَرُمَ", "يَكْرُمُ", "اُكْرُمْ"}, {"حسب", "فعِل يفعِل", "حَسِبَ", "يَحْسِبُ", "اِحْسِبْ"}, } for _, c in ipairs(cases) do local root, pattern, past, pres, imp = c[1], c[2], c[3], c[4], c[5] local r = conj(root, pattern) self:equals(root .. "، " .. pattern .. "، الماضي", past, slot(r, "past_3ms")) self:equals(root .. "، " .. pattern .. "، المضارع", pres, slot(r, "ind_3ms")) self:equals(root .. "، " .. pattern .. "، الأمر", imp, slot(r, "imp_2ms")) end end -- المزيد: الأوزان V–XIII function tests:test_conj_derived_forms() local cases = { -- جذر، وزن، الماضي، المضارع، المصدر {"علم", "تفعّل", "تَعَلَّمَ", "يَتَعَلَّمُ", "تَعَلُّم"}, {"عون", "تفاعل", "تَعَاوَنَ", "يَتَعَاوَنُ", "تَعَاوُن"}, {"كسر", "انفعل", "اِنْكَسَرَ", "يَنْكَسِرُ", "اِنْكِسَار"}, {"جمع", "افتعل", "اِجْتَمَعَ", "يَجْتَمِعُ", "اِجْتِمَاع"}, {"حمر", "افعلّ", "اِحْمَرَّ", "يَحْمَرُّ", "اِحْمِرَار"}, {"خرج", "استفعل", "اِسْتَخْرَجَ", "يَسْتَخْرِجُ", "اِسْتِخْرَاج"}, {"حمر", "افعالّ", "اِحْمَارَّ", "يَحْمَارُّ", "اِحْمِيرَار"}, {"عشب", "افعوعل", "اِعْشَوْشَبَ", "يَعْشَوْشِبُ", "اِعْشِيشَاب"}, {"جلذ", "افعوّل", "اِجْلَوَّذَ", "يَجْلَوِّذُ", "اِجْلِوَّاذ"}, } for _, c in ipairs(cases) do local root, pattern, past, pres, vn = c[1], c[2], c[3], c[4], c[5] local r = conj(root, pattern) self:equals(root .. "، " .. pattern .. "، الماضي", past, slot(r, "past_3ms")) self:equals(root .. "، " .. pattern .. "، المضارع", pres, slot(r, "ind_3ms")) self:equals(root .. "، " .. pattern .. "، المصدر", vn, slot(r, "vn")) end end -- الرباعي: مجرده ومزيده function tests:test_conj_quadriliteral() local cases = { {"دحرج", "فعلل", "دَحْرَجَ", "يُدَحْرِجُ", "دَحْرَجَة"}, {"دحرج", "تفعلل", "تَدَحْرَجَ", "يَتَدَحْرَجُ", "تَدَحْرُج"}, {"حرجم", "افعنلل", "اِحْرَنْجَمَ", "يَحْرَنْجِمُ", "اِحْرِنْجَام"}, {"قشعر", "افعللّ", "اِقْشَعَرَّ", "يَقْشَعِرُّ", "اِقْشِعْرَار"}, } for _, c in ipairs(cases) do local root, pattern, past, pres, vn = c[1], c[2], c[3], c[4], c[5] local r = conj(root, pattern) self:equals(root .. "، " .. pattern .. "، الماضي", past, slot(r, "past_3ms")) self:equals(root .. "، " .. pattern .. "، المضارع", pres, slot(r, "ind_3ms")) self:equals(root .. "، " .. pattern .. "، المصدر", vn, slot(r, "vn")) end end -- الضمائر والأزمنة: نموذج كامل من «كتب» function tests:test_conj_persons_and_moods() local r = conj("كتب", "فعَل يفعُل") local cases = { {"past_1s", "كَتَبْتُ"}, {"past_2ms", "كَتَبْتَ"}, {"past_3fs", "كَتَبَتْ"}, {"past_3fp", "كَتَبْنَ"}, {"ind_1s", "أَكْتُبُ"}, {"ind_2fs", "تَكْتُبِينَ"}, {"ind_3mp", "يَكْتُبُونَ"}, {"sub_3ms", "يَكْتُبَ"}, {"juss_3ms", "يَكْتُبْ"}, {"imp_2fs", "اُكْتُبِي"}, {"imp_2mp", "اُكْتُبُوا"}, {"imp_2fp", "اُكْتُبْنَ"}, } for _, c in ipairs(cases) do self:equals("كتب، " .. c[1], c[2], slot(r, c[1])) end end -- المبني للمجهول function tests:test_conj_passive_forms() local r = conj("كتب", "فعَل يفعُل") self:equals("كتب، الماضي المجهول", "كُتِبَ", slot(r, "past_pass_3ms")) self:equals("كتب، المضارع المجهول", "يُكْتَبُ", slot(r, "ind_pass_3ms")) self:equals("كتب، المنصوب المجهول", "يُكْتَبَ", slot(r, "sub_pass_3ms")) self:equals("كتب، المجزوم المجهول", "يُكْتَبْ", slot(r, "juss_pass_3ms")) local r2 = conj("درس", "فعّل") self:equals("درس، فعّل، الماضي المجهول", "دُرِّسَ", slot(r2, "past_pass_3ms")) self:equals("درس، فعّل، المضارع المجهول", "يُدَرَّسُ", slot(r2, "ind_pass_3ms")) end -- الأفعال الشاذّة المعالَجة داخل الوحدة function tests:test_conj_irregular_verbs() local raa = conj("رأي", "فعَل يفعَل") self:equals("رأى، الماضي", "رَأَى", slot(raa, "past_3ms")) self:equals("رأى، المضارع", "يَرَى", slot(raa, "ind_3ms")) self:equals("رأى، الأمر", "رَ", slot(raa, "imp_2ms")) local araa = conj("رأي", "أفعل") self:equals("أرى، الماضي", "أَرَى", slot(araa, "past_3ms")) self:equals("أرى، المضارع", "يُرِي", slot(araa, "ind_3ms")) local akal = conj("أكل", "فعَل يفعُل") self:equals("أكل، الماضي", "أَكَلَ", slot(akal, "past_3ms")) self:equals("أكل، الأمر (محذوف)", "كُلْ", slot(akal, "imp_2ms")) local akhadh = conj("أخذ", "فعَل يفعُل") self:equals("أخذ، الأمر (محذوف)", "خُذْ", slot(akhadh, "imp_2ms")) -- أمر: للأمر صيغتان، المحذوفة والتامّة بعد حرف عطف local amar = conj("أمر", "فعَل يفعُل") self:equals("أمر، الأمر", "مُرْ/اُؤْمُرْ", slot(amar, "imp_2ms")) -- سأل: للمجزوم والأمر صيغتان local saal = conj("سأل", "فعَل يفعَل") self:equals("سأل، الأمر", "اِسْأَلْ/سَلْ", slot(saal, "imp_2ms")) self:equals("سأل، المجزوم", "يَسْأَلْ/يَسَلْ", slot(saal, "juss_3ms")) -- حيّ/حيي: الإدغام والفكّ local hayy = conj("حيي", "فعِل يفعَل") self:equals("حيّ، الماضي (مدغم ومفكوك)", "حَيَّ/حَيِيَ", slot(hayy, "past_3ms")) self:equals("حيّ، المضارع", "يَحْيَا", slot(hayy, "ind_3ms")) end -- الإدغام بالتاء (var:short/long) وصيغ الإدغام المختصرة (reduced) function tests:test_conj_variants_and_reduced() local short = m_ar_verb.do_generate_forms({[1] = "حيي<I/i~a.var:short>"}, "ar-conj") self:equals("حيي، var:short، الماضي", "حَيَّ", slot(short, "past_3ms")) local long = m_ar_verb.do_generate_forms({[1] = "حيي<I/i~a.var:long>"}, "ar-conj") self:equals("حيي، var:long، الماضي", "حَيِيَ", slot(long, "past_3ms")) -- اتّخذ: افتعل من «أخذ» بإبدال الهمزة تاءً وإدغامها local ittakhadha = m_ar_verb.do_generate_forms({[1] = "اتخذ<VIII.reduced>"}, "ar-conj") self:equals("اتّخذ، الماضي", "اِتَّخَذَ", slot(ittakhadha, "past_3ms")) self:equals("اتّخذ، المضارع", "يَتَّخِذُ", slot(ittakhadha, "ind_3ms")) self:equals("اتّخذ، المصدر", "اِتِّخَاذ", slot(ittakhadha, "vn")) -- ادّارأ: تفاعل مدغم (اِدَّارَأَ عوض تَدَارَأَ) local iddaaraa = m_ar_verb.do_generate_forms({[1] = "ادارأ<VI.reduced>"}, "ar-conj") self:equals("ادّارأ، الماضي", "اِدَّارَأَ", slot(iddaaraa, "past_3ms")) -- اسطاع: استفعل محذوف التاء local istaa = m_ar_verb.do_generate_forms({[1] = "اسطاع<X.reduced>"}, "ar-conj") self:equals("اسطاع، الماضي", "اِسْطَاعَ", slot(istaa, "past_3ms")) self:equals("اسطاع، المضارع", "يَسْطِيعُ", slot(istaa, "ind_3ms")) end -- المعتل: المثال والناقص واللفيف function tests:test_conj_weak_extended() local cases = { -- جذر، وزن، الماضي، المضارع، الأمر، اسم الفاعل {"وعد", "فعَل يفعِل", "وَعَدَ", "يَعِدُ", "عِدْ", "وَاعِد"}, -- مثال واوي {"دعو", "فعَل يفعُل", "دَعَا", "يَدْعُو", "اُدْعُ", "دَاعٍ"}, -- ناقص واوي {"شوي", "فعَل يفعِل", "شَوَى", "يَشْوِي", "اِشْوِ", "شَاوٍ"}, -- ناقص يائي {"وقي", "فعَل يفعِل", "وَقَى", "يَقِي", "قِ", "وَاقٍ"}, -- لفيف مفروق } for _, c in ipairs(cases) do local root, pattern = c[1], c[2] local r = conj(root, pattern) self:equals(root .. "، الماضي", c[3], slot(r, "past_3ms")) self:equals(root .. "، المضارع", c[4], slot(r, "ind_3ms")) self:equals(root .. "، الأمر", c[5], slot(r, "imp_2ms")) self:equals(root .. "، اسم الفاعل", c[6], slot(r, "ap")) end end -- حواشي على حركات الماضي والمضارع، مثل <I/a[نادر]~u>. -- كانت ترمي خطأ لوا: verify_vowel كانت تقارن الكائن نفسه لا قيمته. function tests:test_conj_vowel_footnotes() local a = m_ar_verb.do_generate_forms({[1] = "كتب<I/a[نادر]~u>"}, "ar-conj") self:equals("كتب، حاشية على حركة الماضي", "كَتَبَ", a.forms.past_3ms[1].form) self:equals("كتب، حاشية على حركة الماضي، المضارع", "يَكْتُبُ", a.forms.ind_3ms[1].form) local b = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u[نادر]>"}, "ar-conj") self:equals("كتب، حاشية على حركة المضارع", "كَتَبَ", b.forms.past_3ms[1].form) -- أجوف: يمرّ عبر infer_radicals، وهو موضع الخطأ الأصلي local c = m_ar_verb.do_generate_forms({[1] = "قال<I/a[نادر]~u>"}, "ar-conj") self:equals("قال، حاشية على حركة الماضي", "قَالَ", c.forms.past_3ms[1].form) end -- q/qq/l/ll أُزيلت لأنها تستدعي [[وحدة:pron qualifier]] غير الموجودة في ويكاموس العربي. -- المطلوب الآن خطأ واضح في التحليل، لا خطأ لوا. function tests:test_conj_qualifier_modifiers_rejected() local ok, err = pcall(m_ar_verb.do_generate_forms, {[1] = "كتب<I/a~u.vn:كِتَابَة<q:نادر>>"}, "ar-conj") self:equals("q: مرفوضة", false, ok) self:equals("q: رسالة خطأ واضحة", true, not not tostring(err):find("Unrecognized prefix")) self:equals("q: لا تستدعي pron qualifier", false, not not tostring(err):find("pron qualifier")) end return tests gxxuawdc69wz5e1spoemk8q7if27660 1097566 1097564 2026-07-26T17:53:08Z ForzaGreen 28665 ترتيب وسائط equals ليطابق وحدة:UnitTests: (الاسم، الناتج، المتوقَّع) 1097566 Scribunto text/plain -- Unit tests for [[Module:ar-verb]]. Click talk page to run tests. -- اختبارات الوحدة. انقر على صفحة النقاش لعرض النتائج -- 🚧 Work In Progress !! local tests = require("Module:UnitTests") local m_ar_verb = require("Module:ar-verb") -- ═══════════════════════════════════════════════════════════ -- Utilities required for some tests -- ═══════════════════════════════════════════════════════════ local m_string_utilities = require("Module:string utilities") local u = m_string_utilities.char -- ═══════════════════════════════════════════════════════════ function tests:test_conj_form_I() local result = m_ar_verb.do_generate_forms({["جذر"] = "كتب", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("كتب، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "كَتَبَ") self:equals("كتب، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَكْتُبُ") self:equals("كتب، فعَل يفعُل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "اُكْتُبْ") self:equals("كتب، فعَل يفعُل، اسم الفاعل", result.forms.ap[1].form, "كَاتِب") self:equals("كتب، فعَل يفعُل، اسم المفعول", result.forms.pp[1].form, "مَكْتُوب") self:equals("كتب، فعَل يفعُل، له معلوم", result.has_active, true) self:equals("كتب، فعَل يفعُل، له مجهول", result.has_passive, true) self:equals("كتب، فعَل يفعُل، الوزن I", result.verb_forms[1], "I") end function tests:test_conj_form_II() local result = m_ar_verb.do_generate_forms({["جذر"] = "درس", ["وزن"] = "فعّل"}, "ar-conj") self:equals("درس، فعّل، هو، الماضي", result.forms.past_3ms[1].form, "دَرَّسَ") self:equals("درس، فعّل، هو، المضارع", result.forms.ind_3ms[1].form, "يُدَرِّسُ") self:equals("درس، فعّل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "دَرِّسْ") self:equals("درس، فعّل، اسم الفاعل", result.forms.ap[1].form, "مُدَرِّس") self:equals("درس، فعّل، اسم المفعول", result.forms.pp[1].form, "مُدَرَّس") self:equals("درس، فعّل، المصدر", result.forms.vn[1].form, "تَدْرِيس") end function tests:test_conj_form_III() local result = m_ar_verb.do_generate_forms({["جذر"] = "قتل", ["وزن"] = "فاعل"}, "ar-conj") self:equals("قتل، فاعل، هو، الماضي", result.forms.past_3ms[1].form, "قَاتَلَ") self:equals("قتل، فاعل، هو، المضارع", result.forms.ind_3ms[1].form, "يُقَاتِلُ") self:equals("قتل، فاعل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "قَاتِلْ") self:equals("قتل، فاعل، اسم الفاعل", result.forms.ap[1].form, "مُقَاتِل") self:equals("قتل، فاعل، اسم المفعول", result.forms.pp[1].form, "مُقَاتَل") end function tests:test_conj_form_IV() local result = m_ar_verb.do_generate_forms({["جذر"] = "رسل", ["وزن"] = "أفعل"}, "ar-conj") self:equals("رسل، أفعل، هو، الماضي", result.forms.past_3ms[1].form, "أَرْسَلَ") self:equals("رسل، أفعل، هو، المضارع", result.forms.ind_3ms[1].form, "يُرْسِلُ") self:equals("رسل، أفعل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "أَرْسِلْ") self:equals("رسل، أفعل، اسم الفاعل", result.forms.ap[1].form, "مُرْسِل") self:equals("رسل، أفعل، اسم المفعول", result.forms.pp[1].form, "مُرْسَل") end -- وزن «أفعل» من الأجوف: تُعَلّ العين ألفًا (أَفَالَ) في الواوي واليائي معًا، -- تبعًا لإعلالها في الثلاثي المجرد (معجم متن اللغة). function tests:test_conj_form_IV_hollow() -- أجوف واوي local qwl = m_ar_verb.do_generate_forms({["جذر"] = "قول", ["وزن"] = "أفعل"}, "ar-conj") self:equals("قول، أفعل، هو، الماضي", qwl.forms.past_3ms[1].form, "أَقَالَ") -- أجوف يائي: يُعَلّ كنظيره الواوي local yaa_cases = { {"تيس", "أَتَاسَ"}, {"طيب", "أَطَابَ"}, {"بين", "أَبَانَ"}, {"ضيع", "أَضَاعَ"}, {"طيح", "أَطَاحَ"}, } for _, case in ipairs(yaa_cases) do local root, expected = case[1], case[2] local result = m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = "أفعل"}, "ar-conj") self:equals(root .. "، أفعل، هو، الماضي", result.forms.past_3ms[1].form, expected) end -- الاستثناء يخصّ «أفعل» وحده؛ الأوزان الأخرى من الأجوف اليائي تبقى معلّة local tys_x = m_ar_verb.do_generate_forms({["جذر"] = "تيس", ["وزن"] = "استفعل"}, "ar-conj") self:equals("تيس، استفعل، هو، الماضي", tys_x.forms.past_3ms[1].form, "اِسْتَتَاسَ") end -- 7 جذور يائية شاذّة يبقى وزن «أفعل» منها صحيحًا (أَفْيَلَ) — أفعال مسموعة موثّقة، -- لا الصفة المشبهة (مثل أَخْيَف/خَيْفاء). انظر SOUND_FORM_IV_ROOTS في الوحدة. function tests:test_conj_form_IV_sound_exceptions() local cases = { {"خيل", "أَخْيَلَ"}, -- أخيلت السماء — المعجم الوسيط 1/266 {"غيل", "أَغْيَلَ"}, -- أغيلته سقته الغَيْل — لسان العرب 11/111 {"حيج", "أَحْيَجَ"}, -- أحيجت الأرض — تاج العروس 5/500 {"حين", "أَحْيَنَ"}, -- أحين القوم — المعجم الوسيط 1/212 {"خيف", "أَخْيَفَ"}, -- أخيف الحاج — كتاب الأفعال لابن القوطية 35 {"ريف", "أَرْيَفَ"}, -- أريفت الأرض — المحيط في اللغة 10/256 {"زين", "أَزْيَنَ"}, -- أزينت الأرض — مقاييس اللغة 3/41 } for _, case in ipairs(cases) do local root, expected = case[1], case[2] local result = m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = "أفعل"}, "ar-conj") self:equals(root .. "، أفعل، هو، الماضي (شاذّ صحيح)", result.forms.past_3ms[1].form, expected) end end function tests:test_conj_weak() local result = m_ar_verb.do_generate_forms({["جذر"] = "قول", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("قول، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "قَالَ") self:equals("قول، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَقُولُ") self:equals("قول، فعَل يفعُل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "قُلْ") local result = m_ar_verb.do_generate_forms({["جذر"] = "رمي", ["وزن"] = "فعَل يفعِل"}, "ar-conj") self:equals("رمي، فعَل يفعِل، هو، الماضي", result.forms.past_3ms[1].form, "رَمَى") self:equals("رمي، فعَل يفعِل، هو، المضارع", result.forms.ind_3ms[1].form, "يَرْمِي") self:equals("رمي، فعَل يفعِل، أنتَ، الأمر", result.forms.imp_2ms[1].form, "اِرْمِ") self:equals("رمي، فعَل يفعِل، اسم الفاعل", result.forms.ap[1].form, "رَامٍ") local result = m_ar_verb.do_generate_forms({["جذر"] = "م_د_د", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("مدد، فعَل يفعُل، مضعَّف، هو، الماضي", result.forms.past_3ms[1].form, "مَدَّ") local result = m_ar_verb.do_generate_forms({["جذر"] = "وصل", ["وزن"] = "فعَل يفعِل"}, "ar-conj") self:equals("وصل، فعَل يفعِل، مثال، هو، الماضي", result.forms.past_3ms[1].form, "وَصَلَ") self:equals("وصل، فعَل يفعِل، مثال، هو، المضارع", result.forms.ind_3ms[1].form, "يَصِلُ") self:equals("وصل، فعَل يفعِل، مثال، أنت، الأمر", result.forms.imp_2ms[1].form, "صِلْ") end function tests:test_conj_with_underscores() local result = m_ar_verb.do_generate_forms({["جذر"] = "ك_ت_ب", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("كتب، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "كَتَبَ") self:equals("كتب، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَكْتُبُ") end function tests:test_conj_with_spaces() local result = m_ar_verb.do_generate_forms({["جذر"] = "ك ت ب", ["وزن"] = "فعَل يفعُل"}, "ar-conj") self:equals("كتب، فعَل يفعُل، هو، الماضي", result.forms.past_3ms[1].form, "كَتَبَ") self:equals("كتب، فعَل يفعُل، هو، المضارع", result.forms.ind_3ms[1].form, "يَكْتُبُ") end function tests:test_conj_passive_types() local result_pass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.pass>"}, "ar-conj") self:equals("كتب، مبني للمجهول فقط", result_pass.passive, "pass") local result_ipass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.ipass>"}, "ar-conj") self:equals("كتب، مجهول غير شخصي", result_ipass.passive, "ipass") local result_nopass = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.nopass>"}, "ar-conj") self:equals("كتب، بدون مجهول", result_nopass.passive, "nopass") self:equals("كتب، بدون مجهول، has_passive خطأ", result_nopass.has_passive, false) end function tests:test_conj_overrides() local result = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u.vn:كِتَابَة>"}, "ar-conj") self:equals("كتب، تجاوز المصدر", result.forms.vn[1].form, "كِتَابَة") end function tests:test_infer_radicals() local result = m_ar_verb.infer_radicals({ headword = "كتب", vform = "I" }) self:equals("كتب، نوع الضعف: صحيح", result.weakness, "sound") self:equals("كتب، حرف الجذر الأول: ك", result.rad1, "ك") self:equals("كتب، حرف الجذر الثاني: ت", result.rad2, "ت") self:equals("كتب، حرف الجذر الثالث: ب", result.rad3, "ب") local U = u(0x064F) -- ḍamma local result = m_ar_verb.infer_radicals({ headword = "قال", vform = "I", nonpast_vowel = U }) self:equals("قال، نوع الضعف: أجوف", result.weakness, "hollow") self:equals("قال، حرف الجذر الأول: ق", result.rad1, "ق") self:equals("قال، حرف الجذر الثاني: و (مستنتج)", result.rad2, "و") self:equals("قال، حرف الجذر الثالث: ل", result.rad3, "ل") local result = m_ar_verb.infer_radicals({ headword = "رمى", vform = "I" }) self:equals("رمى، نوع الضعف: ناقص", result.weakness, "final-weak") self:equals("رمى، حرف الجذر الأول: ر", result.rad1, "ر") self:equals("رمى، حرف الجذر الثاني: م", result.rad2, "م") self:equals("رمى، حرف الجذر الثالث: ي", result.rad3, "ي") end function tests:test_infer_participle_vocalization() local result = m_ar_verb.infer_participle_vocalization("كاتب", "I", "sound", true) self:equals("كاتب، تشكيل اسم الفاعل", result, "كَاتِب") local result = m_ar_verb.infer_participle_vocalization("مكتوب", "I", "sound", false) self:equals("مكتوب، تشكيل اسم المفعول", result, "مَكْتُوب") local result = m_ar_verb.infer_participle_vocalization("مدرس", "II", "sound", true) self:equals("مدرس، فعّل، تشكيل اسم الفاعل", result, "مُدَرِّس") end -- ═══════════════════════════════════════════════════════════ -- Tests for classify_triliteral_verb function -- ═══════════════════════════════════════════════════════════ -- الصحيح function tests:test_verb_classify_sound() self:equals("كتب", m_ar_verb.classify_triliteral_verb("ك", "ت", "ب"), "صحيح سالم") self:equals("ذهب", m_ar_verb.classify_triliteral_verb("ذ", "ه", "ب"), "صحيح سالم") self:equals("فعل", m_ar_verb.classify_triliteral_verb("ف", "ع", "ل"), "صحيح سالم") self:equals("مدّ", m_ar_verb.classify_triliteral_verb("م", "د", "د"), "صحيح مُضعَّف") self:equals("شدّ", m_ar_verb.classify_triliteral_verb("ش", "د", "د"), "صحيح مُضعَّف") self:equals("عدّ", m_ar_verb.classify_triliteral_verb("ع", "د", "د"), "صحيح مُضعَّف") self:equals("أخذ", m_ar_verb.classify_triliteral_verb("أ", "خ", "ذ"), "صحيح مهموز الفاء") self:equals("أكل", m_ar_verb.classify_triliteral_verb("أ", "ك", "ل"), "صحيح مهموز الفاء") self:equals("سأل", m_ar_verb.classify_triliteral_verb("س", "أ", "ل"), "صحيح مهموز العين") self:equals("قرأ", m_ar_verb.classify_triliteral_verb("ق", "ر", "أ"), "صحيح مهموز اللام") self:equals("أبأ", m_ar_verb.classify_triliteral_verb("أ", "ب", "أ"), "صحيح مهموز الفاء واللام") end -- المعتل function tests:test_verb_classify_weak() self:equals("وجد", m_ar_verb.classify_triliteral_verb("و", "ج", "د"), "معتل مثال واوي") self:equals("وقف", m_ar_verb.classify_triliteral_verb("و", "ق", "ف"), "معتل مثال واوي") self:equals("وضأ", m_ar_verb.classify_triliteral_verb("و", "ض", "أ"), "معتل مثال واوي مهموز اللام") self:equals("يسر", m_ar_verb.classify_triliteral_verb("ي", "س", "ر"), "معتل مثال يائي") self:equals("يئس", m_ar_verb.classify_triliteral_verb("ي", "أ", "س"), "معتل مثال يائي مهموز العين") self:equals("قال", m_ar_verb.classify_triliteral_verb("ق", "و", "ل"), "معتل أجوف واوي") self:equals("صام", m_ar_verb.classify_triliteral_verb("ص", "و", "م"), "معتل أجوف واوي") self:equals("باع", m_ar_verb.classify_triliteral_verb("ب", "ي", "ع"), "معتل أجوف يائي") self:equals("نام", m_ar_verb.classify_triliteral_verb("ن", "و", "م"), "معتل أجوف واوي") self:equals("دعا", m_ar_verb.classify_triliteral_verb("د", "ع", "و"), "معتل ناقص واوي") self:equals("سما", m_ar_verb.classify_triliteral_verb("س", "م", "و"), "معتل ناقص واوي") self:equals("رمى", m_ar_verb.classify_triliteral_verb("ر", "م", "ي"), "معتل ناقص يائي") self:equals("بكى", m_ar_verb.classify_triliteral_verb("ب", "ك", "ي"), "معتل ناقص يائي") -- لفيف مقرون (adjacent weak letters) self:equals("ويّ", m_ar_verb.classify_triliteral_verb("و", "ي", "ي"), "معتل لفيف مقرون") self:equals("أوى", m_ar_verb.classify_triliteral_verb("أ", "و", "ي"), "معتل لفيف مقرون مهموز الفاء") -- لفيف مفروق (separated weak letters) self:equals("وقى", m_ar_verb.classify_triliteral_verb("و", "ق", "ي"), "معتل لفيف مفروق") self:equals("وأى", m_ar_verb.classify_triliteral_verb("و", "أ", "ي"), "معتل لفيف مفروق مهموز العين") -- Combined weaknesses self:equals("أول", m_ar_verb.classify_triliteral_verb("أ", "و", "ل"), "معتل أجوف واوي مهموز الفاء") self:equals("قوأ", m_ar_verb.classify_triliteral_verb("ق", "و", "أ"), "معتل أجوف واوي مهموز اللام") self:equals("أيب", m_ar_verb.classify_triliteral_verb("أ", "ي", "ب"), "معتل أجوف يائي مهموز الفاء") self:equals("جيأ", m_ar_verb.classify_triliteral_verb("ج", "ي", "أ"), "معتل أجوف يائي مهموز اللام") self:equals("رأو", m_ar_verb.classify_triliteral_verb("ر", "أ", "و"), "معتل ناقص واوي مهموز العين") self:equals("شأي", m_ar_verb.classify_triliteral_verb("ش", "أ", "ي"), "معتل ناقص يائي مهموز العين") self:equals("ودد", m_ar_verb.classify_triliteral_verb("و", "د", "د"), "معتل مثال واوي مُضعَّف") self:equals("ينن", m_ar_verb.classify_triliteral_verb("ي", "ن", "ن"), "معتل مثال يائي مُضعَّف") self:equals("وأب", m_ar_verb.classify_triliteral_verb("و", "أ", "ب"), "معتل مثال واوي مهموز العين") self:equals("أبب", m_ar_verb.classify_triliteral_verb("أ", "ب", "ب"), "صحيح مُضعَّف مهموز الفاء") self:equals("أتى", m_ar_verb.classify_triliteral_verb("أ", "ت", "ي"), "معتل ناقص يائي مهموز الفاء") self:equals("قلا", m_ar_verb.classify_triliteral_verb("ق", "ل", "و"), "معتل ناقص واوي") self:equals("نما (و)", m_ar_verb.classify_triliteral_verb("ن", "م", "و"), "معتل ناقص واوي") self:equals("نمى (ي)", m_ar_verb.classify_triliteral_verb("ن", "م", "ي"), "معتل ناقص يائي") self:equals("يئس", m_ar_verb.classify_triliteral_verb("ي", "أ", "س"), "معتل مثال يائي مهموز العين") end -- ═══════════════════════════════════════════════════════════ -- تغطية كل الأوزان — فعل شاهد لكل وزن -- ═══════════════════════════════════════════════════════════ -- مختصر: يولّد التصريف ويعيد صيغة الخانة المطلوبة local function conj(root, pattern) return m_ar_verb.do_generate_forms({["جذر"] = root, ["وزن"] = pattern}, "ar-conj") end local function slot(result, name) local forms = result.forms[name] if not forms then return nil end local out = {} for i, f in ipairs(forms) do out[i] = f.form end return table.concat(out, "/") end -- الثلاثي المجرد: الأبواب الستة function tests:test_conj_form_I_all_vowel_patterns() local cases = { -- جذر، وزن، الماضي، المضارع، الأمر {"كتب", "فعَل يفعُل", "كَتَبَ", "يَكْتُبُ", "اُكْتُبْ"}, {"ضرب", "فعَل يفعِل", "ضَرَبَ", "يَضْرِبُ", "اِضْرِبْ"}, {"فتح", "فعَل يفعَل", "فَتَحَ", "يَفْتَحُ", "اِفْتَحْ"}, {"علم", "فعِل يفعَل", "عَلِمَ", "يَعْلَمُ", "اِعْلَمْ"}, {"كرم", "فعُل يفعُل", "كَرُمَ", "يَكْرُمُ", "اُكْرُمْ"}, {"حسب", "فعِل يفعِل", "حَسِبَ", "يَحْسِبُ", "اِحْسِبْ"}, } for _, c in ipairs(cases) do local root, pattern, past, pres, imp = c[1], c[2], c[3], c[4], c[5] local r = conj(root, pattern) self:equals(root .. "، " .. pattern .. "، الماضي", slot(r, "past_3ms"), past) self:equals(root .. "، " .. pattern .. "، المضارع", slot(r, "ind_3ms"), pres) self:equals(root .. "، " .. pattern .. "، الأمر", slot(r, "imp_2ms"), imp) end end -- المزيد: الأوزان V–XIII function tests:test_conj_derived_forms() local cases = { -- جذر، وزن، الماضي، المضارع، المصدر {"علم", "تفعّل", "تَعَلَّمَ", "يَتَعَلَّمُ", "تَعَلُّم"}, {"عون", "تفاعل", "تَعَاوَنَ", "يَتَعَاوَنُ", "تَعَاوُن"}, {"كسر", "انفعل", "اِنْكَسَرَ", "يَنْكَسِرُ", "اِنْكِسَار"}, {"جمع", "افتعل", "اِجْتَمَعَ", "يَجْتَمِعُ", "اِجْتِمَاع"}, {"حمر", "افعلّ", "اِحْمَرَّ", "يَحْمَرُّ", "اِحْمِرَار"}, {"خرج", "استفعل", "اِسْتَخْرَجَ", "يَسْتَخْرِجُ", "اِسْتِخْرَاج"}, {"حمر", "افعالّ", "اِحْمَارَّ", "يَحْمَارُّ", "اِحْمِيرَار"}, {"عشب", "افعوعل", "اِعْشَوْشَبَ", "يَعْشَوْشِبُ", "اِعْشِيشَاب"}, {"جلذ", "افعوّل", "اِجْلَوَّذَ", "يَجْلَوِّذُ", "اِجْلِوَّاذ"}, } for _, c in ipairs(cases) do local root, pattern, past, pres, vn = c[1], c[2], c[3], c[4], c[5] local r = conj(root, pattern) self:equals(root .. "، " .. pattern .. "، الماضي", slot(r, "past_3ms"), past) self:equals(root .. "، " .. pattern .. "، المضارع", slot(r, "ind_3ms"), pres) self:equals(root .. "، " .. pattern .. "، المصدر", slot(r, "vn"), vn) end end -- الرباعي: مجرده ومزيده function tests:test_conj_quadriliteral() local cases = { {"دحرج", "فعلل", "دَحْرَجَ", "يُدَحْرِجُ", "دَحْرَجَة"}, {"دحرج", "تفعلل", "تَدَحْرَجَ", "يَتَدَحْرَجُ", "تَدَحْرُج"}, {"حرجم", "افعنلل", "اِحْرَنْجَمَ", "يَحْرَنْجِمُ", "اِحْرِنْجَام"}, {"قشعر", "افعللّ", "اِقْشَعَرَّ", "يَقْشَعِرُّ", "اِقْشِعْرَار"}, } for _, c in ipairs(cases) do local root, pattern, past, pres, vn = c[1], c[2], c[3], c[4], c[5] local r = conj(root, pattern) self:equals(root .. "، " .. pattern .. "، الماضي", slot(r, "past_3ms"), past) self:equals(root .. "، " .. pattern .. "، المضارع", slot(r, "ind_3ms"), pres) self:equals(root .. "، " .. pattern .. "، المصدر", slot(r, "vn"), vn) end end -- الضمائر والأزمنة: نموذج كامل من «كتب» function tests:test_conj_persons_and_moods() local r = conj("كتب", "فعَل يفعُل") local cases = { {"past_1s", "كَتَبْتُ"}, {"past_2ms", "كَتَبْتَ"}, {"past_3fs", "كَتَبَتْ"}, {"past_3fp", "كَتَبْنَ"}, {"ind_1s", "أَكْتُبُ"}, {"ind_2fs", "تَكْتُبِينَ"}, {"ind_3mp", "يَكْتُبُونَ"}, {"sub_3ms", "يَكْتُبَ"}, {"juss_3ms", "يَكْتُبْ"}, {"imp_2fs", "اُكْتُبِي"}, {"imp_2mp", "اُكْتُبُوا"}, {"imp_2fp", "اُكْتُبْنَ"}, } for _, c in ipairs(cases) do self:equals("كتب، " .. c[1], slot(r, c[1]), c[2]) end end -- المبني للمجهول function tests:test_conj_passive_forms() local r = conj("كتب", "فعَل يفعُل") self:equals("كتب، الماضي المجهول", slot(r, "past_pass_3ms"), "كُتِبَ") self:equals("كتب، المضارع المجهول", slot(r, "ind_pass_3ms"), "يُكْتَبُ") self:equals("كتب، المنصوب المجهول", slot(r, "sub_pass_3ms"), "يُكْتَبَ") self:equals("كتب، المجزوم المجهول", slot(r, "juss_pass_3ms"), "يُكْتَبْ") local r2 = conj("درس", "فعّل") self:equals("درس، فعّل، الماضي المجهول", slot(r2, "past_pass_3ms"), "دُرِّسَ") self:equals("درس، فعّل، المضارع المجهول", slot(r2, "ind_pass_3ms"), "يُدَرَّسُ") end -- الأفعال الشاذّة المعالَجة داخل الوحدة function tests:test_conj_irregular_verbs() local raa = conj("رأي", "فعَل يفعَل") self:equals("رأى، الماضي", slot(raa, "past_3ms"), "رَأَى") self:equals("رأى، المضارع", slot(raa, "ind_3ms"), "يَرَى") self:equals("رأى، الأمر", slot(raa, "imp_2ms"), "رَ") local araa = conj("رأي", "أفعل") self:equals("أرى، الماضي", slot(araa, "past_3ms"), "أَرَى") self:equals("أرى، المضارع", slot(araa, "ind_3ms"), "يُرِي") local akal = conj("أكل", "فعَل يفعُل") self:equals("أكل، الماضي", slot(akal, "past_3ms"), "أَكَلَ") self:equals("أكل، الأمر (محذوف)", slot(akal, "imp_2ms"), "كُلْ") local akhadh = conj("أخذ", "فعَل يفعُل") self:equals("أخذ، الأمر (محذوف)", slot(akhadh, "imp_2ms"), "خُذْ") -- أمر: للأمر صيغتان، المحذوفة والتامّة بعد حرف عطف local amar = conj("أمر", "فعَل يفعُل") self:equals("أمر، الأمر", slot(amar, "imp_2ms"), "مُرْ/اُؤْمُرْ") -- سأل: للمجزوم والأمر صيغتان local saal = conj("سأل", "فعَل يفعَل") self:equals("سأل، الأمر", slot(saal, "imp_2ms"), "اِسْأَلْ/سَلْ") self:equals("سأل، المجزوم", slot(saal, "juss_3ms"), "يَسْأَلْ/يَسَلْ") -- حيّ/حيي: الإدغام والفكّ local hayy = conj("حيي", "فعِل يفعَل") self:equals("حيّ، الماضي (مدغم ومفكوك)", slot(hayy, "past_3ms"), "حَيَّ/حَيِيَ") self:equals("حيّ، المضارع", slot(hayy, "ind_3ms"), "يَحْيَا") end -- الإدغام بالتاء (var:short/long) وصيغ الإدغام المختصرة (reduced) function tests:test_conj_variants_and_reduced() local short = m_ar_verb.do_generate_forms({[1] = "حيي<I/i~a.var:short>"}, "ar-conj") self:equals("حيي، var:short، الماضي", slot(short, "past_3ms"), "حَيَّ") local long = m_ar_verb.do_generate_forms({[1] = "حيي<I/i~a.var:long>"}, "ar-conj") self:equals("حيي، var:long، الماضي", slot(long, "past_3ms"), "حَيِيَ") -- اتّخذ: افتعل من «أخذ» بإبدال الهمزة تاءً وإدغامها local ittakhadha = m_ar_verb.do_generate_forms({[1] = "اتخذ<VIII.reduced>"}, "ar-conj") self:equals("اتّخذ، الماضي", slot(ittakhadha, "past_3ms"), "اِتَّخَذَ") self:equals("اتّخذ، المضارع", slot(ittakhadha, "ind_3ms"), "يَتَّخِذُ") self:equals("اتّخذ، المصدر", slot(ittakhadha, "vn"), "اِتِّخَاذ") -- ادّارأ: تفاعل مدغم (اِدَّارَأَ عوض تَدَارَأَ) local iddaaraa = m_ar_verb.do_generate_forms({[1] = "ادارأ<VI.reduced>"}, "ar-conj") self:equals("ادّارأ، الماضي", slot(iddaaraa, "past_3ms"), "اِدَّارَأَ") -- اسطاع: استفعل محذوف التاء local istaa = m_ar_verb.do_generate_forms({[1] = "اسطاع<X.reduced>"}, "ar-conj") self:equals("اسطاع، الماضي", slot(istaa, "past_3ms"), "اِسْطَاعَ") self:equals("اسطاع، المضارع", slot(istaa, "ind_3ms"), "يَسْطِيعُ") end -- المعتل: المثال والناقص واللفيف function tests:test_conj_weak_extended() local cases = { -- جذر، وزن، الماضي، المضارع، الأمر، اسم الفاعل {"وعد", "فعَل يفعِل", "وَعَدَ", "يَعِدُ", "عِدْ", "وَاعِد"}, -- مثال واوي {"دعو", "فعَل يفعُل", "دَعَا", "يَدْعُو", "اُدْعُ", "دَاعٍ"}, -- ناقص واوي {"شوي", "فعَل يفعِل", "شَوَى", "يَشْوِي", "اِشْوِ", "شَاوٍ"}, -- ناقص يائي {"وقي", "فعَل يفعِل", "وَقَى", "يَقِي", "قِ", "وَاقٍ"}, -- لفيف مفروق } for _, c in ipairs(cases) do local root, pattern = c[1], c[2] local r = conj(root, pattern) self:equals(root .. "، الماضي", slot(r, "past_3ms"), c[3]) self:equals(root .. "، المضارع", slot(r, "ind_3ms"), c[4]) self:equals(root .. "، الأمر", slot(r, "imp_2ms"), c[5]) self:equals(root .. "، اسم الفاعل", slot(r, "ap"), c[6]) end end -- حواشي على حركات الماضي والمضارع، مثل <I/a[نادر]~u>. -- كانت ترمي خطأ لوا: verify_vowel كانت تقارن الكائن نفسه لا قيمته. function tests:test_conj_vowel_footnotes() local a = m_ar_verb.do_generate_forms({[1] = "كتب<I/a[نادر]~u>"}, "ar-conj") self:equals("كتب، حاشية على حركة الماضي", a.forms.past_3ms[1].form, "كَتَبَ") self:equals("كتب، حاشية على حركة الماضي، المضارع", a.forms.ind_3ms[1].form, "يَكْتُبُ") local b = m_ar_verb.do_generate_forms({[1] = "كتب<I/a~u[نادر]>"}, "ar-conj") self:equals("كتب، حاشية على حركة المضارع", b.forms.past_3ms[1].form, "كَتَبَ") -- أجوف: يمرّ عبر infer_radicals، وهو موضع الخطأ الأصلي local c = m_ar_verb.do_generate_forms({[1] = "قال<I/a[نادر]~u>"}, "ar-conj") self:equals("قال، حاشية على حركة الماضي", c.forms.past_3ms[1].form, "قَالَ") end -- q/qq/l/ll أُزيلت لأنها تستدعي [[وحدة:pron qualifier]] غير الموجودة في ويكاموس العربي. -- المطلوب الآن خطأ واضح في التحليل، لا خطأ لوا. function tests:test_conj_qualifier_modifiers_rejected() local ok, err = pcall(m_ar_verb.do_generate_forms, {[1] = "كتب<I/a~u.vn:كِتَابَة<q:نادر>>"}, "ar-conj") local msg = tostring(err) self:equals("q: مرفوضة", ok, false) self:equals("q: رسالة خطأ واضحة", not not msg:find("Unrecognized prefix"), true) self:equals("q: لا تستدعي pron qualifier", not not msg:find("pron qualifier"), false) end return tests ng7fbvy0zt8xnjrymgjiy5cr1b4520x وحدة:ar-utilities/en 828 231867 1097568 1004755 2026-07-26T17:56:10Z ForzaGreen 28665 إفراغ وإدراج للحذف السريع: نسخة مكرّرة من [[وحدة:ar-utilities]] لم تعد مستعملة 1097568 Scribunto text/plain -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش وحدة:ar-utilities/en]]. -- -- كانت نسخة مطابقة لـ[[وحدة:ar-utilities]]، لا تختلف عنها إلا بسطر تعليق. -- وكانت [[وحدة:ar-verb]] هي المستدعي الوحيد لها، وقد حُوِّلت إلى -- [[وحدة:ar-utilities]] بتاريخ 2026-07-26. لا تستدعيها أي وحدة الآن. 66k20tygeruyo21af7ne68brkaujzot 1097580 1097568 2026-07-26T18:49:14Z ForzaGreen 28665 إدراج للحذف السريع 1097580 Scribunto text/plain -- {{شطب|نسخة مكرّرة من [[وحدة:ar-utilities]] لم تعد مستعملة}} -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع، انظر [[نقاش وحدة:ar-utilities/en]]. -- -- كانت نسخة مطابقة لـ[[وحدة:ar-utilities]]، لا تختلف عنها إلا بسطر تعليق. -- وكانت [[وحدة:ar-verb]] هي المستدعي الوحيد لها، وقد حُوِّلت إلى -- [[وحدة:ar-utilities]] بتاريخ 2026-07-26. لا تستدعيها أي وحدة الآن. a6tpcwibd341dltar2id7qf714r4kxb 1097581 1097580 2026-07-26T18:49:58Z ForzaGreen 28665 إدراج للحذف السريع بقالب شطب 1097581 Scribunto text/plain -- {{شطب|نسخة مكرّرة من [[وحدة:ar-utilities]] (لا تختلف عنها إلا بسطر تعليق)، ولم تعد تستدعيها أي وحدة.}} -- -- أُفرغت هذه الوحدة وأُدرجت للحذف السريع (2026-07-26). -- نسخة مكرّرة من [[وحدة:ar-utilities]] (لا تختلف عنها إلا بسطر تعليق)، ولم تعد تستدعيها أي وحدة. -- -- ملاحظة: نموذج محتوى صفحات الوحدات هو Scribunto، فلا يُصيَّر قالب {{شطب}} -- هنا ولا تدخل الصفحة في تصنيف الحذف السريع؛ القالب الفعّال في [[نقاش الوحدة:ar-utilities/en]]. a1fsfc3bajkcdnzqnj40p3011irvrf8 أَنَّى 0 240615 1097542 2026-07-26T12:03:10Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097542 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1485598}} === المعاني === '''أَنَّى''' {{فعّل}} {{معجمية|L1485598|معاني}} === النطق === * {{أصد-عر|أَنَّى}} {{-}} === التصريفات === {{تصريف|جذر=أني|وزن=فعّل}} === المراجع === {{مراجع}} r22jf3xos7lotgxzfw6ck523dhgez98 اِسْتَبْطَأَ 0 240616 1097543 2026-07-26T12:09:53Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097543 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1497605}} === المعاني === '''اِسْتَبْطَأَ''' {{استفعل}} {{معجمية|L1497605|معاني}} === النطق === * {{أصد-عر|اِسْتَبْطَأَ}} {{-}} === التصريفات === {{تصريف|جذر=بطأ|وزن=استفعل}} === المراجع === {{مراجع}} fwpgjmw4e3ez9kejsp1b0gcrm2q2yf1 بَطَّأَ 0 240617 1097544 2026-07-26T12:19:47Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097544 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1486902}} === المعاني === '''بَطَّأَ''' {{فعّل}} {{معجمية|L1486902|معاني}} === النطق === * {{أصد-عر|بَطَّأَ}} {{-}} === التصريفات === {{تصريف|جذر=بطأ|وزن=فعّل}} === المراجع === {{مراجع}} 2ggozgriget5jnrah9bc4w3y6zp8vl6 تَبَاطَأَ 0 240618 1097545 2026-07-26T12:24:57Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097545 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1485623}} === المعاني === '''تَبَاطَأَ''' {{تفاعل}} {{معجمية|L1485623|معاني}} === النطق === * {{أصد-عر|تَبَاطَأَ}} {{-}} === التصريفات === {{تصريف|جذر=بطأ|وزن=تفاعل}} === المراجع === {{مراجع}} a2w9m244w9ajviginpxyrag7eoyah4p تَبَطَّأَ 0 240619 1097546 2026-07-26T12:26:52Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097546 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533061}} === المعاني === '''تَبَطَّأَ''' {{تفعّل}} {{معجمية|L1533061|معاني}} === النطق === * {{أصد-عر|تَبَطَّأَ}} {{-}} === التصريفات === {{تصريف|جذر=بطأ|وزن=تفعّل}} === المراجع === {{مراجع}} jkqc06ygkzdr337305rb2nwstuhzul5 بَطْبَطَ 0 240620 1097547 2026-07-26T12:27:54Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097547 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1492003}} === المعاني === '''بَطْبَطَ''' {{فعلل}} {{معجمية|L1492003|معاني}} === النطق === * {{أصد-عر|بَطْبَطَ}} {{-}} === التصريفات === {{تصريف|جذر=بطبط|وزن=فعلل}} === المراجع === {{مراجع}} kx1ba6gyljfni7v2237gl2bjki7bx4a اِسْتَبْطَحَ 0 240621 1097548 2026-07-26T12:55:00Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097548 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533064}} === المعاني === '''اِسْتَبْطَحَ''' {{استفعل}} {{معجمية|L1533064|معاني}} === النطق === * {{أصد-عر|اِسْتَبْطَحَ}} {{-}} === التصريفات === {{تصريف|جذر=بطح|وزن=استفعل}} === المراجع === {{مراجع}} k8bju9ra4wm3hvsel6gtsdqtwpd5sdf أَبْطَخَ 0 240622 1097550 2026-07-26T13:02:01Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097550 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533067}} === المعاني === '''أَبْطَخَ''' {{أفعل}} {{معجمية|L1533067|معاني}} === النطق === * {{أصد-عر|أَبْطَخَ}} {{-}} === التصريفات === {{تصريف|جذر=بطخ|وزن=أفعل}} === المراجع === {{مراجع}} pwklgyfw54ryp3hmkjgk8rz2ghpq5tk تَبَطَّخَ 0 240623 1097551 2026-07-26T13:03:55Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097551 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533068}} === المعاني === '''تَبَطَّخَ''' {{تفعّل}} {{معجمية|L1533068|معاني}} === النطق === * {{أصد-عر|تَبَطَّخَ}} {{-}} === التصريفات === {{تصريف|جذر=بطخ|وزن=تفعّل}} === المراجع === {{مراجع}} bcpbt3izrdvfzzr0xu562hz22oj9t9v أَبْطَشَ 0 240624 1097552 2026-07-26T13:07:45Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097552 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533071}} === المعاني === '''أَبْطَشَ''' {{أفعل}} {{معجمية|L1533071|معاني}} === النطق === * {{أصد-عر|أَبْطَشَ}} {{-}} === التصريفات === {{تصريف|جذر=بطش|وزن=أفعل}} === المراجع === {{مراجع}} 36p71l524u8co5l4tl8rn76n560xe9y أَبَطَّ 0 240625 1097553 2026-07-26T13:08:27Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097553 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533074}} === المعاني === '''أَبَطَّ''' {{أفعل}} {{معجمية|L1533074|معاني}} === النطق === * {{أصد-عر|أَبَطَّ}} {{-}} === التصريفات === {{تصريف|جذر=بطط|وزن=أفعل}} === المراجع === {{مراجع}} 6bzv0gqnjab2a87nmnt6acwlfhqyq80 بَطَّطَ 0 240626 1097554 2026-07-26T13:09:36Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097554 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533075}} === المعاني === '''بَطَّطَ''' {{فعّل}} {{معجمية|L1533075|معاني}} === النطق === * {{أصد-عر|بَطَّطَ}} {{-}} === التصريفات === {{تصريف|جذر=بطط|وزن=فعّل}} === المراجع === {{مراجع}} jg9gm8xeu6o38ufpttiv0jh5zme8zmt أَبْطَغَ 0 240627 1097555 2026-07-26T13:11:56Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097555 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1533076}} === المعاني === '''أَبْطَغَ''' {{أفعل}} {{معجمية|L1533076|معاني}} === النطق === * {{أصد-عر|أَبْطَغَ}} {{-}} === التصريفات === {{تصريف|جذر=بطغ|وزن=أفعل}} === المراجع === {{مراجع}} 76vszppvj2jwodoyrcv2mi1ti5pnvjw بَطِغَ 0 240628 1097556 2026-07-26T13:16:31Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097556 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1543713}} === المعاني === '''بَطِغَ''' {{سمع}} {{معجمية|L1543713|معاني}} === النطق === * {{أصد-عر|بَطِغَ}} {{-}} === التصريفات === {{تصريف|جذر=بطغ|وزن=فعِل يفعَل}} === المراجع === {{مراجع}} jvtaqofef4jo0tioze1prjhpg6xqs9x اِحْبَنْطَأَ 0 240629 1097557 2026-07-26T15:34:39Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097557 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534184}} === المعاني === '''اِحْبَنْطَأَ''' {{افعنلل}} {{معجمية|L1534184|معاني}} === النطق === * {{أصد-عر|اِحْبَنْطَأَ}} {{-}} === التصريفات === {{تصريف|جذر=حبطأ|وزن=افعنلل}} === المراجع === {{مراجع}} 0jvjv83a5czu3grh95vm0a9bavkr22n اِحْبَنْطَى 0 240630 1097558 2026-07-26T15:58:19Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097558 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534185}} === المعاني === '''اِحْبَنْطَى''' {{افعنلل}} {{معجمية|L1534185|معاني}} === النطق === * {{أصد-عر|اِحْبَنْطَى}} {{-}} === التصريفات === {{تصريف|جذر=حبطي|وزن=افعنلل}} === المراجع === {{مراجع}} a0fq8omt97zyll25xs1wvf2tik4oxo6 تَحَتْرَشَ 0 240631 1097561 2026-07-26T16:17:31Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097561 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534205}} === المعاني === '''تَحَتْرَشَ''' {{تفعلل}} {{معجمية|L1534205|معاني}} === النطق === * {{أصد-عر|تَحَتْرَشَ}} {{-}} === التصريفات === {{تصريف|جذر=حترش|وزن=تفعلل}} === المراجع === {{مراجع}} m5j28e31u33ygjby2if2tgvfs0opfiq اِحْتَتَشَ 0 240632 1097562 2026-07-26T16:34:46Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097562 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534206}} === المعاني === '''اِحْتَتَشَ''' {{افتعل}} {{معجمية|L1534206|معاني}} === النطق === * {{أصد-عر|اِحْتَتَشَ}} {{-}} === التصريفات === {{تصريف|جذر=حتش|وزن=افتعل}} === المراجع === {{مراجع}} 1xd57pyxc8xtysq4hyy8k1ioo0k36w0 نقاش وحدة:ar-verb/مختبر 0 240633 1097565 2026-07-26T17:42:17Z ForzaGreen 28665 إنشاء صفحة تشغيل اختبارات وحدة:ar-verb 1097565 wikitext text/x-wiki {{#invoke:ar-verb/testcases|run_tests}} c6a16qy3kk8qr3ld3uqbyecwlro80pn نقاش وحدة:ar-utilities/en 0 240634 1097567 2026-07-26T17:56:00Z ForzaGreen 28665 إدراج [[وحدة:ar-utilities/en]] للحذف السريع: نسخة مكرّرة غير مستعملة 1097567 wikitext text/x-wiki {{شطب|[[وحدة:ar-utilities/en]] نسخة مكرّرة من [[وحدة:ar-utilities]] (لا تختلف عنها إلا بسطر تعليق واحد)، ولم تعد مستعملة: كانت [[وحدة:ar-verb]] هي المستدعي الوحيد لها وقد حُوِّلت إلى [[وحدة:ar-utilities]] بتاريخ 2026-07-26، ولا تستدعيها أي وحدة الآن. '''المطلوب حذف [[وحدة:ar-utilities/en]] نفسها وهذه الصفحة معها''' (تعذّر وضع القالب في صفحة الوحدة لأن نموذج محتواها Scribunto لا يُصيّر الويكي‌نص).}} 9he8v8kd4gfhhchoxmp87ahnbfopmq4 نقاش الوحدة:ar-verb/ملعب 829 240635 1097570 2026-07-26T18:29:16Z ForzaGreen 28665 إدراج [[وحدة:ar-verb/ملعب]] للحذف السريع 1097570 wikitext text/x-wiki {{شطب|[[وحدة:ar-verb/ملعب]]: نسخة ملعب قديمة من [[وحدة:ar-verb]] (17 أكتوبر 2025)، تجاوزتها النسخة الحالية، ولا تُستعمل في أي صفحة. '''المطلوب حذف [[وحدة:ar-verb/ملعب]] نفسها وهذه الصفحة معها''' (تعذّر وضع القالب في صفحة الوحدة لأن نموذج محتواها Scribunto).}} 0jz0k4k4a436ndfswrn8f76jz2xogve نقاش الوحدة:headword/ملعب2 829 240636 1097572 2026-07-26T18:29:36Z ForzaGreen 28665 إدراج [[وحدة:headword/ملعب2]] للحذف السريع 1097572 wikitext text/x-wiki {{شطب|[[وحدة:headword/ملعب2]]: نسخة مكرّرة من [[وحدة:headword]]؛ الدالتان الوحيدتان اللتان كانت [[وحدة:inflection utilities]] تستعملهما منها (head_is_multiword وadd_multiword_links) مطابقتان حرفيًّا لما في [[وحدة:headword]]، وقد حُوِّلت الوحدة إليها. '''المطلوب حذف [[وحدة:headword/ملعب2]] نفسها وهذه الصفحة معها''' (تعذّر وضع القالب في صفحة الوحدة لأن نموذج محتواها Scribunto).}} abcpdwxgt4ey844dle8wiw2ym689dm5 نقاش الوحدة:links/ملعب2 829 240637 1097574 2026-07-26T18:29:56Z ForzaGreen 28665 إدراج [[وحدة:links/ملعب2]] للحذف السريع 1097574 wikitext text/x-wiki {{شطب|[[وحدة:links/ملعب2]]: نسخة أقدم من [[وحدة:links]] لم تكن تختلف عنها إلا بإعادة توجيه أسماء الوحدات (languages/ملعب وscripts/ملعب2). حُوِّلت [[وحدة:ar-verb]] و[[وحدة:inflection utilities]] إلى [[وحدة:links]]. '''المطلوب حذف [[وحدة:links/ملعب2]] نفسها وهذه الصفحة معها''' (تعذّر وضع القالب في صفحة الوحدة لأن نموذج محتواها Scribunto).}} oxoq6o35kvyguci0xvpjhwzj9c1z74t نقاش الوحدة:languages/ملعب 829 240638 1097576 2026-07-26T18:30:16Z ForzaGreen 28665 إدراج [[وحدة:languages/ملعب]] للحذف السريع 1097576 wikitext text/x-wiki {{شطب|[[وحدة:languages/ملعب]]: نسخة أقدم من [[وحدة:languages]] لم تكن تختلف عنها إلا بسطر واحد يعيد التوجيه إلى [[وحدة:scripts/ملعب2]]. حُوِّلت [[وحدة:ar-verb]] إلى [[وحدة:languages]]. ما تبقّى من صفحات تشير إليها هي ملاعب مهجورة من 2017 لا تُستعمل. '''المطلوب حذف [[وحدة:languages/ملعب]] نفسها وهذه الصفحة معها''' (تعذّر وضع القالب في صفحة الوحدة لأن نموذج محتواها Scribunto).}} iygd5zrycoqew73uyxt8zwe60bvrpd5 نقاش الوحدة:scripts/ملعب2 829 240639 1097578 2026-07-26T18:30:36Z ForzaGreen 28665 إدراج [[وحدة:scripts/ملعب2]] للحذف السريع 1097578 wikitext text/x-wiki {{شطب|[[وحدة:scripts/ملعب2]]: نسخة مطابقة حرفيًّا لـ[[وحدة:scripts]] (لا يوجد بينهما أي سطر مختلف)، ولم تكن تُستدعى إلا من [[وحدة:languages/ملعب]] و[[وحدة:links/ملعب2]] وقد أُفرغتا. '''المطلوب حذف [[وحدة:scripts/ملعب2]] نفسها وهذه الصفحة معها''' (تعذّر وضع القالب في صفحة الوحدة لأن نموذج محتواها Scribunto).}} irfzb0ci6f4ju06x0neb3yn5zw3zx8w وحدة:pages/ملعب 828 240640 1097588 2026-07-26T19:30:21Z ForzaGreen 28665 نقل ForzaGreen صفحة [[وحدة:pages/ملعب]] إلى [[وحدة:pages/2]]: إعادة تسمية: ليست صفحة ملعب بل نسخة ثانية مستعملة فعليًّا من [[وحدة:pages]] 1097588 Scribunto text/plain return require [[وحدة:pages/2]] kuhevaiotpo0nzsztevf3ox3rfuzes8 1097591 1097588 2026-07-26T19:33:39Z ForzaGreen 28665 إدراج للحذف السريع بعد النقل إلى [[وحدة:pages/2]] 1097591 Scribunto text/plain -- {{شطب|أُعيدت تسميتها إلى [[وحدة:pages/2]]؛ لم تكن صفحة ملعب بل نسخة ثانية مستعملة من [[وحدة:pages]]. لم يعد أي شيء يشير إلى هذا العنوان. القالب الفعّال في صفحة النقاش.}} -- تبقى السطر التالي عاملًا حتى الحذف، تحسّبًا لأي إشارة لم تُرصد. return require [[وحدة:pages/2]] 018hkzp2ksgkunty2nw4zqy6r7ougu2 نقاش الوحدة:pages/ملعب 829 240641 1097592 2026-07-26T19:33:49Z ForzaGreen 28665 إدراج [[وحدة:pages/ملعب]] للحذف السريع 1097592 wikitext text/x-wiki {{شطب|[[وحدة:pages/ملعب]]: أُعيدت تسميتها إلى [[وحدة:pages/2]] لأنها ليست صفحة ملعب بل نسخة ثانية مستعملة فعليًّا من [[وحدة:pages]]. حُدِّثت [[وحدة:utilities]] و[[وحدة:maintenance category]]، ولم يعد أي شيء يشير إلى العنوان القديم. '''المطلوب حذف [[وحدة:pages/ملعب]] وهذه الصفحة معها.'''}} kxzugk0i5wefy5mg8vkdmsj8yyns2lr ويكاموس:ويكي بيانات/بلاغ 4 240642 1097593 2026-07-26T22:03:28Z ForzaGreen 28665 إنشاء صفحة البلاغات لأداة مراجعة معاني الأفعال 1097593 wikitext text/x-wiki <noinclude> صفحة تحميل مسبق (preload) تستعملها [[toolforge:ar-senses|أداة مراجعة معاني الأفعال]] عند فتح بلاغ جديد. المعاملات: <code>$1</code> معرف المفردة، <code>$2</code> الفعل، <code>$3</code> الجذر، <code>$4</code> المعاني الحالية. </noinclude><includeonly>;المفردة: '''$2''' — [[d:Lexeme:$1|$1]] ;الجذر: $3 ;المعاني الحالية: $4 ;في الأداة: [[toolforge:ar-senses/review/$1|فتح صفحة المراجعة]] <!-- اكتب وصف المشكلة تحت هذا السطر (معنى خاطئ، مصدر غير دقيق، عنوان المفردة، تكرار…) ثم احذف هذا التعليق واحفظ الموضوع. --> <!-- للمجيبين: استخدم {{رد على|اسم صاحب البلاغ}} في ردكم حتى يصله إشعار. --> </includeonly> 8t4wvpd3qb91qw28zcqxxuvmfhn1ca8 1097601 1097593 2026-07-26T23:46:26Z ForzaGreen 28665 تحديث نص البلاغ: سطر واحد للمعلومات، وإضافة توقيع صاحب البلاغ ($4) ليعمل زرّا «رد» و«اشترك» في صفحة النقاش 1097601 wikitext text/x-wiki <noinclude> صفحة تحميل مسبق (preload) تستعملها [[toolforge:ar-senses|أداة مراجعة معاني الأفعال]] عند فتح بلاغ جديد. المعاملات: <code>$1</code> معرف المفردة، <code>$2</code> الفعل، <code>$3</code> الجذر، <code>$4</code> توقيع صاحب البلاغ. التوقيع تُمرِّره الأداة في <code>$4</code> ولا يُكتب هنا مباشرةً، لأن حفظ هذه الصفحة كان سيحوّله إلى توقيع فيها. ووجوده ضروري: من دون تعليق موقَّع لا يعرض «أدوات النقاش» زرَّي «رد» و«اشترك» على الموضوع. </noinclude><includeonly>'''$2''' ([[d:Lexeme:$1|$1]]) · الجذر: $3 · [[toolforge:ar-senses/review/$1|مراجعة في الأداة]] <!-- اكتب وصف المشكلة هنا، قبل التوقيع. رجاءً لا تغيّر عنوان الموضوع ولا السطر الذي فوق هذا التعليق، فالأداة تعتمد عليهما في ربط النقاش بالمفردة. --> $4</includeonly> 8svbpechw8llrtz3sox5yffzs9hwyr8 1097605 1097601 2026-07-27T00:04:37Z ForzaGreen 28665 استعمال أيقونتي ويكي بيانات وويكاموس، ووضع رابط الأداة بجانبهما 1097605 wikitext text/x-wiki <noinclude> صفحة تحميل مسبق (preload) تستعملها [[toolforge:ar-senses|أداة مراجعة معاني الأفعال]] عند فتح بلاغ جديد. المعاملات: <code>$1</code> معرف المفردة، <code>$2</code> الفعل، <code>$3</code> الجذر، <code>$4</code> توقيع صاحب البلاغ. التوقيع تُمرِّره الأداة في <code>$4</code> ولا يُكتب هنا مباشرةً، لأن حفظ هذه الصفحة كان سيحوّله إلى توقيع فيها. ووجوده ضروري: من دون تعليق موقَّع لا يعرض «أدوات النقاش» زرَّي «رد» و«اشترك» على الموضوع. </noinclude><includeonly>'''$2''' [[ملف:Wikidata-logo.svg|وصلة=d:Lexeme:$1|20بك|ويكي بيانات: $1]] [[ملف:Wiktionary-logo.svg|وصلة=$2|16بك|صفحة ويكاموس]] · الجذر: $3 · ([[toolforge:ar-senses/review/$1|رابط المُفردة في الأداة]]) <!-- اكتب وصف المشكلة هنا، قبل التوقيع. رجاءً لا تغيّر عنوان الموضوع ولا السطر الذي فوق هذا التعليق، فالأداة تعتمد عليهما في ربط النقاش بالمفردة. --> $4</includeonly> 8tesmi5tdxonf9x722ec60plajnvp9s 1097608 1097605 2026-07-27T00:20:19Z ForzaGreen 28665 استعمال أيقونتي ويكي بيانات وويكاموس الرسميتين (favicon) بدل الشعارين الكاملين 1097608 wikitext text/x-wiki <noinclude> صفحة تحميل مسبق (preload) تستعملها [[toolforge:ar-senses|أداة مراجعة معاني الأفعال]] عند فتح بلاغ جديد. المعاملات: <code>$1</code> معرف المفردة، <code>$2</code> الفعل، <code>$3</code> الجذر، <code>$4</code> توقيع صاحب البلاغ. التوقيع تُمرِّره الأداة في <code>$4</code> ولا يُكتب هنا مباشرةً، لأن حفظ هذه الصفحة كان سيحوّله إلى توقيع فيها. ووجوده ضروري: من دون تعليق موقَّع لا يعرض «أدوات النقاش» زرَّي «رد» و«اشترك» على الموضوع. </noinclude><includeonly>'''$2''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:$1|18بك|ويكي بيانات: $1]] [[ملف:Wiktionary-favicon.svg|وصلة=$2|18بك|صفحة ويكاموس]] · الجذر: $3 · ([[toolforge:ar-senses/review/$1|رابط المُفردة في الأداة]]) <!-- اكتب وصف المشكلة هنا، قبل التوقيع. رجاءً لا تغيّر عنوان الموضوع ولا السطر الذي فوق هذا التعليق، فالأداة تعتمد عليهما في ربط النقاش بالمفردة. --> $4</includeonly> p4xfso3yertl81283pzdn5s3m021kdm 1097611 1097608 2026-07-27T00:32:33Z ForzaGreen 28665 العودة إلى شعار ويكاموس ذي المربّعات 1097611 wikitext text/x-wiki <noinclude> صفحة تحميل مسبق (preload) تستعملها [[toolforge:ar-senses|أداة مراجعة معاني الأفعال]] عند فتح بلاغ جديد. المعاملات: <code>$1</code> معرف المفردة، <code>$2</code> الفعل، <code>$3</code> الجذر، <code>$4</code> توقيع صاحب البلاغ. التوقيع تُمرِّره الأداة في <code>$4</code> ولا يُكتب هنا مباشرةً، لأن حفظ هذه الصفحة كان سيحوّله إلى توقيع فيها. ووجوده ضروري: من دون تعليق موقَّع لا يعرض «أدوات النقاش» زرَّي «رد» و«اشترك» على الموضوع. </noinclude><includeonly>'''$2''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:$1|18بك|ويكي بيانات: $1]] [[ملف:Wiktionary-logo.svg|وصلة=$2|18بك|صفحة ويكاموس]] · الجذر: $3 · ([[toolforge:ar-senses/review/$1|رابط المُفردة في الأداة]]) <!-- اكتب وصف المشكلة هنا، قبل التوقيع. رجاءً لا تغيّر عنوان الموضوع ولا السطر الذي فوق هذا التعليق، فالأداة تعتمد عليهما في ربط النقاش بالمفردة. --> $4</includeonly> df9qjc18ep6hf32bdpxu2cqcdbygk8v أَحْتَى 0 240643 1097594 2026-07-26T22:20:22Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097594 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534216}} === المعاني === '''أَحْتَى''' {{أفعل}} {{معجمية|L1534216|معاني}} === النطق === * {{أصد-عر|أَحْتَى}} {{-}} === التصريفات === {{تصريف|جذر=حتي|وزن=أفعل}} === المراجع === {{مراجع}} 38aykckcm2p68bewgqpbjfehu354gqa اِسْتَحَثَّ 0 240644 1097595 2026-07-26T22:24:02Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097595 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1483735}} === المعاني === '''اِسْتَحَثَّ''' {{استفعل}} {{معجمية|L1483735|معاني}} === النطق === * {{أصد-عر|اِسْتَحَثَّ}} {{-}} === التصريفات === {{تصريف|جذر=حثث|وزن=استفعل}} === المراجع === {{مراجع}} 57p0693xwwbqn01v82ae0lt264h1bs4 تَحَاثَّ 0 240645 1097596 2026-07-26T22:25:47Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097596 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1496376}} === المعاني === '''تَحَاثَّ''' {{تفاعل}} {{معجمية|L1496376|معاني}} === النطق === * {{أصد-عر|تَحَاثَّ}} {{-}} === التصريفات === {{تصريف|جذر=حثث|وزن=تفاعل}} === المراجع === {{مراجع}} 8g7h09kgi82m4vxbrf0pwi50x9l83hi حَثْحَثَ 0 240646 1097597 2026-07-26T22:36:31Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097597 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534221}} === المعاني === '''حَثْحَثَ''' {{فعلل}} {{معجمية|L1534221|معاني}} === النطق === * {{أصد-عر|حَثْحَثَ}} {{-}} === التصريفات === {{تصريف|جذر=حثحث|وزن=فعلل}} === المراجع === {{مراجع}} fjpzwzyp4v1z34aqtir4bmj9zarfkc2 اِحْتَثَى 0 240647 1097598 2026-07-26T22:39:59Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097598 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534234}} === المعاني === '''اِحْتَثَى''' {{افتعل}} {{معجمية|L1534234|معاني}} === النطق === * {{أصد-عر|اِحْتَثَى}} {{-}} === التصريفات === {{تصريف|جذر=حثي|وزن=افتعل}} === المراجع === {{مراجع}} o8pzi4uf3wnc8u7asn1fos6k097pk9i أَحْثَى 0 240648 1097599 2026-07-26T22:43:11Z أبو هشام 20788 إنشاء صفحة فعل من بيانات المعاجم (ar-senses tool) 1097599 wikitext text/x-wiki == {{اللغة|عربية}} == {{صندوق معلومات فعل|L1534232}} === المعاني === '''أَحْثَى''' {{أفعل}} {{معجمية|L1534232|معاني}} === النطق === * {{أصد-عر|أَحْثَى}} {{-}} === التصريفات === {{تصريف|جذر=حثي|وزن=أفعل}} === المراجع === {{مراجع}} q6c3d6w6nbarx8lqfqdegm708rgwyuc نقاش ويكاموس:ويكي بيانات 5 240649 1097600 2026-07-26T23:32:50Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) */ قسم جديد 1097600 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == ;المفردة: '''اِسْتَرَقَ''' — [[d:Lexeme:L1480330|L1480330]] ;الجذر: سرق ;في الأداة: [[toolforge:ar-senses/review/L1480330|فتح صفحة المراجعة]] <!-- اكتب وصف المشكلة تحت هذا السطر (معنى خاطئ، مصدر غير دقيق، عنوان المفردة، تكرار…) ثم احذف هذا التعليق واحفظ الموضوع. --> <!-- للمجيبين: استخدم {{رد على|اسم صاحب البلاغ}} في ردكم حتى يصله إشعار. --> كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س-ر-ق. e3ua3617lych512gz0v7c2qs14jwosk 1097602 1097600 2026-07-26T23:50:38Z ForzaGreen 28665 إفراغ الصفحة 1097602 wikitext text/x-wiki phoiac9h4m842xq45sp7s6u21eteeq1 1097603 1097602 2026-07-26T23:51:47Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) */ قسم جديد 1097603 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' ([[d:Lexeme:L1480330|L1480330]]) · الجذر: سرق · [[toolforge:ar-senses/review/L1480330|مراجعة في الأداة]] كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س-ر-ق. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 23:51، 26 يوليو 2026 (ت ع م) ptzfapm3z8dlyhg8c4xrkmoif7roxfx 1097606 1097603 2026-07-27T00:07:05Z ForzaGreen 28665 إفراغ الصفحة 1097606 wikitext text/x-wiki phoiac9h4m842xq45sp7s6u21eteeq1 1097607 1097606 2026-07-27T00:10:27Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) */ قسم جديد 1097607 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata-logo.svg|وصلة=d:Lexeme:L1480330|20بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|16بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:10، 27 يوليو 2026 (ت ع م) r9neterwb8l229iidxg4g9msf0nllze 1097609 1097607 2026-07-27T00:31:07Z ForzaGreen 28665 إفراغ الصفحة 1097609 wikitext text/x-wiki phoiac9h4m842xq45sp7s6u21eteeq1 1097610 1097609 2026-07-27T00:31:43Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) */ قسم جديد 1097610 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-favicon.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:31، 27 يوليو 2026 (ت ع م) a8l363r95r0uk6pc15276gs8j3ejxa6 1097612 1097610 2026-07-27T00:33:25Z ForzaGreen 28665 إفراغ الصفحة 1097612 wikitext text/x-wiki phoiac9h4m842xq45sp7s6u21eteeq1 1097613 1097612 2026-07-27T00:33:46Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) */ قسم جديد 1097613 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) heposoryafq6mgp7592mqjejc4bk3l9 1097614 1097613 2026-07-27T11:23:43Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) */ ردّ 1097614 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) b75eaboy80fxv8c9epfl3h5xsha7cof 1097615 1097614 2026-07-27T11:25:15Z ForzaGreen 28665 1097615 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) dcrklzg8fu24h25k7vaqur33fil5c2d 1097616 1097615 2026-07-27T11:27:17Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) */ قسم جديد 1097616 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) p7pt8fzrfbpmp43cpvv5dvah35pix2i 1097617 1097616 2026-07-27T11:28:22Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) */ ردّ 1097617 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) 1ch4c3wkqrrvk51bhrabjynayx85i12 1097618 1097617 2026-07-27T11:36:10Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) */ قسم جديد 1097618 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) 2kfwrbs5f2z2r2gqyfglxc2vxvowc3k 1097619 1097618 2026-07-27T11:38:07Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) */ ردّ 1097619 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) 27c4hz6yp8l1f3hft9v81n25r4orggg 1097620 1097619 2026-07-27T11:39:30Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) */ ردّ 1097620 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) pos0ubqi7z27vmw3vk9ckyp1ska6lzv 1097621 1097620 2026-07-27T11:42:34Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) */ ردّ 1097621 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) ::حالة أخرى حيث يشترك جذران في نفس صيغة الفعل: ::* جذر ت-ل-و على وزن تفعّل => [[تَتَلَّى]] ::* جذر ت-ل-ي على وزن تفعّل => [[تَتَلَّى]] ::[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:42، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) 66bmk0j11bwdpa4d593m46ga38mqhd7 1097622 1097621 2026-07-27T11:44:17Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) */ 1097622 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) ::حالة أخرى حيث يشترك جذران في نفس صيغة الفعل: ::* جذر ت-ل-و على وزن تفعّل => [[تَتَلَّى]] ([https://ar-senses.toolforge.org/review/L1533461 رابط المُفردة في الأداة]). ::* جذر ت-ل-ي على وزن تفعّل => [[تَتَلَّى]] ::[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:42، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) 9y1w89fl8kd0cgh91w80210ny2eshu4 1097623 1097622 2026-07-27T11:45:35Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: دَمْشَقَ (L1535560) */ قسم جديد 1097623 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) ::حالة أخرى حيث يشترك جذران في نفس صيغة الفعل: ::* جذر ت-ل-و على وزن تفعّل => [[تَتَلَّى]] ([https://ar-senses.toolforge.org/review/L1533461 رابط المُفردة في الأداة]). ::* جذر ت-ل-ي على وزن تفعّل => [[تَتَلَّى]] ::[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:42، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: دَمْشَقَ (L1535560) == '''دَمْشَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1535560|18بك|ويكي بيانات: L1535560]] [[ملف:Wiktionary-logo.svg|وصلة=دَمْشَقَ|18بك|صفحة ويكاموس]] · الجذر: دمشق · ([[toolforge:ar-senses/review/L1535560|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''أثناء محاولة الإضافة في ويكي بيانات يظهر: invalid literal for int0 with :خطأ في النشر بويكي بيانات 'base 10: 'None'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:45، 27 يوليو 2026 (ت ع م) kcgbfrp4sw8l7f130fe9v3tksviwwxo 1097624 1097623 2026-07-27T11:48:11Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: دَمْشَقَ (L1535560) */ ردّ 1097624 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) ::حالة أخرى حيث يشترك جذران في نفس صيغة الفعل: ::* جذر ت-ل-و على وزن تفعّل => [[تَتَلَّى]] ([https://ar-senses.toolforge.org/review/L1533461 رابط المُفردة في الأداة]). ::* جذر ت-ل-ي على وزن تفعّل => [[تَتَلَّى]] ::[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:42، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: دَمْشَقَ (L1535560) == '''دَمْشَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1535560|18بك|ويكي بيانات: L1535560]] [[ملف:Wiktionary-logo.svg|وصلة=دَمْشَقَ|18بك|صفحة ويكاموس]] · الجذر: دمشق · ([[toolforge:ar-senses/review/L1535560|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''أثناء محاولة الإضافة في ويكي بيانات يظهر: invalid literal for int0 with :خطأ في النشر بويكي بيانات 'base 10: 'None'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:45، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك على التبليغ أخي {{مس|رِفْد}}. المُشكلة كانت بسبب مُعرف خاطئ لأحد المعاجم في قاعدة البيانات. أعدت إنشاء المعاني في ويكي بيانات وأنشأت الصفحة في ويكاموس: [[دَمْشَقَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:48، 27 يوليو 2026 (ت ع م) tnlup1jslxf0t8lnzlz2f7hz95q4elb 1097625 1097624 2026-07-27T11:52:32Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: أَبَانَ (L1477016) */ قسم جديد 1097625 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) ::حالة أخرى حيث يشترك جذران في نفس صيغة الفعل: ::* جذر ت-ل-و على وزن تفعّل => [[تَتَلَّى]] ([https://ar-senses.toolforge.org/review/L1533461 رابط المُفردة في الأداة]). ::* جذر ت-ل-ي على وزن تفعّل => [[تَتَلَّى]] ::[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:42، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: دَمْشَقَ (L1535560) == '''دَمْشَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1535560|18بك|ويكي بيانات: L1535560]] [[ملف:Wiktionary-logo.svg|وصلة=دَمْشَقَ|18بك|صفحة ويكاموس]] · الجذر: دمشق · ([[toolforge:ar-senses/review/L1535560|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''أثناء محاولة الإضافة في ويكي بيانات يظهر: invalid literal for int0 with :خطأ في النشر بويكي بيانات 'base 10: 'None'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:45، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك على التبليغ أخي {{مس|رِفْد}}. المُشكلة كانت بسبب مُعرف خاطئ لأحد المعاجم في قاعدة البيانات. أعدت إنشاء المعاني في ويكي بيانات وأنشأت الصفحة في ويكاموس: [[دَمْشَقَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:48، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: أَبَانَ (L1477016) == '''أَبَانَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1477016|18بك|ويكي بيانات: L1477016]] [[ملف:Wiktionary-logo.svg|وصلة=أَبَانَ|18بك|صفحة ويكاموس]] · الجذر: بين · ([[toolforge:ar-senses/review/L1477016|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:52، 27 يوليو 2026 (ت ع م) bk8ps6yndzglkai53o257nlubt5khfv 1097626 1097625 2026-07-27T11:57:22Z ForzaGreen 28665 /* بلاغ من أداة مراجعة المعاني: أَبَانَ (L1477016) */ ردّ 1097626 wikitext text/x-wiki == بلاغ من أداة مراجعة المعاني: اِسْتَرَقَ (L1480330) == '''اِسْتَرَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1480330|18بك|ويكي بيانات: L1480330]] [[ملف:Wiktionary-logo.svg|وصلة=اِسْتَرَقَ|18بك|صفحة ويكاموس]] · الجذر: سرق · ([[toolforge:ar-senses/review/L1480330|رابط المُفردة في الأداة]]) كانت هناك مشكلة في صندوق المعلومات في ويكاموس بعد إنشاء الصفحات لجميع المفردات من جذر س ر ق: [[اِسْتَرَقَ]]، [[اِنْسَرَقَ]]، [[تَسَرَّقَ]]. --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 00:33، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك {{مس|إسلام}} التبليغ. فعلا كان هناك خلل في محارف الجذر [https://www.wikidata.org/wiki/Lexeme:L1012573 س ر ق] في ويكي بيانات. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:23، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: حَلَّى (L1534604) == '''حَلَّى''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1534604|18بك|ويكي بيانات: L1534604]] [[ملف:Wiktionary-logo.svg|وصلة=حَلَّى|18بك|صفحة ويكاموس]] · الجذر: حلو · ([[toolforge:ar-senses/review/L1534604|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:27، 27 يوليو 2026 (ت ع م) :شكرا لك @[[مستخدم:رِفْد|رِفْد]] على الإبلاغ. المشكلة في الفعل حَلَّى (وأفعال آخرين) أَنَّه يُوَلَّدُ من جذرين: ح-ل-ي و ح-ل-و. في ويكي بيانات توجد وحدتان معجميتان، واحدة لكل فعل، لكن في ويكاموس لا يُمكن إنشاء صفحتين بنفس الاسم. سأفكر في حل لهذه المشكلة وأوافيك بالجديد. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:28، 27 يوليو 2026 (ت ع م) ::حالة أخرى حيث يشترك جذران في نفس صيغة الفعل: ::* جذر ت-ل-و على وزن تفعّل => [[تَتَلَّى]] ([https://ar-senses.toolforge.org/review/L1533461 رابط المُفردة في الأداة]). ::* جذر ت-ل-ي على وزن تفعّل => [[تَتَلَّى]] ::[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:42، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: آرَنَ (L1532612) == '''آرَنَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1532612|18بك|ويكي بيانات: L1532612]] [[ملف:Wiktionary-logo.svg|وصلة=آرَنَ|18بك|صفحة ويكاموس]] · الجذر: أرن · ([[toolforge:ar-senses/review/L1532612|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:36، 27 يوليو 2026 (ت ع م) :شكرا لك {{مس|رِفْد}} على التبليغ، المشكل سببه حالة خاصة حيث صيغة الفعل هي نفسها على وزنين: :* جذر ء ر ن، على وزن أفعل => آرَنَ :* جذر ء ر ن، على وزن فاعل => آرَنَ :[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:38، 27 يوليو 2026 (ت ع م) ::{{أصلح}} ونُشِر في ويكاموس: [[آرَنَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:39، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: دَمْشَقَ (L1535560) == '''دَمْشَقَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1535560|18بك|ويكي بيانات: L1535560]] [[ملف:Wiktionary-logo.svg|وصلة=دَمْشَقَ|18بك|صفحة ويكاموس]] · الجذر: دمشق · ([[toolforge:ar-senses/review/L1535560|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''أثناء محاولة الإضافة في ويكي بيانات يظهر: invalid literal for int0 with :خطأ في النشر بويكي بيانات 'base 10: 'None'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:45، 27 يوليو 2026 (ت ع م) :{{أصلح}}. شكرا لك على التبليغ أخي {{مس|رِفْد}}. المُشكلة كانت بسبب مُعرف خاطئ لأحد المعاجم في قاعدة البيانات. أعدت إنشاء المعاني في ويكي بيانات وأنشأت الصفحة في ويكاموس: [[دَمْشَقَ]]. [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:48، 27 يوليو 2026 (ت ع م) == بلاغ من أداة مراجعة المعاني: أَبَانَ (L1477016) == '''أَبَانَ''' [[ملف:Wikidata Favicon color.svg|وصلة=d:Lexeme:L1477016|18بك|ويكي بيانات: L1477016]] [[ملف:Wiktionary-logo.svg|وصلة=أَبَانَ|18بك|صفحة ويكاموس]] · الجذر: بين · ([[toolforge:ar-senses/review/L1477016|رابط المُفردة في الأداة]]) إبلاغ من الزميل {{مس|رِفْد}}: ''لا يمكن نشرها في ويكاموس عند الضغط على زر النشر يظهر: "خطأ: الصفحة موجودة بالفعل".'' --[[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:52، 27 يوليو 2026 (ت ع م) :{{أصلح}}. فعلا [[أَبَانَ|الصفحة]] كانت موجودة سَلَفًا، وكان المفروض من الأداة أن لا تُعطيك خيار إنشائها. المشكل كان مصدرة خطأ في تصريف الأجوف اليائي على وزن أفعل، فكانت الخوارزمية تقترح كلمة "أَبْيَنَ" (خطأ) بدل "أَبَانَ" (صحيح). [[مستخدم:ForzaGreen|وائل]] ([[نقاش المستخدم:ForzaGreen|نقاش]]) 11:57، 27 يوليو 2026 (ت ع م) qn0dfoe2k5fj3ia851qt438plln131e قالب:أصلح 10 240650 1097604 2026-07-27T00:04:11Z ForzaGreen 28665 إنشاء قالب ردّ للنقاشات على غرار {{نفذ}} 1097604 wikitext text/x-wiki [[ملف:Yes Check Circle.svg|وصلة=|16بك]] '''{{{1|أُصلح}}}'''<noinclude> == انظر أيضا == * {{قا|نفذ}} * {{قا|لم ينفذ}} [[تصنيف:قوالب نقاش|{{اسم_الصفحة}}]]</noinclude> lqeevicxnmfixctzylv1ywanlfhoyjz