Pergi ke kandungan

Modul:ms-headword

Daripada Wikikamus


local export = {}
local pos_functions = {}

local require = require
local require_when_needed = require("Module:require when needed")

-- Modules
local m_debug_track = require("Module:debug/track")
local m_head = require("Module:headword")
local m_params = require("Module:parameters")

-- Utilities
local u = require_when_needed("Module:string/char")
local PAGENAME = mw.loadData("Module:headword/data").pagename

-- Languages
local lang = require("Module:languages").getByCode("ms")
local langname = lang:getCanonicalName()
local script = lang:findBestScript(PAGENAME) -- Latn or ms-Arab

local function track(page)
	return m_debug_track("ms-headword/" .. page)
end

function export.show(frame)
	local iparams = {
		[1] = {required = true},
	}
	local params = {
		["head"] = {list = true, disallow_holes = true},
		["j"] = {list = true, disallow_holes = true},
		["id"] = true,
		["sort"] = true,
		["nolinkhead"] = {type = "boolean"},
		["json"] = {type = "boolean"},
		["pagename"] = true, -- for testing
	}
	
	local par_args = frame:getParent().args
	
	-- Process frame arguments i.e. {{#invoke:ms-headword|show|arguments}}
	local iargs = m_params.process(frame.args, iparams)
	local poscat = iargs[1]

	-- Append additional params in pos_functions to global params in export.show
	-- Necessary before processing parent args as each part of speech has their own parameters
	-- See pos_functions["adjectives"] for example
	if pos_functions[poscat] then
		local posparams = pos_functions[poscat].params
		if type(posparams) == "function" then
			posparams = posparams(lang)
		end
		for key, val in pairs(posparams) do
			params[key] = val
		end
	end

	-- Process frame parent arguments i.e. {{ms-head|arguments}}
	local args = m_params.process(par_args, params)
	
	local pagename = args.pagename or mw.title.getCurrentTitle().text
	local head = args.head and args.head[1] or PAGENAME
	
	local data = {
		lang = lang,
		pos_category = poscat,
		categories = {},
		heads = args.head,
		inflections = {},
		pagename = pagename,
		id = args.id,
		sort_key = args.sort,
		is_suffix = false,
		translits = { "-" },
		altform = script:getCode() == "ms-Arab"
	}
	
	if (mw.ustring.find(PAGENAME, "اء[وي]")) then
		head = mw.ustring
			.gsub(PAGENAME, "(ا)(ء)([وي])", "%1<sup>%2</sup>%3") -- superscript hamza
	end

	if args["ku"] ~= nil or args["mu"] ~= nil or args["nya"] ~= nil then
		track("clean up kumunya")
	end

	if args["r"] ~= nil then
		track("clean up r")
	end
	
	local function any_match(pattern)
	    if head and mw.ustring.find(head, pattern) then
	        return true
	    end
	
	    for _, v in ipairs(args["j"]) do
	        if v and mw.ustring.find(v, pattern) then
	            return true
	        end
	    end
	
	    return false
	end

	if any_match("[كڬ]") then
	    track("incorrect Jawi spelling")
	    error("Please use ک in place of ك and ݢ in place of ڬ.")
	end
	
	-- special marks: ZWSP (200B), ZWNJ (200C), ZWJ (200D), LRM (200E), RLM (200F)
	local special_marks = "[" .. u(0x200B) .. "-" .. u(0x200F) .. "]"
	if any_match(special_marks) then
	    track("incorrect Jawi spelling")
	    error("Please remove ZWSP, ZWNJ, ZWJ, LRM, and RLM from title/headword.")
	end

	local letter_replacements = { ["M"] = "m", ["N#"] = "ng", ["R#"] = "r" }
	local pattern_replacements = { }

	function export.affixation(text, affix)
		local first_consonant = ""
		if affix == "-" then
			text = text .. "-" .. text
		elseif mw.ustring.match(affix, "^%-.+%-$") then
			first_consonant = mw.ustring.match(text, "^[^aeiou]+")
			text = first_consonant .. mw.ustring.sub(affix, 2, -2) ..
				mw.ustring.sub(text, mw.ustring.len(first_consonant) + 1,
					-1)
		else
			text = gsub(affix, "%-", "#" .. text)
			text = gsub(text, "N([^aeiou]*)([aeiou])([^aeiou]*)$", "nge%1%2%3")
			text = gsub(text, "meN#p", "memp")
			for pattern, replacement in pairs(pattern_replacements) do
				text = gsub(text, pattern, replacement)
			end
			text = gsub(text, "[MNR]#?", letter_replacements)
			text = gsub(text, "#", "")
		end
		return text
	end

	local jawi = { label = "ejaan Jawi" }
	
	for _, term in ipairs(args["j"]) do
		if term then
			table.insert(jawi, { term = term })
		end
	end
	if #jawi > 0 then table.insert(data.inflections, jawi) end

	if script:getCode() == "ms-Arab" then
		table.insert(data.categories, "Perkataan dalam tulisan Arab bahasa Melayu")
	end

    if pos_functions[poscat] and pos_functions[poscat].func then
        pos_functions[poscat].func(args, data)
    end

	return m_head.full_headword(data)
end

pos_functions["kata nama"] = {
	params = {
		[1] = {list = true},
		["pl"] = {alias_of = 1, list = true},
	},
	func = function(args, data)
		-- Main code for noun plurality
		local jawi = data.altform
		local pl1 = args[1][1]
		
		-- Helpers -------------------------------------------------------
		local function insert_plural(tbl, ...)
    		for _, v in ipairs(...) do
        		table.insert(tbl, v)
    		end

    		return tbl
		end
		
		local function make_default_plural(pagename, jawi)
			local plural = {}
			local jawi_letter = "ابتةثجچحخدذرزسشصضطظعغڠفڤقکݢلمنوۏهءيىڽ"
			-- Auto-detect full reduplication
			if pagename:match("^([a-zA-Z" .. jawi_letter .. "]+)%-%1$") then
				table.insert(
					plural, "[[" .. pagename .. "]]"
				)
				return plural
			end

			local subwords = mw.text.split(pagename, "%s")
			local firstword = subwords[1]
			local plural_entry = "[[" .. firstword .. "]]-[[" .. firstword .. "]]"
			local plural_entry2_latn = "[[" .. firstword .. "]][[2#" .. langname .. "|<sup>2</sup>]]"
			local plural_entry2_j = "[[" .. firstword .. "]][[ـ٢|٢]]"

			if #subwords > 1 then
				-- Concatenate all the remaining subwords (from 2 onward)
				for i = 2, #subwords do
					plural_entry = plural_entry .. " [[" .. subwords[i] .. "]]"
					if jawi then
						plural_entry2_j = plural_entry2_j .. " [[" .. subwords[i] .. "]]"
					else
						plural_entry2_latn = plural_entry2_latn .. " [[" .. subwords[i] .. "]]"
					end
				end
			end
			
			table.insert(
				plural, plural_entry
			)
			
			if not jawi then
				table.insert(
					plural, plural_entry2_latn
				)
			end
			
			if jawi then
				table.insert(
					plural, plural_entry2_j
				) -- add "2" only first word
			end
			
			return plural
		end
		------------------------------------------------------------------
		
		-- Unknown or uncertain and requests
		if pl1 == "req" then
			table.insert(data.categories, "Permintaan bentuk jamak dalam entri bahasa " .. langname)
		elseif pl1 == "?" then
			table.insert(data.categories, "Kata nama dengan bentuk jamak yang tidak diketahui atau tidak pasti bahasa " .. langname)
		
		-- Uncountable and semi-countable
		elseif pl1 == "-" then
			table.insert(data.categories, "Kata nama tidak berbilang bahasa " .. langname)
			table.insert(data.inflections, {label = "[[Lampiran:Glosari#tidak berbilang|tidak berbilang]]"})
		elseif pl1 == "u" then
			local pl_countable = {label = "biasanya [[Lampiran:Glosari#tidak berbilang|tidak berbilang]]"}
			local pl_plural = {label = "jamak"}
			table.insert(data.categories, "Kata nama berbilang bahasa " .. langname)
			table.insert(data.categories, "Kata nama tidak berbilang bahasa " .. langname)
			pl_plural = insert_plural(pl_plural, make_default_plural(data.pagename, jawi))
			table.insert(data.inflections, pl_countable)
			table.insert(data.inflections, pl_plural)
		elseif pl1 == "~" then
			local pl_countable = {label = "[[Lampiran:Glosari#berbilang|berbilang]] dan [[Lampiran:Glosari#tidak berbilang|tidak berbilang]]"}
			local pl_plural = {label = "jamak"}
			table.insert(data.categories, "Kata nama berbilang bahasa " .. langname)
			table.insert(data.categories, "Kata nama tidak berbilang bahasa " .. langname)
			pl_plural = insert_plural(pl_plural, make_default_plural(data.pagename, jawi))
			table.insert(data.inflections, pl_countable)
			table.insert(data.inflections, pl_plural)
		elseif pl1 == "pt" or pl1 == "p" then
			error(langname .. " doesn't have plurale tantum, please delete the causing parameter.")
		elseif pl1 == "st" or pl1 == "s" then
			error(langname .. " doesn't have singulare tantum, please replace the causing parameter with |-.")
		elseif pl1 == "1" then
			error("The parameter |pl=1 is invalid. Please specify the plurality with an existing value.")
		else
			-- Countable
			local pl = {label = "jamak"}
			if not pl1 or pl1 == "+" then
				pl = insert_plural(pl, make_default_plural(data.pagename, jawi))
			elseif pl1 == "a" then
				pl = insert_plural(pl, make_default_plural(data.pagename, jawi))
				table.insert(pl, "[[para]] " .. data.pagename)
			elseif pl1 == "*" then
				table.insert(pl, data.pagename)
	        elseif pl1 == "*+" then
				table.insert(pl, data.pagename)
				pl = insert_plural(pl, make_default_plural(data.pagename, jawi))
			else
				for _, i in ipairs(args[1]) do
					local exclude = {
						a = true,
						u = true,
						req = true,
						["+"] = true,
						["~"] = true,
						["-"] = true,
						["*"] = true,
						["*+"] = true,
						}
					-- Incase first argument is used not as the first argument
					-- (e.g. {{ms-noun|custom-plural|a}})
					if not exclude[i] then
						table.insert(pl, i)
					end
				end
			end
			table.insert(data.inflections, pl)
		end
	end
}

-- Generally, proper nouns don't have plural unless in certain cases
-- e.g. Muslim -> Muslimin
pos_functions["kata nama khas"] = {
	params = {
		[1] = {list = true}, -- allow custom plural
		["pl"] = {alias_of = 1, list = true},
	},
	func = function(args, data)
		local pl1 = args[1]
		local pl = { label = "plural" }
		
		if #pl1 ~= 0 then
			for _, v in ipairs(pl1) do
				table.insert(pl, v)
			end
			table.insert(data.inflections, pl)
		end
	end
}

pos_functions["kata kerja"] = {
    params = {
        [1]     = { list = true },
        active  = { list = true },
        bare    = { list = true },
        passive = { list = true },
    },

    func = function(args, data)
        local base = data.pagename
        local jawi = data.altform
        local utf = mw.ustring
        
        local equivalent_letters = {
			k = "ک", t = "ت", s = "س", p = "ڤ",
		}

        -- Helpers -------------------------------------------------------
        local function starts_with(s, prefix)
            return utf.sub(s, 1, utf.len(prefix)) == prefix
        end

        local function first_n(s, n)
            return utf.sub(s, 1, n)
        end

        local function rest_from(s, n)
            return utf.sub(s, n + 1)
        end
        
        local function is_equiv(first, target)
			return first == target or first == equivalent_letters[target]
		end

        local function is_vowel_char(ch)
            return ch:match('[aiueoاويى]') ~= nil
        end

        local function vowel_count(s)
            local count = 0
            for ch in utf.gmatch(s, '.') do
                if is_vowel_char(ch) then
                    count = count + 1
                    if count > 1 then return count end
                end
            end
            return count
        end

        local function make_form(prefix_latin, prefix_jawi, use_jawi, rest)
            if use_jawi then
                return prefix_jawi .. (rest or base)
            else
                return prefix_latin .. (rest or base)
            end
        end
        ----------------------------------------------------------------
        
        if args[1] and args[1][1] == "b" then
	        -- Default base forms
	        local default_active_latin  = "me" .. base
	        local default_passive_latin = "di" .. base
	        local default_active_jawi   = "م" .. base
	        local default_passive_jawi  = "د" .. base
	
	        local active_form = jawi and default_active_jawi or default_active_latin
	        local passive_form = jawi and default_passive_jawi or default_passive_latin
	
	        -- If base has more than 1 vowel, use 'meng'/'مڠ' forms
	        local vcount = vowel_count(base)
	
	        if vcount == 1 then
	            -- For bases with exactly one vowel: use 'menge' / 'مڠ' + base
	            if jawi then
	                active_form = "مڠ" .. base
	            else
	                active_form = "menge" .. base
	            end
	        else
	            local first_two = first_n(base, 2)
	            local first = first_n(base, 1)
	
	            -- Mapping for special two-letter onsets
	            local two_map = {
	                ["sy"] = { latin = "men", jawi = "من", strip = false },
	                ["pl"] = { latin = "mem", jawi = "مم", strip = false },
	                ["ڤل"] = { latin = "mem", jawi = "مم", strip = false },
	                ["pr"] = { latin = "mem", jawi = "مم", strip = false },
	                ["ڤر"] = { latin = "mem", jawi = "مم", strip = false },
	                ["tr"] = { latin = "men", jawi = "من", strip = false },
	                ["تر"] = { latin = "men", jawi = "من", strip = false },
	                ["kl"] = { latin = "meng", jawi = "مڠ", strip = false },
	                ["کل"] = { latin = "meng", jawi = "مڠ", strip = false },
	                ["kr"] = { latin = "meng", jawi = "مڠ", strip = false },
	                ["کر"] = { latin = "meng", jawi = "مڠ", strip = false },
	                ["kh"] = { latin = "meng", jawi = "مڠ", strip = false },
	            }
	
	            if two_map[first_two] then
	                local m = two_map[first_two]
	                active_form = make_form(m.latin, m.jawi, jawi)
	            else
	                -- Single-letter rules and character-class rules
	                if is_equiv(first, "k") then
	                    -- drop first letter and use meng / مڠ + rest
	                    if jawi then
	                        active_form = "مڠ" .. rest_from(base, 1)
	                    else
	                        active_form = "meng" .. rest_from(base, 1)
	                    end
	                elseif is_equiv(first, "t") then
	                    if jawi then
	                        active_form = "من" .. rest_from(base, 1)
	                    else
	                        active_form = "men" .. rest_from(base, 1)
	                    end
	                elseif is_equiv(first, "s") then
	                    if jawi then
	                        active_form = "مڽ" .. rest_from(base, 1)
	                    else
	                        active_form = "meny" .. rest_from(base, 1)
	                    end
	                elseif is_equiv(first, "p") then
	                    if jawi then
	                        active_form = "مم" .. rest_from(base, 1)
	                    else
	                        active_form = "mem" .. rest_from(base, 1)
	                    end
	                else
	                    if first:match("[cjdzچجدزش]") then
	                        active_form = make_form("men", "من", jawi)
	                    elseif first:match("[bfvبفۏ]") then
	                        active_form = make_form("mem", "مم", jawi)
	                    elseif first:match("[ghݢݢحخهة]") then
	                        active_form = make_form("meng", "مڠ", jawi)
	                    elseif is_vowel_char(first) then
	                        active_form = make_form("meng", "مڠ", jawi)
	                    else
	                        -- fallback: default 'me' form
	                        active_form = jawi and ("م" .. base) or ("me" .. base)
	                    end
	                end
	            end
	        end
	        
			args.active  = { active_form }
			args.passive = { passive_form }
			
        elseif args[1] and args[1][1] == "a" then
        	if #args[1] > 2 then
        		error("This template doesn't use argument |3=")
        	elseif #args[1] < 2 then
        		error("Please enter the bare form of the verb.")
        	end
        	
	        local default_passive_latin = "di" .. args[1][2]
	        local default_passive_jawi  = "د" .. args[1][2]
	        
	        local passive_form = jawi and default_passive_jawi or default_passive_latin
		
			args.bare = { args[1][2] }
			args.passive = { passive_form }
			
	    else
	    	-- still, if active provided but passive missing, generate passive
            if args.active and args.active[1] and (not args.passive or not args.passive[1]) then
                args.passive = { jawi and ("د" .. base) or ("di" .. base) }
            end
	    end

        if args.active and args.active[1] then
            args.active.label = "aktif"
            table.insert(data.inflections, args.active)
        end
        if args.bare and args.bare[1] then
			args.bare.label = "bentuk sebenar"
            table.insert(data.inflections, args.bare)
        end
        if args.passive and args.passive[1] then
            args.passive.label = "pasif diri ketiga"
            table.insert(data.inflections, args.passive)
        end
    end
}

pos_functions["bentuk kata kerja"] = pos_functions["kata kerja"]

pos_functions["kata sifat"] = {
	params = {
		[1]           = { list = true }, -- allow |~|, |-|, |?|, |+|, |e|
		comparative   = { alias_of = 1, list = false },
		superlative   = { list = true },
		equative      = { list = true },
	},
	func = function(args, data)
	    local base     = data.pagename
	    local jawi     = data.altform
	    local first    = mw.ustring.sub(base, 1, 1)
	    local eq_enable = false
	    
	    -- Comperative & Superlative
		local periphrastic = {
		    comp_latn = "[[lebih]] "  .. base,
		    sup_latn  = "[[paling]] " .. base,
		    comp_j =    "[[لبيه]] "   .. base,
		    sup_j  =    "[[ڤاليڠ]] "  .. base,
		}

		-- Equative is non-periphrastic (seBASE)
		local equative = {
		    latn = "se"     .. base,
		    jawi = "[[س]]" .. base,
		}
	    
	    -- Helpers -------------------------------------------------------
	    local function eq_check(tbl)
			local found = false
			for i = #tbl, 1, -1 do -- iterate backwards when removing
				if tbl[i] == "e" then
					table.remove(tbl, i)
					found = true
				end
			end
			return tbl, found
	    end
	    ------------------------------------------------------------------
	    
	    args[1], eq_enable = eq_check(args[1])
	
	    -- 1) “?” → no inflections, but add request category and stop.
	    if args[1][1] == "?" then
	    	table.insert(data.categories,
				"Permintaan infleksi dalam entri kata sifat bahasa " .. langname)
			return
	    end
	
	    -- 2) “-” → just “not comparable” and stop.
	    if args[1][1] == "-" then
			table.insert(data.inflections, { label = "[[Lampiran:Glosari#tidak sebanding|tidak sebanding]]" })
			table.insert(data.categories, "Kata sifat tidak sebanding bahasa " .. langname)
			return
	    end
	
	    -- 3) “~” → insert the note, but then fall through to normal comp/sup.
	    if args[1][1] == "~" then
			table.insert(data.inflections, { label = "umumnya [[Lampiran:Glosari#tidak sebanding|tidak sebanding]]" })
	    end
	
	    -- 4) Comparative: always show (default)
	    if args[1][1] == nil or args[1][1] == "+" then
	    	if jawi then
	    		args.comparative = { periphrastic.comp_j }
				args.superlative = { periphrastic.sup_j }
	    	else
				args.comparative = { periphrastic.comp_latn }
				args.superlative = { periphrastic.sup_latn }
			end
			-- If "+" flag, additionally show morphological superlative
		    if args[1][1] == "+" then
		        local is_r = (first == "r" or first == "ر")
		        local morph = jawi
		            and (is_r and "ت"  or "تر")
		            or  (is_r and "te" or "ter")
		        
		        -- First show periphrastic form (Jawi or Latin), then synthetic form
				args.superlative = {
					jawi and periphrastic.sup_j or periphrastic.sup_latn,
					morph .. base
				}
		    end
	    end
	
	    -- 5) Equative: only when e is called
	    if eq_enable then
	    	args.equative = { jawi and equative.jawi or equative.latn }
	    else
			args.equative = nil
	    end
	
	    -- 6) emit in order
	    if args.comparative  and args.comparative[1] then
			args.comparative.label = "[[Lampiran:Glosari#sebanding|sebanding]]"
			table.insert(data.inflections, args.comparative)
	    end
	    if args.superlative  and args.superlative[1] then
			args.superlative.label = "[[Lampiran:Glosari#superlatif|superlatif]]"
			table.insert(data.inflections, args.superlative)
	    end  
	    if args.equative     and args.equative[1] then
			args.equative.label = "[[Lampiran:Glosari#samaan|samaan]]"
			table.insert(data.inflections, args.equative)
	    end
	end,
}

