Module:sa-utilities

local export = {}

-- Common regex patterns: export.consonant_list = "kKgGNcCjJYwWqQRtTdDnpPbBmyrlLvSzsh" export.consonant = "[" .. export.consonant_list .. "]" export.accent = "[/\\]" export.vowel_list = "aAiIuUfFxXeEoO" export.vowel = "[" .. export.vowel_list .. "]" export.vowel_with_accent = export.vowel .. export.accent .. "?"

-- Abbreviated helper functions: local match = mw.ustring.match local gsub = mw.ustring.gsub local gasub = string.gsub -- All we need for SLP1! Much faster than mw.ustring.gsub local sub = mw.ustring.sub local lower = mw.ustring.lower local upper = mw.ustring.upper

--[=[Detects whether a specified text ends in a given pattern. Parameters: text (String): the text to be tested pattern (String): the query pattern to be tested on the end of the text. Return: Boolean ]=] local function ends_with(text, pattern) return match(text, pattern .. "$") end

--[=[Detects whether a specified text begins in a given pattern. Parameters: text (String): the text to be tested pattern (String): the query pattern to be tested on the beginning of the text. Return: Boolean ]=] local function starts_with(text, pattern) return match(text, "^" .. pattern) end

-- Common transformation types: --[=[ Increase a vowel one grade (guṇation). It is possible that this should include provisions for guṇation of sonorants into CV configurations (e.g. i/ī -> ya -> yā, etc.). Perhaps will need to be updated. ]=] export.up_one_grade = { ['a'] = 'A', ['A'] = 'A', ['a/'] = 'A/', ['A/'] = 'A/', ['a\\'] = 'A\\', ['A\\'] = 'A\\', ['i'] = 'e', ['I'] = 'e', ['i/'] = 'e/', ['I/'] = 'e/', ['i\\'] = 'e\\', ['I\\'] = 'e\\', ['u'] = 'o', ['U'] = 'o', ['u/'] = 'o/', ['U/'] = 'o/', ['u\\'] = 'o\\', ['U\\'] = 'o\\', ['e'] = 'E', ['E'] = 'E', ['e/'] = 'E/', ['E/'] = 'E/', ['e\\'] = 'E\\', ['E\\'] = 'E\\', ['o'] = 'O', ['O'] = 'O', ['o/'] = 'O/', ['O/'] = 'O/', ['o\\'] = 'O\\', ['O\\'] = 'O\\', ['f'] = 'ar', ['F'] = 'ar', ['f/'] = 'a/r', ['F/'] = 'a/r', ['f\\'] = 'a\\r', ['F\\'] = 'a\\r', ['x'] = 'al', ['x/'] = 'a/l', }

-- Lengthen a vowel export.lengthen = { ['a'] = 'A', ['A'] = 'A', ['a/'] = 'A/', ['A/'] = 'A/', ['a\\'] = 'A\\', ['A\\'] = 'A\\', ['i'] = 'I', ['I'] = 'I', ['i/'] = 'I/', ['I/'] = 'I/', ['i\\'] = 'I\\', ['I\\'] = 'I\\', ['u'] = 'U', ['U'] = 'U', ['u/'] = 'U/', ['U/'] = 'U/', ['u\\'] = 'U\\', ['U\\'] = 'U\\', ['f'] = 'F', ['F'] = 'F', ['f/'] = 'F/', ['F/'] = 'F/', ['f\\'] = 'F\\', ['F\\'] = 'F\\', }

-- Decrease vowel one grade (reverse of above) export.shorten = { ['a'] = 'a', ['A'] = 'a', ['a/'] = 'a/', ['A/'] = 'a/', ['a\\'] = 'a\\', ['A\\'] = 'a\\', ['i'] = 'i', ['I'] = 'i', ['i/'] = 'i/', ['I/'] = 'i/', ['i\\'] = 'i\\', ['I\\'] = 'i\\', ['u'] = 'u', ['U'] = 'u', ['u/'] = 'u/', ['U/'] = 'u/', ['u\\'] = 'u\\', ['U\\'] = 'u\\', ['f'] = 'f', ['F'] = 'f', ['f/'] = 'f/', ['F/'] = 'f/', ['f\\'] = 'f\\', ['F\\'] = 'f\\', }

