MODULO
Memtesto disponeblas sur la dokumentaĵa subpaĝo.
Ĉi tiu modulo estas multfoje bindita.
Se vi konas la eblajn sekvojn, tiam vi povas zorgeme ekredakti.
Se vi ne kuraĝas redakti tiam vi povas proponi la deziratan ŝanĝon en la diskutejo.

--[===[

MODULE "MENSB" (english substantivo)

"eo.wiktionary.org/wiki/Modulo:mensb" <!--2024-Jun-26-->
"id.wiktionary.org/wiki/Modul:mensb"

Purpose: generates a declension table for an English noun,
         correctly guessing the plural form for ca 99.9 % of words

Utilo: generas deklinacian tabelon por angla substantivo,
       divenas gxuste pluralan formon por ca 99.9 % da vortoj

Manfaat: menghasilkan tabel deklinasi untuk nomina Inggris,
         membuat bentuk jamak dengan benar untuk ca 99.9 % kata

Syfte: genererar en tabell foer ett engelskt substantiv,
       gissar raett pluralformen foer ca 99.9 % av ord

Used by templates / Uzata far sxablonoj /
Digunakan oleh templat / Anvaent av mallar:
* {{tf-en-sb}}

This module can accept parameters whether sent to itself (own frame) or
to the caller (caller's frame). If there is a parameter "caller=true"
on the own frame then that own frame is discarded in favor of the
caller's one.

Parameters: * 0 to 6 optional named parameters
              * "sa="  : alternative singular form
              * "pl="  : explicit plural form that replaces the
                         automatically generated one
              * "pa="  : alternative plural form
              * "paa=" : alternative other plural form
              * "not=" : notes
              * "pagenameoverridetestonly=" : base form of the word, replaces
                                              peeking it from "{{PAGENAME}}"

Returned: * one big string containing the complete table

Possible errors:
* anon parameter
* pagename or "sa=" is shorter than 2 or longer than 100 characters
* pagename or "sa=" contains double apos '' or rectangular brackets [[ ]]
* pagename is equal "sa="

Following rules are applied (this works only with lowercase letters):
* consonant + "y" -> drop "y" add "ies" (try -> tries)
* consonant + "o" -> add "es" (hero -> heroes)
* ends with "s" "x" "z" "ch" "sh" -> add "es"
  (virus, fix, ??, reach, clutch, bush)
* otherwise -> add "s"

DO NOT use this module for inherently uncountable
nouns like "news".

NE UZU cxi tiun modulon por kerne nenombreblaj
substantivoj kiel "news".

JANGAN menggunakan modul ini untuk nomina tidak
terhitung seperti "news".

This module is unbreakable (when called with correct module name
and function name).

Cxi tiu modulo estas nerompebla (kiam vokita kun gxustaj nomo de modulo
kaj nomo de funkcio).

Usage examples with results / Ekzemploj de uzo
kun rezultoj / Contoh penggunaan dengan hasil:

{{hr3}} <!-------------------------------->

* #T00 page "star"
* expected result: "stars"
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=star}}"
* {{nevideblafinodesekcio}}

::* #T01 page "try"
::* expected result: "tries"
::* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=try}}"
::* {{nevideblafinodesekcio}}

* #T02 page "play"
* expected result: "plays"
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=play}}"
* {{nevideblafinodesekcio}}

::* #T03 page "hero"
::* expected result: "heroes"
::* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=hero}}"
::* {{nevideblafinodesekcio}}

* #T04 page "loo"
* expected result: "loos"
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=loo}}"
* {{nevideblafinodesekcio}}

::* #T05 page "metro"
::* expected result: "metroes" (ungrammatical, use "pl" to override)
::* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=metro}}"
::* {{nevideblafinodesekcio}}

* #T06 page "zero"
* expected result: "zeroes" (uncommon form, use "pl" to override)
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=zero}}"
* {{nevideblafinodesekcio}}

::* #T07 page "zero", using "pl=zeros" override
::* expected result: "zeros"
::* actual result: "{{#invoke:mensb|ek|pl=zeros|pagenameoverridetestonly=zero}}"
::* {{nevideblafinodesekcio}}

* #T08 page "thief"
* expected result: "thiefs" (ungrammatical, use "pl" to override)
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=thief}}"
* {{nevideblafinodesekcio}}

