Jump to content

Module:smi-common

From Wiktionary, the free dictionary

This module contains functions for inflection in the Sami languages.


local export = {}

local function gsub_nil(text, pattern, repl)
	local num
	text, num = mw.ustring.gsub(text, pattern, repl)
	
	if num == 0 then
		return nil
	else
		return text
	end
end

local function get_item(word, patterns)
	local ret, remainder
	
	for _, pattern in ipairs(patterns) do
		remainder, ret = mw.ustring.match(word, pattern)
		
		if remainder then
			return remainder, ret
		end
	end
end

local function transform(cons, patterns)
	if not patterns then
		return nil
	end
	
	local newcons
	
	for _, pattern in ipairs(patterns) do
		if newcons then
			break
		end
		
		if pattern[2] then
			newcons = gsub_nil(cons, pattern[1], pattern[2])
		else
			newcons = mw.ustring.match(cons, pattern[1])
		end
	end
	
	return newcons
end

local function make_new_pattern(from, to)
	local i = 1
	
	if from:sub(1, 1) == "^" then
		to = "^" .. to
	end
	
	if from:sub(-1) == "$" then
		to = to .. "$"
	end
	
	for match in mw.ustring.gmatch(from, "%([^()]+%)") do
		to = mw.ustring.gsub(to, "%%" .. i, match, 1)
		i = i + 1
	end
	
	i = 1
	local repls
	
	while true do
		from, repls = mw.ustring.gsub(from, "%([^()]+%)", "%%" .. i, 1)
		i = i + 1
		
		if repls == 0 then
			break
		end
	end
	
	from = mw.ustring.gsub(from, "[$^]", "")
	
	return to, from
end

export.Stem = {}
export.Stem.__index = export.Stem

export.make_constructor = function(langdata)
	-- Add weak and extra-strong patterns to the langdata
	if langdata.scons then
		for _, quantity in ipairs({3, 2}) do
			for _, pattern in ipairs(langdata.scons[quantity]) do
				if pattern[2] then
					local from = make_new_pattern(pattern[1], pattern[2])
					table.insert(langdata.scons[quantity - 1], {from})
				end
				
				if quantity == 2 and pattern[3] then
					local from, to = make_new_pattern(pattern[1], pattern[3])
					table.insert(langdata.scons[quantity + 1], {from, to})
				end
			end
		end
	end
	
	return function(stem, gradation, final)
		local self = setmetatable({langdata = langdata}, export.Stem)
		
		self.init = stem
		self.final = final
		
		-- Unstressed consonant, consonant margin
		self.init, self.ucons = get_item(self.init, self.langdata.consonant)
		
		-- Unstressed vowel, latus
		self.init, self.uvowel = get_item(self.init, self.langdata.vowel)
		
		-- Stressed consonant, consonant center
		self.init, self.scons = get_item(self.init, self.langdata.consonant)
		
		-- Stressed vowel, vowel center
		self.init, self.svowel = get_item(self.init, self.langdata.vowel)
		
		if self.langdata.preprocess then
			self.langdata.preprocess(self)
		end
		
		if self.scons ~= "" and self.langdata.scons then
			-- Determine the quantity and gradation pattern of the stressed consonant
			local matched_patterns = {}
			
			for quantity, patterns in ipairs(self.langdata.scons) do
				for _, pattern in ipairs(patterns) do
					if mw.ustring.find(self.scons, pattern[1]) then
						table.insert(matched_patterns, {pattern = pattern, quantity = quantity})
					end
				end
			end
			
			if not matched_patterns[1] then
				error("The quantity of the consonant \"" .. self.scons .. "\" cannot be determined.")
			end
			
			local scons_pattern = matched_patterns[#matched_patterns].pattern
			self.quantity = matched_patterns[#matched_patterns].quantity
			
			-- Make weak and extra-strong grades
			if gradation then
				if not scons_pattern[2] then
					error("The consonant \"" .. self.scons .. "\" cannot gradate.")
				end
				
				self.gradation = {}
				self.gradation.strong = {scons = self.scons, quantity = self.quantity}
				self.gradation.extra = {scons = self.scons, quantity = self.quantity}
				self.gradation.weak = {
					scons = mw.ustring.gsub(self.scons, scons_pattern[1], scons_pattern[2]),
					quantity = self.quantity - 1,
				}
				
				if scons_pattern[3] then
					self.gradation.extra = {
						scons = mw.ustring.gsub(self.scons, scons_pattern[1], scons_pattern[3]),
						quantity = self.quantity + 1,
					}
				end
				
				if gradation == "Q31" then
					if self.quantity ~= 3 then
						error("The consonant \"" .. self.scons "\" is not quantity 3 and therefore cannot have quantity 3-1 gradation.")
					end
					
					-- Find the weak grade of the weak grade
					local scons_pattern
					
					for _, pattern in ipairs(self.langdata.scons[2]) do
						if mw.ustring.find(self.gradation.weak.scons, pattern[1]) then
							scons_pattern = pattern
							break
						end
					end
					
					if not scons_pattern then
						error("The consonant \"" .. self.scons .. "\" cannot gradate to quantity 1.")
					end
					
					self.gradation.weak = {
						scons = mw.ustring.gsub(self.gradation.weak.scons, scons_pattern[1], scons_pattern[2]),
						quantity = 1,
					}
				end
			end
		else
			self.quantity = 1
		end
		
		return self
	end
end

function export.Stem:make_form(data)
	data.variant = data.variant or "normal"
	
	local form = {
		svowel = self.svowel,
		scons = self.scons,
		uvowel = self.uvowel,
		ucons = self.ucons,
		ending = data.ending or "",
		quantity = self.quantity,
	}
	
	if self.gradation then
		if not data.grade then
			error("No grade was specified for ending \"" .. form.ending .. "\".")
		end
		
		form.scons = self.gradation[data.grade].scons
		form.quantity = self.gradation[data.grade].quantity
	end
	
	-- Turn ucons into its word-final form if applicable
	if form.ucons ~= "" and self.langdata.make_final_if then
		for _, pattern in ipairs(self.langdata.make_final_if) do
			if mw.ustring.find(form.ending, pattern) then
				form.ucons = self.final or self:make_final(form.ucons)
				break
			end
		end
	end
	
	if data.variant == "none" then
		if form.ending ~= "" then
			error("The variant \"none\" can only be used with no ending.")
		elseif form.ucons ~= "" then
			error("The variant \"none\" can only be used with vowel-final stems.")
		end
		
		-- Turn scons into its word-final form
		form.uvowel = ""
		form.scons = self:make_final(form.scons)
	else
		-- Retrieve vowel variant data from the table
		if not self.langdata.vowel_variants[data.variant] then
			error("The stem variant \"" .. data.variant .. "\" does not exist.")
		end
		
		local vowel_info = self.langdata.vowel_variants[data.variant][form.uvowel]
		local vowel_effect
		
		if vowel_info then
			form.uvowel = vowel_info[1]
			vowel_effect = vowel_info[2]
		end
		
		-- Call language-specific function for postprocessing
		if self.langdata.postprocess then
			self.langdata.postprocess(form, vowel_effect)
		end
	end
	
	return self.init .. form.svowel .. form.scons .. form.uvowel .. form.ucons .. form.ending
end

function export.Stem:make_final(cons)
	local newcons = transform(cons, self.langdata.to_final)
	
	if newcons then
		return newcons
	else
		error("The consonant(s) \"" .. cons .. "\" are not allowed word-finally, but this template does not know how to replace them with allowed ones.")
	end
end

return export