pos_functions["penjodoh bilangan"] = {
	params = {
	    [1] = { list = true }, -- allow |+| or custom singular
	},
	func = function(args, data)
	    local base = data.pagename
	    local jawi = data.altform
	    
	    -- Helpers -------------------------------------------------------
	    local function alt_sing(tbl)
	    	if #tbl == 0 then return false end
			if #tbl == 1 and tbl[1] == "+" then
        		return false
    		end
    		return true
	    end
	    
	    local function plus_sign(tbl)
	    	for _, v in ipairs(tbl) do
	    		if v == "+" then
	    			return true
	    		end
	    	end
	    	return false
	    end
	    ------------------------------------------------------------------
	
	    -- decide what form(s) to emit
	    local sing_forms = {label = "mufrad"}

	    -- if nothing explicit was passed, or "+" is called, auto‑generate
	    if #args[1] == 0 or plus_sign(args[1]) then
	    	if jawi then
	    		table.insert(sing_forms, "سي" .. base)
	    	else
	    		table.insert(sing_forms, "se" .. base)
	    	end
	    end
	    
		-- If there's alternative singular form
		-- {e.g. {{ms-classifier|sorang}}
		if alt_sing(args[1]) then
			for _, v in ipairs(args[1]) do
				table.insert(sing_forms, v)
			end
		end
	
	    -- emit an inflection labeled “singular”
	    table.insert(data.inflections, sing_forms)
    	table.insert(data.categories, "Penjodoh bilangan bahasa " .. langname)
	end
}

return export