-- Convert a monosegmental (or at least monoliteral) diphthong into a/ā + glide. export.split_diphthong = { ['e'] = 'ay', ['e/'] = 'a/y', ['e\\'] = 'a\\y', ['E'] = 'Ay', ['E/'] = 'A/y', ['E\\'] = 'A\\y', ['o'] = 'av', ['o/'] = 'a/v', ['o\\'] = 'a\\v', ['O'] = 'Av', ['O/'] = 'A/v', ['O\\'] = 'A\\v', }

-- reverse of above local join_diphthong = { ['ay'] = 'e', ['a/y'] = 'e/', ['a\\y'] = 'e\\', ['Ay'] = 'E', ['A/y'] = 'E/', ['A\\y'] = 'E\\', ['av'] = 'o', ['a/v'] = 'o/', ['a\\v'] = 'o\\', ['Av'] = 'O', ['A/v'] = 'O/', ['A\\v'] = 'O\\', }

-- Convert a syllabic sonorant to its associated consonantal form. export.vowel_to_cons = { ['i'] = 'y', ['I'] = 'y', ['u'] = 'v', ['U'] = 'v', ['f'] = 'r', ['F'] = 'r', ['x'] = 'l', ['X'] = 'l', }

local cons_to_vowel = { ['y'] = 'i', ['r'] = 'f', ['v'] = 'u', ['l'] = 'x' }

-- Add a homorganic glide to a vowel local insert_glide = { ['i'] = 'iy', ['I'] = 'iy', ['i/'] = 'i/y', ['I/'] = 'i/y', ['i\\'] = 'i\\y', ['I\\'] = 'i\\y', ['u'] = 'uv', ['U'] = 'uv', ['u/'] = 'u/v', ['U/'] = 'u/v', ['u\\'] = 'u\\v', ['U\\'] = 'u\\v', }

--[=[Convert all unambiguous stops to their absolute final value. The equivalent values (e.g. k = k) may be redundant given the implementation of absolute_final and internal_sandhi below. ]=] export.to_final = { ['k'] = 'k', ['K'] = 'k', ['g'] = 'k', ['G'] = 'k', ['c'] = 'k', ['C'] = 'w',			  ['J'] = 'w', -- value for 'J' is theoretical ['w'] = 'w', ['W'] = 'w', ['q'] = 'w', ['Q'] = 'w', ['L'] = 'w', ['t'] = 't', ['T'] = 't', ['d'] = 't', ['D'] = 't', ['p'] = 'p', ['P'] = 'p', ['b'] = 'p', ['B'] = 'p', ['Y'] = 'N', ['z'] = 'w', }

-- Convert dental to retroflex local function dental_to_retroflex(str) local changes = { ['t'] = 'w', ['T'] = 'W', ['d'] = 'q', ['D'] = 'Q', ['n'] = 'R', }	return gasub(str, '.', changes) end

-- Remove aspiration export.deaspirate = { ['K'] = 'k', ['G'] = 'g', ['C'] = 'c', ['J'] = 'j', ['W'] = 'w', ['Q'] = 'q', ['T'] = 't', ['D'] = 'd', ['P'] = 'p', ['B'] = 'b', }

export.aspirate = { ['g'] = 'G', ['j'] = 'J', ['q'] = 'Q', ['d'] = 'D', ['b'] = 'B' }

export.to_voiced = { ['k'] = 'g', ['c'] = 'j', ['w'] = 'q', ['t'] = 'd', ['p'] = 'b' }

export.homorganic_nasal = { ['k'] = 'N', ['K'] = 'N', ['g'] = 'N', ['G'] = 'N', ['c'] = 'Y', ['C'] = 'Y', ['j'] = 'Y', ['J'] = 'Y', ['w'] = 'R', ['W'] = 'R', ['q'] = 'R', ['Q'] = 'R', ['t'] = 'n', ['T'] = 'n', ['d'] = 'n', ['D'] = 'n', ['p'] = 'm', ['P'] = 'm', ['b'] = 'm', ['B'] = 'm', ['r'] = 'M', ['l'] = 'M', -- or 'l~' ['S'] = 'M', ['z'] = 'M', ['s'] = 'M', ['h'] = 'M' }

--[=[Detects whether a word is monosyllabic. This function does not apply sandhi to determing whether a potential phonemic form like /dā́rv/ would be syllabified to [dā́ru]. This might need to be changed. Parameters: text (String): the text to be checked for monosyllabicity Return: Boolean ]=] function export.is_monosyllabic(text) return match(text, "^" .. export.consonant .. "*" .. export.vowel_with_accent .. "[HM~]?" .. export.consonant .. "*$") end

