Modul:inflection/ru/noun/parse args

A modult a Modul:inflection/ru/noun/parse args/doc lapon tudod dokumentálni

local dev_prefix = ''
-- dev_prefix = 'User:Vitalik/'  -- comment this on active version

local export = {}
local _ = require('Module:' .. dev_prefix .. 'inflection/tools')


local function get_cyrl_animacy(index, gender)
	_.log_func('parse_args', 'get_cyrl_animacy')

	if _.extract(index, '^' .. gender .. 'о//' .. gender) then
		return 'an//in'
	elseif _.extract(index, '^' .. gender .. '//' .. gender .. 'о') then
		return 'in//an'
	elseif _.extract(index, '^' .. gender .. 'о') then
		return 'an'
	else
		return 'in'
	end
end


local function extract_gender_animacy(data)
	_.log_func('parse_args', 'extract_gender_animacy')

	local convert_animacy, orig_index, rest_index

	-- мо-жо - mf a
	-- ж//жо - f ina//a
	-- мо - m a
	-- с  - n ina
	data.pt = false

	if _.startswith(data.index, 'п') then
		data.adj = true
	elseif _.extract(data.index, '^м//ж') or _.extract(data.index, '^m//f') then
		data.gender = 'mf'
		data.animacy = 'in'
	elseif _.extract(data.index, '^м//с') or _.extract(data.index, '^m//n') then
		data.gender = 'mn'
		data.animacy = 'in'
	elseif _.extract(data.index, '^ж//м') or _.extract(data.index, '^f//m') then
		data.gender = 'fm'
		data.animacy = 'in'
	elseif _.extract(data.index, '^ж//с') or _.extract(data.index, '^f//n') then
		data.gender = 'fn'
		data.animacy = 'in'
	elseif _.extract(data.index, '^с//м') or _.extract(data.index, '^n//m') then
		data.gender = 'nm'
		data.animacy = 'in'
	elseif _.extract(data.index, '^с//ж') or _.extract(data.index, '^n//m') then
		data.gender = 'nm'
		data.animacy = 'in'
	elseif _.extract(data.index, '^мо%-жо') or _.extract(data.index, '^mf a') then
		data.gender = 'f'
		data.animacy = 'an'
		data.common_gender = true
	elseif _.extract(data.index, '^мн') then
		data.gender = ''
		data.animacy = ''
		data.common_gender = false
		data.pt = true
		if _.extract(data.index, 'одуш') then
			data.animacy = 'an'
		elseif _.extract(data.index, 'неод') then
			data.animacy = 'in'
		end
		-- TODO: Также удалить это ниже для rest_index, аналогично как удаляется м, мо и т.п.
		data.rest_index = data.index
	elseif _.extract(data.index, '^мс') then
		data.pronoun = true
	elseif _.extract(data.index, '^м') then
		data.gender = 'm'
		data.animacy = get_cyrl_animacy(data.index, 'м')
		data.common_gender = false
	elseif _.extract(data.index, '^ж') then
		data.gender = 'f'
		data.animacy = get_cyrl_animacy(data.index, 'ж')
		data.common_gender = false
	elseif _.extract(data.index, '^с') then
		data.gender = 'n'
		data.animacy = get_cyrl_animacy(data.index, 'с')
		data.common_gender = false
	else
		data.gender = _.extract(data.index, '^([mnf])')
		data.animacy = _.extract(data.index, '^[mnf] ([a-z/]+)')
		data.common_gender = false
		if data.animacy then
			convert_animacy = {}
			convert_animacy['in'] = 'in'
			convert_animacy['an'] = 'an'
			convert_animacy['ina'] = 'in'
			convert_animacy['a'] = 'an'
			convert_animacy['a//ina'] = 'an//in'
			convert_animacy['ina//a'] = 'in//an'
			convert_animacy['anin'] = 'an//in'
			convert_animacy['inan'] = 'in//an'
			data.animacy = convert_animacy[data.animacy]
		end
	end

	-- Удаляем теперь соответствующий кусок индекса
	if (data.gender or data.gender == '') and data.animacy and not data.adj and not data.pronoun then
		_.log_value(data.index, 'data.index')
		orig_index = mw.text.trim(data.index)