{{hr3}} <!-------------------------------->

* #T10 page "HERO" (dubious)
* expected result: "HEROs" (dubious)
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=HERO}}"
* {{nevideblafinodesekcio}}

::* #T11 page "virus"
::* expected result: "viruses" (YES this is the most correct form)
::* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=virus}}"
::* {{nevideblafinodesekcio}}

* #T12 page "fix"
* expected result: "fixes"
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=fix}}"
* {{nevideblafinodesekcio}}

::* #T13
::* expected result: "reaches"
::* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=reach}}"
::* {{nevideblafinodesekcio}}

* #T14
* expected result: "bushes"
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=bush}}"
* {{nevideblafinodesekcio}}

::* #T15
::* expected result: "paths" (good although difficult to pronounce, NOT "pathes")
::* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=path}}"
::* {{nevideblafinodesekcio}}

{{hr3}} <!-------------------------------->

* #T20 page "news"
* expected result: "newses" (ungrammatical, don't apply this module to uncountable nouns)
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=news}}"
* {{nevideblafinodesekcio}}

* #T21 page "short story"
* expected result: "short stories" (multiword lemma)
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=short story}}"
* {{nevideblafinodesekcio}}

* #T22 page "ii"
* expected result: "iis" (nonsense)
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=ii}}"
* {{nevideblafinodesekcio}}

<pre>
* #T23 page "i"
* expected result: "bad usage"
* actual result: "{{#invoke:mensb|ek|pagenameoverridetestonly=i}}"
* {{nevideblafinodesekcio}}

* #T24 undesirable anonymous parameter
* expected result: "bad usage"
* actual result: "{{#invoke:mensb|ek|test|pagenameoverridetestonly=test}}"
* {{nevideblafinodesekcio}}
</pre>

{{hr3}} <!-------------------------------->

* note that all tests depend on "nevideblafinodesekcio"
* note that tests #T23 #T24 cannot be reasonably executed on the docs subpage without help of "pate" or "debu"

{{hr3}} <!-------------------------------->

]===]

local exporttable = {}

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

---- CONSTANTS [O] ----

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

  -- uncommentable EO vs ID constant strings (core site-related features)

  local constrpriv = "eo"                 -- EO (privileged site language)
  -- local constrpriv = "id"                 -- ID (privileged site language)

    -- surrogate transcoding table (only needed for EO)

  local contabtransluteo = {}
  contabtransluteo[ 67] = 0xC488 -- CX
  contabtransluteo[ 99] = 0xC489 -- cx
  contabtransluteo[ 71] = 0xC49C -- GX
  contabtransluteo[103] = 0xC49D -- gx
  contabtransluteo[ 74] = 0xC4B4 -- JX
  contabtransluteo[106] = 0xC4B5 -- jx
  contabtransluteo[ 83] = 0xC59C -- SX
  contabtransluteo[115] = 0xC59D -- sx
  contabtransluteo[ 85] = 0xC5AC -- UX breve
  contabtransluteo[117] = 0xC5AD -- ux breve

  -- constant strings (generic & misc)

  local constrfrco  = '707070'  -- table frame color (grey)
  local constrbkti  = 'B0D0F0'  -- title cell background color (light grey/blue)
  local constrtdsty = '<td style="border:0.25em solid #' .. constrfrco .. ';padding:0.4em;'
  local constrtdbg2 = constrtdsty .. '">'                                                                -- NOT centered
  local constrtdbg4 = constrtdsty .. 'text-align:center;" colspan="2">'                                  -- centered & cs 2
  local constrtdbg6 = constrtdsty .. 'background:#' .. constrbkti .. ';text-align:center;" colspan="2">' -- centered & bk & cs 2

  -- constant strings (error circumfixes)

  local constrlaxhu = '&nbsp;&#42;&#42;&nbsp;'    -- lagom -> huge circumfix " ** "
  local constrelabg = '<span class="error"><b>'   -- lagom whining begin
  local constrelaen = '</b></span>'               -- lagom whining end
  local constrehubg = constrlaxhu .. constrelabg  -- huge whining begin
  local constrehuen = constrelaen .. constrlaxhu  -- huge whining end

  -- uncommentable EO vs ID constant strings (error messages, depends from above "error circumfixes" section)

  local constrbadu = constrehubg .. 'Erara uzo de sxablono "tf-en-sb", legu gxian dokumentajxon' .. constrehuen   -- EO
  -- local constrbadu = constrehubg .. 'Penggunaan salah templat "tf-en-sb", bacalah dokumentasinya' .. constrehuen  -- ID

  local constrkates = '[[Kategorio:Erara uzo de sxablono]]'               -- EO
  local constrkatel = '[[Kategorio:Erara uzo de sxablono (tf-en-sb)]]'    -- EO
  -- local constrkates = '[[Kategori:Penggunaan salah templat]]'             -- ID
  -- local constrkatel = '[[Kategori:Penggunaan salah templat (tf-en-sb)]]'  -- ID

  -- uncommentable EO vs ID constant strings (misc)

  local constrtit = 'Angla substantivo<br><small>English noun</small>'   -- EO
  -- local constrtit = 'Nomina Inggris<br><small>English noun</small>'      -- ID

  local constrsng = '[[singularo|Singularo]]'                -- EO
  -- local constrsng = '[[bentuk tunggal|Tunggal (singular)]]'  -- ID
  local constrplu = '[[pluralo|Pluralo]]'                    -- EO
  -- local constrplu = '[[bentuk jamak|Jamak (plural)]]'        -- ID

  local constralt = 'alternative'  -- EO
  -- local constralt = 'alternatif'   -- ID

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