--[=[Transforms a word to its absolute final sandhi form. Parameters: text (String): the text to be converted to final sandhi position ambig_final (String): an indication of what outcome j/ś/h should have as			these will unpredictably produce either a retroflex or a velar stop in final position (e.g. spáś- > spáṭ 'spy' vs. náś- > nák 'night'). Required for j/ś/h-final strings. Return: String ]=] local function absolute_final(text, ambig_final, j_to_z, h_to_g) if ends_with(text, "kz") then -- final -kṣ > ṭ text = gasub(text, "..$", "w") -- final r + stop is allowed in stem, see Whitney §150b (endings -s/-t after consonant have been eliminated previously) elseif ends_with(text, export.vowel_with_accent .. "r[kKgGcCjJwWqQtTdDpPbB]") then -- do nothing elseif ends_with(text, export.consonant .. export.consonant) then -- at least 2 consonants -- Take the first of the cluster. text = gasub(text, "(" .. export.consonant .. "+)$",			function(cluster) return sub(cluster, 1, 1) end) end -- ḷh, v, and y are not handled as they should not appear finally. Perhaps wrong. if ambig_final then -- in case of -j/ś/h, but also to override the normal value in a few exceptional cases text = gasub(text, ".$", ambig_final) elseif ends_with(text, "[kwtpNRnmlaAiIuUeEoOfFxXH][/\\]?") then -- do nothing elseif ends_with(text, "M") then -- just in case, ṃ > m		text = gasub(text, ".$", "m") elseif ends_with(text, "[sr]") then -- convert to final visarga text = gasub(text, ".$", "H") elseif ends_with(text, "[KgGcCJWqQTdDPbBYzL]") then -- Handle final stops. text = gasub(text, ".$", export.to_final) elseif ends_with(text, "[jhS]") then -- see also ambig_final above if j_to_z then text = gasub(text, "j$", "w") elseif h_to_g then text = gasub(text, "h$", "k") else text = gasub(text, ".$", {['j'] = 'k', ['h'] = 'w', ['S'] = 'w'}) -- most common values end end return text end

--[=[Applying retroflexion to a stem and ending without joining them. This include RUKI, ṣ-cluster harmony, and nasal retroflexions. Parameters: stem (String): the stem to receive an ending ending (String): the ending to be affixed Return: String (stem), String (ending) ]=] function export.retroflexion(stem, ending, no_retroflex_root_s) -- Does the stem end in a RUKI environment? if ends_with(stem, "[iIeEfFxuUoOrk][/\\]?[HM]?") then ending = gasub(ending, "^s([^rfF])", "z%1")			-- Convert ending-initial s > ṣ not followed by [rṛṝ]. ending = gasub(ending, "^z[tTdDn]*", dental_to_retroflex) end -- Does the stem end in a RUKI environment followed by s and the ending not start with [rṛṝ]? if ends_with(stem, "[iIeEfFxuUoOrk][/\\]?[HM]?s") and starts_with(ending, "[^rfF]") and not no_retroflex_root_s == true then stem = gasub(stem, "s$", "z")						-- Convert stem-final s > ṣ end if ends_with(stem, "[zqwR]") then	-- Does the stem end in a retroflex? -- Convert an ending-initial dental (cluster) to retroflex ending = gasub(ending, "^[tTdDn]*", dental_to_retroflex) end -- Does the stem contain a nasal harmony trigger without intervening blockers? if ends_with(stem, "[zrfF][^cCjJYwWqQRtTdDnSsl]*") then -- Convert all retroflexable n > ṇ in the ending ending = gasub(ending, "^([^cCjJYwWqQRtTdDnSsl]*)n([aAiIeEfFxuUoOynmv])", "%1R%2") end -- Does the stem contain a nasal harmony trigger without intervening blockers and stem-final n?	if ends_with(stem, "[zrfF][^cCjJYwWqQRtTdDnSsl]*n") and starts_with(ending, "[aAiIeEfFxuUoOynmv]") then stem = gasub(stem, "n$", "R")	-- Convert stem-final n > ṇ end -- For safety, does the ending contain an unblocked nasal harmony trigger and un retroflexed n?	ending = gasub(ending,		"([zrfF][^cCjJYwWqQRtTdDnSsl]*)n([aAiIeEfFxuUoOynmv])", "%1R%2") return stem, ending end