--		local test1 = _.replaced(data.index, '^mf a ?', '')
--		mw.log('test1 = ' .. mw.text.trim(test1))
--
--		local test2 = _.replaced(data.index, '^mf a ', '')
--		mw.log('test2 = ' .. mw.text.trim(test2))
--
--		local test3 = _.replaced(data.index, 'mf a ', '')
--		mw.log('test3 = ' .. mw.text.trim(test3))
--
--		local test4 = _.replaced(data.index, 'mf a', '')
--		mw.log('test4 = ' .. mw.text.trim(test4))
--
--		local test5 = mw.text.trim(_.replaced(data.index, '^mf a ?', ''))
--		mw.log('test5 = ' .. test5)
--
--		local test6 = _.replaced(data.index, '^mf a ?', '')
--		mw.log('test6 = ' .. test6)
--		local test7 = mw.text.trim(test6)
--		mw.log('test7 = ' .. test7)

		-- TODO: Simplify things a bit here (сделать циклом!):

		rest_index = _.replaced(data.index, '^mf a ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "mf a" из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
		rest_index = _.replaced(data.index, '^[mnf]+ [a-z/]+ ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "[mnf] [in/an]" из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
		rest_index = _.replaced(data.index, '^мн%.? неод%.? ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "мн. неод." из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
		rest_index = _.replaced(data.index, '^мн%.? одуш%.? ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "мн. одуш." из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
		rest_index = _.replaced(data.index, '^мн%.? ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "мн." из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
		rest_index = _.replaced(data.index, '^[-мжсо/]+%,? ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "м/ж/с/мо/жо/со/..." из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
		return {error = 'TODO'}  -- dict -- TODO: process such errors
	elseif data.adj then
		_.log_value(data.index, 'data.index (п)')
		orig_index = mw.text.trim(data.index)

		rest_index = _.replaced(data.index, '^п ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "п" из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
	elseif data.pronoun then
		_.log_value(data.index, 'data.index (мс)')
		orig_index = mw.text.trim(data.index)

		rest_index = _.replaced(data.index, '^мс ?', '')
		if rest_index ~= orig_index then
			data.rest_index = mw.text.trim(rest_index)
			mw.log('  -- Удаление "мс" из индекса')
			_.log_value(data.rest_index, 'data.rest_index')
			return
		end
	end
end


local function init(data)
	_.log_func('parse_args', 'init')

	local several_vovwels, has_stress

--	INFO: Исходное слово без ударения:
	data.word = _.replaced(data.word_stressed, '́ ', '')

--	INFO: Исходное слово вообще без ударений (в т.ч. без грависа):
	data.word_cleared = _.replaced(_.replaced(_.replaced(data.word, '̀', ''), 'ѐ', 'е'), 'ѝ', 'и')

	if data.adj then
		if _.endswith(data.word_stressed, 'ся') then
			data.postfix = true
			data.stem = _.replaced(data.word, '{vowel}[йяе]ся$', '')
			data.stem_stressed = _.replaced(data.word_stressed, '{vowel}́ ?[йяе]ся$', '')
		else
			data.stem = _.replaced(data.word, '{vowel}[йяе]$', '')
			data.stem_stressed = _.replaced(data.word_stressed, '{vowel}́ ?[йяе]$', '')
		end
	else
--		INFO: Удаляем окончания (-а, -е, -ё, -о, -я, -й, -ь), чтобы получить основу:
		data.stem = _.replaced(data.word, '[аеёийоьыя]$', '')
		data.stem_stressed = _.replaced(data.word_stressed, '[аеёийоьыя]́ ?$', '')
	end

	_.log_value(data.word, 'data.word')
	_.log_value(data.stem, 'data.stem')
	_.log_value(data.stem_stressed, 'data.stem_stressed')

--  INFO: Случай, когда не указано ударение у слова:
	several_vovwels = _.contains_several(data.word_stressed, '{vowel+ё}')
	has_stress = _.contains(data.word_stressed, '[́ ё]')
	if several_vovwels and not has_stress then
		return {
			error='Ошибка: Не указано ударение в слове',
			error_category='Ошибка в шаблоне "сущ-ru": не указано ударение в слове',
		}  -- dict
	end
end


local function angle_brackets(data)
	_.log_func('parse_args', 'angle_brackets')

	local another_index, pt, error

	another_index = _.extract(data.rest_index, '%<([^>]+)%>')
	if another_index then
		pt = data.pt
		if not pt then
			data.output_gender = data.gender
			data.output_animacy = data.animacy
		end
		data.orig_index = data.index
		data.index = another_index
		error = extract_gender_animacy(data)
		data.pt = pt
		if error then return error end

		_.log_value(data.adj, 'data.adj')
		if data.adj then  -- Для прилагательных надо по-особенному
			error = init(data)
			if error then return data, error end
		end
	end
end


function export.parse(base, args)
	_.log_func('parse_args', 'parse')

	local data, error, parts, n_parts, data1, data2
	local index_parts, words_parts, n_sub_parts, data_copy

--	INFO: Достаём значения из параметров:
	data = {}  -- AttrDict
	data.base = base
	data.args = args
	data.index = mw.text.trim(args['индекс'])
	data.word_stressed = mw.text.trim(args['слово'])

	_.log_value(data.index, 'data.index')
	_.log_value(data.word_stressed, 'data.word_stressed')

	-- mw.log('')
	-- mw.log('==================================================')
	-- mw.log('args: ' .. tostring(data.index) .. ' | ' .. tostring(data.word_stressed))
	-- mw.log('--------------------------------------------------')

	-- -------------------------------------------------------------------------

	_.log_info('Получение информации о роде и одушевлённости')

	error = extract_gender_animacy(data)

	if error then return data, error end

	_.log_value(data.gender, 'data.gender')
	_.log_value(data.animacy, 'data.animacy')
	_.log_value(data.common_gender, 'data.common_gender')
	_.log_value(data.adj, 'data.adj')
	_.log_value(data.pronoun, 'data.pronoun')
	_.log_value(data.pt, 'data.pt')
	_.log_value(data.rest_index, 'data.rest_index')

--	INFO: stem, stem_stressed, etc.
	error = init(data)
	if error then return data, error end

--	INFO: Случай, если род или одушевлённость не указаны:
	if (not data.gender or not data.animacy) and not data.pt then
		return data, {}  -- dict -- INFO: Не показываем ошибку, просто считаем, что род или одушевлённость *ещё* не указаны
	end

--	INFO: Проверяем случай с вариациями:
	parts = mw.text.split(data.rest_index, '//')
	n_parts = table.getn(parts)

	if n_parts == 1 then  -- INFO: Дополнительных вариаций нет
		if _.contains(data.animacy, '//') then  -- INFO: Случаи 'in//an' и 'an//in'
--			INFO: Клонируем две вариации на основании текущих данных
			data1 = mw.clone(data)
			data2 = mw.clone(data)

--			INFO: Устанавливаем для них соответствующую вариацию одушевлённости
			data1.animacy = mw.ustring.sub(data.animacy, 1, 2)
			data2.animacy = mw.ustring.sub(data.animacy, 5, 6)

--			INFO: Заполняем атрибут с вариациями
			data.sub_cases = {data1, data2}  -- list

			return data, nil
			-- TODO: А что если in//an одновременно со следующими случаями "[]" или "+"
		end

		-- _.log_info('Случай с "+" (несколько составных частей слова через дефис)')

		index_parts = mw.text.split(data.rest_index, '%+')
		words_parts = mw.text.split(data.word_stressed, '-')
		n_sub_parts = table.getn(index_parts)
		if n_sub_parts > 1 then
			data.sub_parts = {}  -- list
			for i = 1, n_sub_parts do
				data_copy = mw.clone(data)
				data_copy.word_stressed = words_parts[i]

				error = init(data_copy)
				if error then return data, error end

				data_copy.rest_index = index_parts[i]

				error = angle_brackets(data_copy)
				if error then return data, error end

				table.insert(data.sub_parts, data_copy)
			end
			return data, nil
		end

		error = angle_brackets(data)
		if error then return data, error end

		if _.contains(data.rest_index, '%[%([12]%)%]') or _.contains(data.rest_index, '%[[①②]%]') then
--			INFO: Клонируем две вариации на основании текущих данных
			data1 = mw.clone(data)
			data2 = mw.clone(data)

--			INFO: Устанавливаем факультативность (первый случай):
			data1.rest_index = _.replaced(data1.rest_index, '%[(%([12]%))%]', '')
			data1.rest_index = _.replaced(data1.rest_index, '%[([①②])%]', '')

--			INFO: Устанавливаем факультативность (второй случай):
			data2.rest_index = _.replaced(data2.rest_index, '%[(%([12]%))%]', '%1')
			data2.rest_index = _.replaced(data2.rest_index, '%[([①②])%]', '%1')
			data2.rest_index = _.replaced(data2.rest_index, '%*', '')

--			INFO: Заполняем атрибут с вариациями
			data.sub_cases = {data1, data2}  -- list

			return data, nil
		end

	elseif n_parts == 2 then  -- INFO: Вариации "//" для ударения (и прочего индекса)
		_.log_info('> Случай с вариациями //')

		if _.contains(data.animacy, '//') then
--			INFO: Если используются вариации одновременно и отдельно для одушевлённости и ударения
			return data, {error='Ошибка: Случай с несколькими "//" пока не реализован. Нужно реализовать?'}  -- dict
		end

--		INFO: Клонируем две вариации на основании текущих данных
		data1 = mw.clone(data)
		data2 = mw.clone(data)

--		INFO: Предпогалаем, что у нас пока не "полная" вариация (не затрагивающая род)
		data1.rest_index = parts[1]
		data2.rest_index = parts[2]

--		INFO: Проверяем, не находится ли род+одушевлённость во второй вариации
		data2.index = parts[2]  -- INFO: Для этого инициируем `.index`, чтобы его обработала функция `extract_gender_animacy`
		extract_gender_animacy(data2)

--		INFO: Если рода и одушевлённости во второй вариации нет (простой случай):
		if not data2.gender and not data2.animacy then
--			INFO: Восстанавливаем прежние общие значения:
			data2.gender = data.gender
			data2.animacy = data.animacy
			data2.common_gender = data.common_gender

--		INFO: Проверка на гипотетическую ошибку в алгоритме:
		elseif not data2.gender and data2.animacy or data2.gender and not data2.animacy then
			return data, {error='Странная ошибка: После `extract_gender_animacy` не может быть частичной заполненности полей' }  -- dict

--		INFO: Если что-то изменилось, значит, прошёл один из случаев, и значит у нас "полная" вариация (затрагивающая род)
		elseif data.gender ~= data2.gender or data.animacy ~= data2.animacy or data.common_gender ~= data2.common_gender then
			data.rest_index = nil  -- INFO: Для случая "полной" вариации понятие `rest_index`, наверное, не определено
		end
		data2.index = data.index  -- INFO: Возвращаем исходное значение `index`; инвариант: оно всегда будет равно исходному индексу

--		INFO: Заполняем атрибут с вариациями
		data.sub_cases = {data1, data2}  -- list

	else  -- INFO: Какая-то ошибка, слишком много "//" в индексе
		return data, {error='Ошибка: Слишком много частей для "//"'}  -- dict
	end

	return data, nil  -- INFO: `nil` здесь -- признак, что нет ошибок
end


return export