---- MATH FUNCTIONS [E] ----

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

local function mathdiv (xdividend, xdivisor)
  local resultdiv = 0 -- DIV operator lacks in LUA :-(
  resultdiv = math.floor (xdividend / xdivisor)
  return resultdiv
end--function mathdiv

local function mathmod (xdividendo, xdivisoro)
  local resultmod = 0 -- MOD operator is "%" and bitwise AND operator lack too
  resultmod = xdividendo % xdivisoro
  return resultmod
end--function mathmod

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

---- HIGH LEVEL STRING FUNCTIONS [I] ----

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

local function lfinememlig (strmem,strtuff) -- prevents self-linking
  if (strmem~=strtuff) then
    strtuff = '[[' .. strtuff .. ']]'
  end--if
  return strtuff
end--function lfinememlig

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

-- Local function LFICHECKFORBAD

-- Output : * booisbaad -- true if string is bad

local function lficheckforbad (streniro)
  local booisbaad = false
  booisbaad =              (string.find(streniro,"''",1,true)~=nil) -- dbl apo
  booisbaad = booisbaad or (string.find(streniro,"[[",1,true)~=nil)
  booisbaad = booisbaad or (string.find(streniro,"]]",1,true)~=nil)
  return booisbaad
end--function lficheckforbad

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

-- Local function LFIKODEOSG

-- Transcode eo X-surrogates to cxapeloj in a single string (eo only).

-- Input  : * streosurr -- ANSI string (empty is useless but cannot
--                                      cause major harm)

-- Output : * strutf8eo -- UTF8 string

-- Depends on functions :
-- [E] mathdiv mathmod

-- Depends on constants :
-- * table "contabtransluteo" inherently holy

-- * the "x" in a surr pair is case insensitive,
--   for example both "kacxo" and "kacXo" give same result
-- * avoid "\", thus for example "ka\cxo" would get converted but the "\" kept
-- * double "x" (both case insensitive) prevents conversion and becomes
--   reduced to single "x", for example "kacxxo" becomes "kacxo"

local function lfikodeosg (streosurr)

  local vareopeek = 0
  local strutf8eo = ''
  local numeoinplen = 0
  local numinpinx = 0 -- ZERO-based source index
  local numknar0k = 0 -- current char
  local numknaf1x = 0 -- next char (ZERO is NOT valid)
  local numknaf2x = 0 -- post next char (ZERO is NOT valid)
  local boonext1x = false
  local boonext2x = false
  local boosudahdone = false

  numeoinplen = string.len(streosurr)

  while true do

    if (numinpinx>=numeoinplen) then
      break
    end--if

    numknar0k = string.byte(streosurr,(numinpinx+1),(numinpinx+1))
    numknaf1x = 0 -- preASSume no char
    numknaf2x = 0 -- preASSume no char
    if ((numinpinx+1)<numeoinplen) then
      numknaf1x = string.byte(streosurr,(numinpinx+2),(numinpinx+2))
    end--if
    if ((numinpinx+2)<numeoinplen) then
      numknaf2x = string.byte(streosurr,(numinpinx+3),(numinpinx+3))
    end--if

    boonext1x = ((numknaf1x==88) or (numknaf1x==120)) -- case insensitive
    boonext2x = ((numknaf2x==88) or (numknaf2x==120)) -- case insensitive
    boosudahdone = false
    if (boonext1x and boonext2x) then -- got "xx"
      strutf8eo = strutf8eo .. string.char(numknar0k,numknaf1x) -- keep one "x" only
      numinpinx = numinpinx + 3 -- eaten 3 written 2
      boosudahdone = true
    end--if
    if (boonext1x and (not boonext2x)) then -- got yes-"x" and no-"x"
      vareopeek = contabtransluteo[numknar0k] -- UINT16 or type "nil"
      if (type(vareopeek)=='number') then
        strutf8eo = strutf8eo .. string.char(mathdiv(vareopeek,256),mathmod(vareopeek,256)) -- add UTF8 char
        numinpinx = numinpinx + 2 -- eaten 2 written 2
        boosudahdone = true
      end--if
    end--if
    if (not boosudahdone) then
      strutf8eo = strutf8eo .. string.char(numknar0k) -- copy char
      numinpinx = numinpinx + 1 -- eaten 1 written 1
    end--if

  end--while

  return strutf8eo

end--function lfikodeosg

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

---- VARIABLES [R] ----

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

function exporttable.ek (arxframent)

  -- general unknown type

  local vartmp = 0    -- variable without type multipurpose

  -- special type "args" AKA "arx"

  local arxsomons = 0 -- metaized "args" from our own or caller's "frame"

  -- param str

  local strsa   = ''  -- this MUST be DIFFERENT from "strpgnm" otherwise ERROR
  local strpl   = ''
  local strpa   = ''
  local strpaa  = ''
  local strnot  = ''

  -- general str

  local strpgnm = ''  -- base form f "{{PAGENAME}}" "pagenameoverridetestonly"
  local stralti = ''  -- "br" .. "small" .. alt .. "/small" .. "br"
  local strret  = ''  -- output string

  -- general num

  local numlength = 0 -- length of the input word
  local numlast = 0   -- last char
  local numbela = 0   -- char before the last one

  -- general boo

  local booerr = false   -- fatal error flag
  local boovowe = false  -- vowel flag for char in "numbela" (false if consonant)
  local boocut = false   -- cut off last letter flag
  local booiii = false   -- add "i" flag
  local booeee = false   -- add "e" flag

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

---- MAIN [Z] ----

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

  ---- TRANSCODE EO IF NEEDED ----

  if (constrpriv=="eo") then
    constrbadu  = lfikodeosg(constrbadu)
    constrkates = lfikodeosg(constrkates)
    constrkatel = lfikodeosg(constrkatel)
  end--if

  ---- GET THE ARX (ONE OF TWO) ----

  -- must be seized independently on "numerr" even if we already suck

  -- give a f**k in possible params other than "caller=true"

  arxsomons = arxframent.args -- "args" from our own "frame"
  if (type(arxsomons)~='table') then
    arxsomons = {} -- guard against indexing error from our own
    booerr = true
  end--if
  if (arxsomons['caller']=='true') then
    arxsomons = arxframent:getParent().args -- "args" from caller's "frame"
  end--if
  if (type(arxsomons)~='table') then
    arxsomons = {} -- guard against indexing error again
    booerr = true
  end--if

  ---- SEIZE 6 OPTIONAL NAMED PARAMS ----

  if ((arxsomons[1])~=nil) then
    booerr = true -- anonymous parameters NOT appreciated
  end--if

  if (not booerr) then
    vartmp = arxsomons['sa']
    if (type(vartmp)=='string') then
      strsa = vartmp
    end--if
    vartmp = arxsomons['pl']
    if (type(vartmp)=='string') then
      strpl = vartmp
    end--if
    vartmp = arxsomons['pa']
    if (type(vartmp)=='string') then
      strpa = vartmp
    end--if
    vartmp = arxsomons['paa']
    if (type(vartmp)=='string') then
      strpaa = vartmp
    end--if
    vartmp = arxsomons['not']
    if (type(vartmp)=='string') then
      strnot = vartmp
    end--if
    vartmp = arxsomons['pagenameoverridetestonly']
    if (type(vartmp)=='string') then
      strpgnm = vartmp
    end--if
  end--if

  ---- SEIZE THE PAGENAME IF NEEDED ----

  if ((not booerr) and (strpgnm=='')) then
    vartmp = mw.title.getCurrentTitle().text
    if (type(vartmp)=="string") then
      if (string.len(vartmp)~=0) then
        strpgnm = vartmp
      end--if
    end--if
    if (strpgnm=='') then
      booerr = true -- would result in "bad usage" but is hopefully impossible
    end--if
  end--if

  ---- CHECK WORD LENGTH (NO EN NOUN HAS ONLY 1 LETTER) AND MORE ----

  if (not booerr) then
    numlength = string.len (strpgnm) -- length of the input word
    booerr = (numlength<2) or (numlength>100) or lficheckforbad(strpgnm)
  end--if
  if ((not booerr) and (strsa~='')) then
    numlength = string.len (strsa) -- length of alt singular
    booerr = (numlength<2) or (numlength>100) or lficheckforbad(strsa)
  end--if
  if (not booerr) then
    booerr = (strpgnm==strsa) -- must NOT be same
  end--if

  ---- BREW PLURAL FORM IF NEEDED ----

  if ((not booerr) and (strpl=='')) then
      numlast = string.byte (strpgnm,-1,-1) -- last
      numbela = string.byte (strpgnm,-2,-2) -- before last
      boovowe = (numbela==97) or (numbela==101) or (numbela==105) or (numbela==111) or (numbela==117) -- damn, it's a vowel
      if (not boovowe) then
        if (numlast==121) then -- "y" -> "ies"
          boocut = true
          booiii = true -- "activity" -> "activities" but "key" -> "keys"
          booeee = true
        end--if
        if (numlast==111) then -- "o"
          booeee = true -- "hero" -> "heroes" but "kangaroo" -> "kangaroos"
        end--if
      end--if
      booeee = booeee or (numlast==115) or (numlast==120) or (numlast==122) -- "s" "x" "z"
      if (numlast==104) then
        booeee = booeee or (numbela==99) or (numbela==115) -- "ch" "sh" but not "th"
      end--if
      if (boocut) then
        strpl = string.sub (strpgnm,1,-2) -- omit last letter
      else
        strpl = strpgnm -- take it all
      end--if
      if (booiii) then
        strpl = strpl .. 'i'
      end--if
      if (booeee) then
        strpl = strpl .. 'e'
      end--if
      strpl = strpl .. 's' -- always add "s"
  end--if

  ---- BREW EITHER THE TABLE OR ERROR ----

  if (not booerr) then
    stralti = '<br><small>' .. constralt .. '</small><br>'
    strret = '<table style="float:right;clear:right;margin:0.3em 0 0.3em 0.3em;border:0.25em solid #'
    strret = strret .. constrfrco .. ';border-collapse:collapse;">'
    strret = strret .. '<tr>' .. constrtdbg6 .. constrtit .. '</td></tr>'
    strret = strret .. '<tr>' .. constrtdbg2 .. '<b>' ..constrsng .. '</b></td>'
    strret = strret .. constrtdbg2 .. '<b>' .. constrplu .. '</b></td></tr>'
    strret = strret .. '<tr>' .. constrtdbg2 .. strpgnm
    if (strsa~='') then -- lfinememlig NOT needed here
      strret = strret .. stralti .. '[[' .. strsa .. ']]'
    end--if
    strret = strret .. '</td>' .. constrtdbg2 .. lfinememlig(strpgnm,strpl)
    if (strpa~='') then
      strret = strret .. stralti .. lfinememlig(strpgnm,strpa)
    end--if
    if (strpaa~='') then
      strret = strret .. stralti .. lfinememlig(strpgnm,strpaa)
    end--if
    strret = strret .. '</td></tr>'
    if (strnot~='') then
      strret = strret .. '<tr>' .. constrtdbg4 .. '<small>' .. strnot .. '</small></td></tr>'
    end--if
    strret = strret .. '</table>'
  else
    strret = constrbadu .. constrkates .. constrkatel -- "bad usage" and diag
  end--if

  ---- RETURN THE JUNK STRING ----

  return strret

end--function

  ---- RETURN THE JUNK TABLE ----

return exporttable