--[=[Combine a stem and ending while modifiying the accentuation. This does not currently account for mobility of accent between the stem and ending. Parameters: stem (String): the stem to receive an ending ending (String): the ending to be affixed has_accent (Boolean): whether the word has an accent to be modified at all accent_override (Boolean): whether to strip the stem of accent mono (Boolean): whether the stem is monosyllabic AND susceptible to accentual mobility (cf. pā́dam ~ padā́ vs. gā́m ~ gávā). This should perhaps be renamed or removed as redundant to accent_override in this function. This should not be applied to *all* monosyllabic nouns! recessive (Boolean): whether the accent must be moved to the leftmost vowel (e.g. in the vocative) Return: String ]=] local function combine_accent(stem, ending, has_accent, accent_override, mono, recessive) if has_accent then if recessive then local combined = stem .. ending				 -- combine word combined = gasub(combined, export.accent, "") -- remove any accent combined = gasub(combined, "^([^" .. export.vowel_list .. "]-)(" .. export.vowel .. ")", "%1%2/") -- accent first vowel of combined form return combined elseif accent_override then stem = gasub(stem, export.accent, "")		-- remove all accents from stem elseif mono and match(ending, export.accent) then stem = gasub(stem, export.accent, "")		-- remove all accents from stem ending = gasub(ending, "\\", "/")			-- convert svarita to udatta on ending -- If both the stem and ending are accented, remove the ending accent. This may be too simple. elseif match(stem, export.accent) and match(ending, export.accent) then ending = gasub(ending, export.accent, "") end end return stem .. ending end

local function aspirate_diaspirate(stem) stem = gasub(stem, "(.*)([gdb])([^gdb]+[gdb]?)$", function(pre, cons, post) return (pre .. export.aspirate[cons]) .. post end) return stem end

--[=[Return a word-stem combined with a given ending while handling internal sandhi and accentuation. Please see the implementation for details. Parameters: input_table (Table): This table is expected to contain the items: stem (String): the stem to receive an ending ending (String): the ending to be affixed has_accent (Boolean): whether the word has an accent to be modified mono (Boolean): whether the stem is monosyllabic OR behaves like a monosyllable (e.g. root noun compounds) accent_override (Boolean): whether to strip the stem of accent since some non-monosyllabic forms (e.g. present participles and gen.pl.) may show stem-to-ending accentual mobility. recessive (Boolean): whether the accent must be moved to the leftmost vowel (e.g. in the vocative) j_to_z (Boolean): whether to convert j > ṣ before {t, th} instead of j > k			h_to_g (Boolean): whether to convert h > g before {t, th, d, dh} (duh + -tá = dugdhá) instead of h + t > ḍh (lih + -tá = līḍhá) non_final (Boolean): if true, will not apply word-final sandhi (i.e. convert s to visarga) Return: String ]=] function export.internal_sandhi(input_table) local stem, ending = input_table.stem, input_table.ending local last		-- last segment of the stem local acc		-- the accent local first		-- the first segment of the ending local combined	-- the combined form -- explicitly ignored are CV, C + semivowel, or C + nasal -- {i, u} > {ī, ū} /__[rs]C if stem is monosyllabic (e.g. gir, pur +  ā-śis) if ends_with(stem, "[iu][/\\]?[rs]") and starts_with(ending, export.consonant) and input_table.mono then stem = gasub(stem, "([iu][/\\]?)([rs])$", function(vow, cons) return export.lengthen[vow] .. cons end) end -- remove -s/-t verbal endings after consonant (Whitney §555) -- plus -s after nominal consonant stem (used to provoke a long vowel in -ir/-ur stems) if ends_with(stem, "[" .. export.consonant_list .. "M]") and match(ending, "^[st]$") then ending = gasub(ending, ".", "") end if ending == "" then if ends_with(stem, "[mM]") then  -- final 'm' of a root becomes 'n'			stem = gasub(stem, ".$", "n") elseif input_table.diaspirate then    -- budh > bhut, etc.			stem = aspirate_diaspirate(stem) end -- combine_accent for vocatives without ending combined = combine_accent(stem, ending, input_table.has_accent, input_table.accent_override, input_table.mono, input_table.recessive) return absolute_final(combined, input_table.ambig_final, input_table.j_to_z, input_table.h_to_g) -- all cases of ending starts with vowel elseif starts_with(ending, export.vowel) then if ends_with(stem, export.vowel_with_accent) then -- stem ends with vowel -- strip last vowel and accent off stem stem, last, acc = match(stem, "^(.*)(" .. export.vowel .. ")(" .. export.accent .. "?)$")			-- strip first vowel off ending first, ending = match(ending, "^(.)(.*)$") -- monosyllabic semivowel-final stems (this applies to root noun compounds as well) if match(last, '[iIuU]') and ( input_table.mono or input_table.no_syncope ) then stem = stem .. insert_glide[last .. acc] ending = first .. ending elseif export.split_diphthong[last] then			-- Can a stem-final guna and vrddhi be split into V + sonorant? stem = stem .. export.split_diphthong[last .. acc] ending = first .. ending elseif lower(last) == lower(first) then				-- homorganic vowels ending = upper(first) .. acc .. ending elseif lower(last) == "a" then						-- gunation and vrddhization after stem-final a. ending = export.up_one_grade[first .. acc] .. ending elseif export.vowel_to_cons[last] then			-- Can the stem-final vowel be made consonantal? if ends_with(stem, "Lh?") then stem = gasub(stem, "L$", "q"); stem = gasub(stem, "Lh$", "Q") -- convert ḷ(h) to ḍ(h) end stem = stem .. export.vowel_to_cons[last] ending = first .. (acc == "/" and "\\" or "") .. ending		-- Convert stem-final udatta to ending-initial svarita if match(stem, "^" .. export.consonant .. "*yv$") then	-- dyu- > divā stem = gasub(stem, "..$", "iv") end end end -- endings starting with consonants -- for middle s-aorist 2nd plural elseif ends_with(stem, export.vowel_with_accent .. 'M?') and starts_with(ending, "sD") then if ends_with(stem, "[iIeEfFxuUoO][/\\]?") then ending = gasub(ending, '^..', 'Q') else ending = gasub(ending, '^..', 'D') stem = gasub(stem, 'M$', 'n') end -- consonant + y/r/v + consonant from ending -- note that sequences like 'trv' can occur, e.g. śatrvos, gen.dual of śatru, so the case ending should be given as 'os' there, not 'vos' elseif ends_with(stem, export.consonant .. "[yrv]") then  -- e.g. śvan > śunā / cakruḥ > cakṛma stem = gasub(stem, ".$", cons_to_vowel) -- no word-initial clusters with y-, for verb 'eti, yanti' elseif stem == "y" then stem = "i" -- e.g. yuvan > yūnā / iyaja > īje (Whitney §800j) / babhūva > babhūtha (§800d) elseif (ends_with(stem, "[uU][/\\]?v") or ends_with(stem, "[iI][/\\]?y") ) and starts_with(ending, "[^yrl]") then stem = gasub(stem, "([iIuU][/\\]?)[yv]$", function(vow) return export.lengthen[vow] end) elseif ends_with(stem, "[aA][/\\]?[vy]") and starts_with(ending, "[^yrl]") then stem = gasub(stem, "([aA][/\\]?[vy])$", function(diph) return join_diphthong[diph] end) -- n > ñ /__{c, j}	elseif ends_with(stem, "[cj]") and starts_with(ending, "n") then ending = gasub(ending, "^.", "Y") -- All other C- + -C interactions elseif ends_with(stem, export.consonant) and starts_with(ending, export.consonant) then if ends_with(stem, "Lh?") then stem = gasub(stem, "L$", "q"); stem = gasub(stem, "Lh$", "Q") end if input_table.final then -- for nominal endings -bhyām, -bhis, -bhyas, -su (see Whitney §111a) if ends_with(stem, "kz") then -- final -kṣ > ṭ stem = gasub(stem, "..$", "w") -- final r + stop is allowed in stem, see Whitney §150b elseif ends_with(stem, export.vowel_with_accent .. "r[kKgGcCjJwWqQtTdDpPbB]") then -- do nothing elseif ends_with(stem, export.consonant .. export.consonant) then -- at least 2 consonants -- take the first of the cluster stem = gasub(stem, "(" .. export.consonant .. "+)$",					function(cluster) return sub(cluster, 1, 1) end) end if ends_with(stem, "[jSh]") or input_table.ambig_final then stem = gasub(stem, ".$", input_table.ambig_final) -- in case of -j/ś/h, but also to override the normal value in a few exceptional cases elseif ends_with(stem, "[KgGcCJWqQTdDPbBz]") then stem = gasub(stem, ".$", export.to_final) elseif ends_with(stem, "m") then stem = gasub(stem, ".$", "n") -- Loss of {z, ẓ} with compensatory lengthening before voiced consonant. (FIXME: but what about the médha- < *mázdha- type?) elseif ends_with(stem, "[aA][/\\]?[sHr]") and starts_with(ending, "[rgGdDbByvjJqQlLhnm]") then stem = gasub(stem, "([aA])([/\\]?)[sHr]$",					function(vow, acc) return (vow == "a" and "o" or "A") .. acc end) -- final s-allophones elseif ends_with(stem, "s") then if starts_with(ending, "[kKpPzsS]") then		-- visarga stem = gasub(stem, ".$", "H") elseif starts_with(ending, "[gGjJqQdDbByvlLhnm]") then	-- s > r 					stem = gasub(stem, ".$", "r") -- outcommented because not relevant to internal sandhi --				elseif starts_with(ending, "[cCwW]") then		-- homorganic fricative --					local homorg_s = { --							['c'] = 'S', ['C'] = 'S', --							['w'] = 'z', ['W'] = 'z', --						} --					stem = gasub(stem, ".$", homorg_s[sub(ending, 1, 1)]) --				elseif starts_with(ending, "r") then		-- Loss of z before r with compensatory lengthening before voiced consonant. --					stem = gasub(stem, "(" .. export.vowel .. "[/\\])[sHr]$", function(vow) return export.lengthen[vow] or vow end) end end else -- s-aorist (Whitney §881) if (ends_with(stem, "[^rnm]") and starts_with(ending, "s[tT]")) or starts_with(ending, "sD") then ending = gasub(ending, '^s', '') end -- diaspirate verbs with -dhve/-dhvam ending, see paper "Sanskrit as she has been misanalyzed ..." (Janda & Joseph, 2002), p.28-29 if input_table.diaspirate and starts_with(ending, "Dv") then stem = aspirate_diaspirate(stem) elseif (ends_with(stem, "jj") or ends_with(stem, "Sc")) and starts_with(ending, "[tTdDs]") then stem = gasub(stem, ".$", "") -- further treated below end if ends_with(stem, "kz") then -- roots on -kṣ if starts_with(ending, "s") then stem = gasub(stem, "..$", 'k') elseif starts_with(ending, "[tT]") then stem = gasub(stem, "Nkz$", 'Mz') stem = gasub(stem, "kz$", 'z') elseif starts_with(ending, "[dD]") then stem = gasub(stem, "Nkz$", 'Rq') stem = gasub(stem, "kz$", 'q') end elseif ends_with(stem, "[cJ]") and starts_with(ending, "[tTdDs]") then stem = gasub(stem, "Y.$", 'Nk') stem = gasub(stem, "[cJ]$", 'k') elseif ends_with(stem, "[jCSzh]") and starts_with(ending, "s") then stem = gasub(stem, "[YM].$", 'Nk') stem = gasub(stem, "[jCSzh]$", 'k') elseif ends_with(stem, "[jCSz]") and starts_with(ending, "[tTdD]") then if input_table.j_to_z or input_table.ambig_final == "w" or ends_with(stem, "[CSz]") then if starts_with(ending, "[tT]") then stem = gasub(stem, 'Y[jC]$', 'Mz') stem = gasub(stem, "[jCS]$", 'z') else stem = gasub(stem, '[YM].$', 'Rq') stem = gasub(stem, '[jCSz]$', 'q') end else stem = gasub(stem, "Yj$", 'Nk') stem = gasub(stem, "j$", 'k') end elseif ends_with(stem, "h") and starts_with(ending, "[tTdD]") then if input_table.h_to_g then stem = gasub(stem, "h$", "g") ending = gasub(ending, "^.", "D") else -- {a, i, u}h + {t, th, d, dh} > {ā, ī, ū}ḍh (Bartholomae's Law with retroflection and compensatory lengthening) stem = gasub(stem, "([aiu]?)([/\\]?)h$", function(vow, acc) return (export.lengthen[vow] or "") .. acc end) stem = gasub(stem, 'M$', 'R') ending = gasub(ending, "^.", "Q") end -- e.g. śādhi (Whitney §166) elseif ends_with(stem, "As") and match(ending, "^Di/?$") then stem = gasub(stem, "s$", "") -- following Whitney's convention at §612 of interpreting 'dhv' from 's + dhv' as 'ddhv' (see also §166 and §232) -- sometimes also in nouns + -bhis, etc. (normally handled by input_table.final above) elseif ends_with(stem, ".s") and starts_with(ending, "[DB]") then stem = gasub(stem, "Ms$", "nd") stem = gasub(stem, "s$", "d") -- for the (basically theoretical) middle voice of 'asti' elseif stem == "s" and starts_with(ending, "[sD]") then stem = "" elseif ends_with(stem, "m") then	-- if stem ends in m				-- m > ṃ before {h, ś, ṣ, s} and homorganic before stops (Whitney §212) if starts_with(ending, "[kKgGcCjJwWqQtTdDSzsh]") then stem = gasub(stem, "m$", export.homorganic_nasal[sub(ending, 1, 1)]) elseif starts_with(ending, "[mv]") then stem = gasub(stem, "m$", "n") end elseif ends_with(stem, "n") then	-- if stem ends in n				if starts_with(ending, "[Szs]") then		-- n > ṃ /__{ś, ṣ, s}					stem = gasub(stem, ".$", "M") end elseif ends_with(stem, "[KGWQTDPB]") and starts_with(ending, "[^NYRnmyrlv]") then -- Whitney §153 -- Bartholomae's Law if ends_with(stem, "[GQDB]") and starts_with(ending, "[tT]") then ending = gasub(ending, "^.", "D") end stem = gasub(stem, ".$", export.deaspirate) end -- {g, ḍ, d, b} > {k, ṭ, t, p} before following unvoiced stop/sibilant if ends_with(stem, "[gqbd]") and starts_with(ending, "[kKcCwWtTpPSzs]") then stem = gasub(stem, ".$", export.to_final) end end -- general rules for both 'final' (pāda) endings and others (outcommented lines are only relevant for external sandhi) if ends_with(stem, "[kwp]") then if starts_with(ending, "[gGjJqQdDbB]") then -- {k, ṭ, p} > {g, ḍ, b} before following voiced stop stem = gasub(stem, ".$", export.to_voiced) --			elseif starts_with(ending, "h") then		-- {k, ṭ, p}h > {ggh, ḍḍh, bbh} --				stem = gasub(stem, ".$", export.to_voiced) --				ending = gasub(ending, "^.", gasub(stem, ".$", {['k'] = 'G', ['w'] = 'Q', ['p'] = 'B'})) end elseif ends_with(stem, "t") then if starts_with(ending, "[gGdDbB]") then	-- t > d /__{g(h), d(h), b(h)} stem = gasub(stem, ".$", "d") --			elseif starts_with(ending, "h") then		-- t + h > ddh --				stem = gasub(stem, ".$", "d") --				ending = gasub(ending, "^.", "D") --			elseif starts_with(ending, "[cCjJwWqQ]") then	-- homorganic with following stop --				local t_pre_palatal_retroflex = --					{ ['c'] = 'c', ['C'] = 'c', ['j'] = 'j', ['J'] = 'j', --					['w'] = 'w', ['W'] = 'w', ['q'] = 'q', ['Q'] = 'q', } --				stem = gasub(stem, ".$", t_pre_palatal_retroflex[sub(ending, 1, 1)]) --			elseif starts_with(ending, "S") then		-- tś > cch --				stem = gasub(stem, ".$", "c") --				ending = gasub(ending, "^.", "C") --			elseif starts_with(ending, "l") then --				stem = gasub(stem, ".$", "l") end end -- note that nominal endings with -bh also trigger aspiration throwback if input_table.diaspirate and ends_with(stem, "[^GDBh]") and starts_with(ending, "[^QD]") then stem = aspirate_diaspirate(stem) end end stem, ending = export.retroflexion(stem, ending, input_table.no_retroflex_root_s) combined = combine_accent(stem, ending, input_table.has_accent, input_table.accent_override, input_table.mono, input_table.recessive) if input_table.non_final then return combined end return absolute_final(combined) end

return export