Module:Wordify

Revision as of 08:01, 16 July 2021 by Zoran (talk | contribs) (Pywikibot 6.4.0)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This module provides a number-formatting function. This function can be used from #invoke or from other Lua modules.

This module is used by {{FXConvert}}

Use from other Lua modules

To use the module from normal wiki pages, no special preparation is needed. If you are using the module from another Lua module, first you need to load it, like this:

<syntaxhighlight lang="lua"> local mf = require('Module:Wordify') </syntaxhighlight>

(The mf variable stands for Module wordiFy; you can choose something more descriptive if you prefer.)

Most functions in the module have a version for Lua and a version for #invoke. It is possible to use the #invoke functions from other Lua modules, but using the Lua functions has the advantage that you do not need to access a Lua frame object. Lua functions are preceded by _, whereas #invoke functions are not.

main

{{#invoke:Wordify|main|x|prec=|lk=|numsys=|lang=|script=|state=|case=|class=|possessed=|person=|plural=|exclude=|simplify=}}

<syntaxhighlight lang="lua"> mf._wordify(x, prec, lk, numsys, lang, script, state, case, class, possessed, person, plural, exclude, simplify) </syntaxhighlight>

Simplify a number x using a word denoting an order of magnitude.

  • numsys can be short (short scale, the default), ind (Indian scale), or long (long scale)
  • prec is the digits of precision to output
  • if lk is true, the words will be linked to an explanation
  • lang allows to specify the locale for formatting and wording
  • script allows to specify a script if the language supports more than one
  • state can be indefinite, definite, or construct
  • case allows to specify a grammatical case
  • class allows to specify a noun class
  • if possessed is true, it designates that there are multiple possessed objects
  • person designates the person (1, 2, 3)
  • if plural is true, it indicates that the person is plural
  • exclude indicates the persons to exclude (2, 3)
  • if simplify is true, only the order of magnitude words will be output

Notes

  • The function is currently not applying declensions beyond the base case for some of the supported languages.
  • If a case that does not exist in a certain language is requested, the function may give an error or just return the base result.
  • The function understands case arguments that are not a word in a single language, like "akkusative".

Examples

Scales, links, scripts, capitalization

  • {{#invoke:Wordify|main|9876000}} displays 10 million.
  • {{#invoke:Wordify|main|9876000|lk=yes|prec=2}} displays 9.88 million.
  • {{#invoke:Wordify|main|9876000|numsys=ind}} displays 1 crore.
  • {{#invoke:Wordify|main|9876000|numsys=ind|lk=yes|prec=2}} displays 98.76 lakh.
  • {{#invoke:Wordify|main|1000000000000}} displays 1 trillion.
  • {{#invoke:Wordify|main|1000000000000|numsys=long}} displays 1 billion.
  • {{#invoke:Wordify|main|1000000000000|numsys=ind}} displays 1 lakh crore.
  • {{#invoke:Wordify|main|100000000000000}} displays 100 trillion.
  • {{#invoke:Wordify|main|100000000000000|numsys=ind}} displays 1 crore crore.
  • {{#invoke:Wordify|main|100000000000000|lang=de}} displays 100 Billionen.
  • {{#invoke:Wordify|main|100000000000000|lang=ko}} displays 100조.
  • {{#invoke:Wordify|main|100000000000000|lang=ko|script=hanja}} displays 100兆.

State, case

  • {{#invoke:Wordify|main|1000000000000|lang=sv}} displays 1 biljon.
  • {{#invoke:Wordify|main|1000000000000|lang=sv|state=d}} displays 1 biljonen.
  • {{#invoke:Wordify|main|1000000000000|lang=sv|case=g}} displays 1 biljons.
  • {{#invoke:Wordify|main|1000000000000|lang=sv|state=d|case=g}} displays 1 biljonens.
  • {{#invoke:Wordify|main|100000000000000|lang=sv}} displays 100 biljoner.
  • {{#invoke:Wordify|main|100000000000000|lang=sv|state=d}} displays 100 biljonerna.
  • {{#invoke:Wordify|main|100000000000000|lang=sv|case=g}} displays 100 biljoners.
  • {{#invoke:Wordify|main|100000000000000|lang=sv|state=d|case=g}} displays 100 biljonernas.

Simplification

  • {{#invoke:Wordify|main|1000000000|lang=fr|lk=yes}} displays 1 milliard.
  • {{#invoke:Wordify|main|1000000000|lang=fi}} displays tuhat miljoonaa.
  • {{#invoke:Wordify|main|1000000000|lang=fi|simplify=yes}} displays tuhat miljoonaa.
  • {{#invoke:Wordify|main|10000000000|lang=fi}} displays 10 tuhatta miljoonaa.
  • {{#invoke:Wordify|main|10000000000|lang=fi|simplify=yes}} displays tuhat miljoonaa.
  • {{#invoke:Wordify|main|1000000000000|lang=fi}} displays 1 biljoona.
  • {{#invoke:Wordify|main|1000000000000|lang=fi|simplify=yes}} displays biljoona.
  • {{#invoke:Wordify|main|10000000000000|lang=fi}} displays 10 biljoonaa.
  • {{#invoke:Wordify|main|10000000000000|lang=fi|simplify=yes}} displays biljoonat.

Noun class agreement

  • {{#invoke:Wordify|main|2000000|lang=la}} displays 2 millionia.
  • {{#invoke:Wordify|main|2000000|lang=la|class=n}} displays 2 millionia.
  • {{#invoke:Wordify|main|2000000|lang=la|class=m}} displays 2 milliones.
  • {{#invoke:Wordify|main|2000000|lang=la|class=f}} displays 2 milliones.

Slavic numerals

  • {{#invoke:Wordify|main|1000000|lang=sl}} displays 1 milijon.
  • {{#invoke:Wordify|main|2000000|lang=sl}} displays 2 milijona.
  • {{#invoke:Wordify|main|3000000|lang=sl}} displays 3 milijone.
  • {{#invoke:Wordify|main|4000000|lang=sl}} displays 4 milijone.
  • {{#invoke:Wordify|main|5000000|lang=sl}} displays 5 milijonov.
  • {{#invoke:Wordify|main|10000000|lang=sl}} displays 10 milijonov.
  • {{#invoke:Wordify|main|1000000000|lang=sl}} displays 1 milijarda.
  • {{#invoke:Wordify|main|2000000000|lang=sl}} displays 2 milijardi.
  • {{#invoke:Wordify|main|3000000000|lang=sl}} displays 3 milijarde.
  • {{#invoke:Wordify|main|4000000000|lang=sl}} displays 4 milijarde.
  • {{#invoke:Wordify|main|5000000000|lang=sl}} displays 5 milijard.
  • {{#invoke:Wordify|main|10000000000|lang=sl}} displays 10 milijard.
  • {{#invoke:Wordify|main|2500000|lang=sl|prec=1}} displays 2,5 milijona.
  • {{#invoke:Wordify|main|2500000000|lang=sl|prec=1}} displays 2,5 milijarde.
  • {{#invoke:Wordify|main|1000000|lang=pl}} displays 1 milion.
  • {{#invoke:Wordify|main|2000000|lang=pl}} displays 2 miliony.
  • {{#invoke:Wordify|main|3000000|lang=pl}} displays 3 miliony.
  • {{#invoke:Wordify|main|4000000|lang=pl}} displays 4 miliony.
  • {{#invoke:Wordify|main|5000000|lang=pl}} displays 5 milionów.
  • {{#invoke:Wordify|main|11000000|lang=pl}} displays 11 milionów.
  • {{#invoke:Wordify|main|12000000|lang=pl}} displays 12 milionów.
  • {{#invoke:Wordify|main|13000000|lang=pl}} displays 13 milionów.
  • {{#invoke:Wordify|main|14000000|lang=pl}} displays 14 milionów.
  • {{#invoke:Wordify|main|15000000|lang=pl}} displays 15 milionów.
  • {{#invoke:Wordify|main|21000000|lang=pl}} displays 21 milionów.
  • {{#invoke:Wordify|main|22000000|lang=pl}} displays 22 miliony.
  • {{#invoke:Wordify|main|23000000|lang=pl}} displays 23 miliony.
  • {{#invoke:Wordify|main|24000000|lang=pl}} displays 24 miliony.
  • {{#invoke:Wordify|main|25000000|lang=pl}} displays 25 milionów.
  • {{#invoke:Wordify|main|2500000|lang=pl|prec=1}} displays 2,5 miliona.
  • {{#invoke:Wordify|main|2500000000|lang=pl|prec=1}} displays 2,5 miliarda.

Possessive cases

  • {{#invoke:Wordify|main|1000000|case=possessive}} displays 1 million's.
  • {{#invoke:Wordify|main|10000000|case=possessive}} displays 10 million's.
  • {{#invoke:Wordify|main|1000000|case=possessive|simplify=yes}} displays million's.
  • {{#invoke:Wordify|main|10000000|case=possessive|simplify=yes}} displays millions'.
  • {{#invoke:Wordify|main|1000000|lang=hu|case=possessi}} displays 1 millióé.
  • {{#invoke:Wordify|main|2000000|lang=hu|case=possessi}} displays 2 millióké.
  • {{#invoke:Wordify|main|1000000000|lang=hu|case=possessi}} displays 1 milliárdé.
  • {{#invoke:Wordify|main|2000000000|lang=hu|case=possessi}} displays 2 milliárdoké.
  • {{#invoke:Wordify|main|1000000|lang=hu|case=possessi|possessed=true}} displays 1 millióéi.
  • {{#invoke:Wordify|main|2000000|lang=hu|case=possessi|possessed=true}} displays 2 milliókéi.
  • {{#invoke:Wordify|main|1000000000|lang=hu|case=possessi|possessed=true}} displays 1 milliárdéi.
  • {{#invoke:Wordify|main|2000000000|lang=hu|case=possessi|possessed=true}} displays 2 milliárdokéi.

Possessive forms

  • {{#invoke:Wordify|main|1000000|lang=hu|person=1}} displays 1 millióm.
  • {{#invoke:Wordify|main|2000000|lang=hu|person=1}} displays 2 millióim.
  • {{#invoke:Wordify|main|1000000000|lang=hu|person=1}} displays 1 milliárdom.
  • {{#invoke:Wordify|main|2000000000|lang=hu|person=1}} displays 2 milliárdaim.
  • {{#invoke:Wordify|main|1000000|lang=hu|person=2}} displays 1 milliód.
  • {{#invoke:Wordify|main|2000000|lang=hu|person=2}} displays 2 millióid.
  • {{#invoke:Wordify|main|1000000000|lang=hu|person=2}} displays 1 milliárdod.
  • {{#invoke:Wordify|main|2000000000|lang=hu|person=2}} displays 2 milliárdjaid.
  • {{#invoke:Wordify|main|1000000|lang=hu|person=3}} displays 1 milliója.
  • {{#invoke:Wordify|main|2000000|lang=hu|person=3}} displays 2 milliói.
  • {{#invoke:Wordify|main|1000000000|lang=hu|person=3}} displays 1 milliárdja.
  • {{#invoke:Wordify|main|2000000000|lang=hu|person=3}} displays 2 milliárdjai.
  • {{#invoke:Wordify|main|1000000|lang=hu|person=1|plural=yes}} displays 1 milliónk.
  • {{#invoke:Wordify|main|2000000|lang=hu|person=1|plural=yes}} displays 2 millióink.
  • {{#invoke:Wordify|main|1000000000|lang=hu|person=1|plural=yes}} displays 1 milliárdunk.
  • {{#invoke:Wordify|main|2000000000|lang=hu|person=1|plural=yes}} displays 2 millióink.
  • {{#invoke:Wordify|main|1000000|lang=hu|person=2|plural=yes}} displays 1 milliótok.
  • {{#invoke:Wordify|main|2000000|lang=hu|person=2|plural=yes}} displays 2 millióitok.
  • {{#invoke:Wordify|main|1000000000|lang=hu|person=2|plural=yes}} displays 1 milliárdotok.
  • {{#invoke:Wordify|main|2000000000|lang=hu|person=2|plural=yes}} displays 2 milliárdjaitok.
  • {{#invoke:Wordify|main|1000000|lang=hu|person=3|plural=yes}} displays 1 milliójuk.
  • {{#invoke:Wordify|main|2000000|lang=hu|person=3|plural=yes}} displays 2 millióik.
  • {{#invoke:Wordify|main|1000000000|lang=hu|person=3|plural=yes}} displays 1 milliárdjuk.
  • {{#invoke:Wordify|main|2000000000|lang=hu|person=3|plural=yes}} displays 2 milliárdjaik.

Support for additional languages

New languages can be added by creating a corresponding object.

The possessive field conforms to the following grammar:

<syntaxhighlight lang="bnf"> <possessive-rules> ::= "{}" | <string> | "{" <possessor-cases> "}" | <possessive-function> <possessor-cases> ::= <singular-possessors> | <singular-posessors> "," <plural-posessors> <singular-possessors> ::= "{" <singular-possessor-cases> "}" <singular-posessor-cases> := <simple-posessor-case> | <simple-possessor-case> "," <singular-possessor-cases> <simple-posessor-case> := "{ {" <singular-possessed> "," <plural-possessed> "} }" | <possessor-function> <singular-possessed> ::= <possessed> <plural-possessed> ::= <possessed> <possessed> ::= <string> | "{" <possessed-cases> "}" | <possessed-function> <plural-posessors> ::= "{" <plural-possessor-cases> "}" <plural-possessor-cases> := <plural-posessor-case> | <plural-possessor-case> "," <plural-possessor-cases> <plural-possessor-case> ::= "{" <clusivity-cases> "}" <clusivity-cases> ::= <simple-possessor-case> | <simple-possessor-case> "," <clusivity-cases> </syntaxhighlight>

The inflection field conforms to the following grammar:

<syntaxhighlight lang="bnf"> <inflection-rules> ::= "{}" | "{" <stem-statement> "}" | "{" <number-cases> "}" | "{" <stem-statement> "," <number-cases> "}" <stem-statement> ::= "stem" "=" <stem-function> <number-cases> ::= <number-case> | <number-case> "," <number-cases> <number-case> ::= <number-index> <number-rule> <number-index> ::= "" | "[" <integer> "]" "=" | | "[" "fraction" "]" "=" <number-rule> ::= <string> | <ending-cases-list> | <state-cases> | <number-function> <ending-cases-list> ::= "{" <ending-cases> "}" <ending-cases> ::= <ending-case> | <ending-case> "," <ending-cases> <ending-case> ::= <string-index> "=" <ending-rule> <ending-rule> ::= <string> | <class-cases-list> | <state-cases> | <ending-function> <class-cases-list> ::= "{" <class-cases> "}" <class-cases> ::= <class-case> | <class-case> "," <class-cases> <class-case> ::= <string-index> "=" <class-rule> <class-rule> ::= <string> | <grammatical-cases-list> | <state-cases> | <class-function> <state-cases> ::= "{" <indefinite-rule> "}" | "{" <indefinite-rule> "," <definite-rule> "}" | "{" <indefinite-rule> "," <definite-rule> "," <construct-rule> "}" <indefinite-rule> ::= <state-rule> <definite-rule> ::= <state-rule> <construct-rule> ::= <state-rule> <state-rule> ::= <string> | <grammatical-cases-list> | <state-function> <grammatical-cases-list> ::= "{" <grammatical-cases> "}" <grammatical-cases> ::= <grammatical-case> | <grammatical-case> "," <grammatical-cases> <grammatical-case> ::= <string-index> "=" <grammatical-rule> <grammatical-rule> ::= <string> | <simple-class-cases-list> | "{" <possessed-cases> "}" | <grammatical-function> <simple-class-cases-list> ::= "{" <simple-class-cases> "}" <simple-class-cases> ::= <simple-class-case> | <simple-class-case> "," <simple-class-cases> <simple-class-case> ::= <string-index> "=" <simple-class-rule> <simple-class-rule> ::= <string> | <simple-ending-cases-list> | <class-function> <simple-ending-cases-list> ::= "{" <simple-ending-cases> "}" <simple-ending-cases> ::= <simple-ending-case> | <simple-ending-case> "," <simple-ending-cases> <simple-ending-case> ::= <string-index> "=" <simple-ending-rule> <simple-ending-rule> ::= <string> | <ending-function> </syntaxhighlight>

Common elements:

<syntaxhighlight lang="bnf"> <possessed-cases> ::= <possessed-case> | <possessed-case> "," <possessed-cases> <possessed-case> ::= <string-index> "=" <possessed-rule> <string-index> ::= <identifier> | "[" <string> "]" <possessed-rule> ::= <string> | <possessed-function> </syntaxhighlight>

The elements <identifier>, <string>, and <integer> are as defined for the Lua language and those ending in -function are Lua functions with signatures that can be found in the code.

A certain language might not be supported by the current algorithm, in this case it should be extended for the new kind.

See also

Template:Language grammars Template:Math templates



local mf = require('Module:Formatnum')

local yesno = require('Module:Yesno')

local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.

--[[
Helper functions used to avoid redundant code.
]]

local function err(msg)
    -- Generates wikitext error messages.
    return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg)
end

local function getArgs(frame)
    local args = {}
    for key, value in pairs(frame:getParent().args) do
        args[key] = value
    end
    for key, value in pairs(frame.args) do
        args[key] = value
    end
    return args
end

local function getCurrentLanguage()
    local result = mw.title.getCurrentTitle().pageLanguage
    if not result then
        result = mw.language.getContentLanguage():getCode()
    end
    return result
end
               
local function _round(value, precision)
    local rescale = math.pow(10, precision or 0);
    return math.floor(value * rescale + 0.5) / rescale;
end

--[[
------------------------------------------------------------------------------------
-- tableLength
--
-- This function returns the number of keys in a table.
------------------------------------------------------------------------------------
--]]
local function tableLength(t)
  local count = 0
  for _ in pairs(t) do count = count + 1 end
  return count
end

--[[
------------------------------------------------------------------------------------
-- singleEntry
--
-- If a table contains a single entry, it returns the key and value, otherwise nil.
------------------------------------------------------------------------------------
--]]
local function singleEntry(t)
  local count = 0
  local resultk, resultv
  for k, v in pairs(t) do 
    if count > 0 then
        return nil
    else
        count = count + 1 
        resultk = k
        resultv = v
    end
  end
  
  if count == 1 then
    return resultk, resultv
  else
    return nil
  end
end

--[[
------------------------------------------------------------------------------------
-- isPositiveInteger
--
-- This function returns true if the given value is a positive integer, and false
-- if not. Although it doesn't operate on tables, it is included here as it is
-- useful for determining whether a given table key is in the array part or the
-- hash part of a table.
------------------------------------------------------------------------------------
--]]
local function isPositiveInteger(v)
    return type(v) == 'number' and v >= 1 and math.floor(v) == v and v < math.huge
end


--[[
------------------------------------------------------------------------------------
-- reverseNumKeys
--
-- This takes a table and returns an array containing the numbers of any numerical
-- keys that have non-nil values, sorted in reverse numerical order.
------------------------------------------------------------------------------------
--]]
local function reverseNumKeys(t)
    local nums = {}
    local ispi = isPositiveInteger
    for k, _ in pairs(t) do
        if ispi(k) then
            nums[#nums + 1] = k
        end
    end
    table.sort(nums, function(a, b) return a > b end)
    return nums
end

--[[
------------------------------------------------------------------------------------
-- reverseSparseIpairs
--
-- This is a reverse iterator for sparse arrays. It can be used like a reversed ipairs, but can
-- handle nil values.
------------------------------------------------------------------------------------
--]]
local function reverseSparseIpairs(t)
    local nums = reverseNumKeys(t)
    local i = 0
    local lim = #nums
    return function ()
        i = i + 1
        if i <= lim then
            local key = nums[i]
            return key, t[key]
        else
            return nil, nil
        end
    end
end

--[[
------------------------------------------------------------------------------------
-- addMultipleKeys
--
-- This takes a table and adds all the values in an array as keys to a single object
------------------------------------------------------------------------------------
--]]
local function addMultipleKeys(arr, keys, val)
    if keys then
        for _, v in ipairs(keys) do
            arr[v] = val
        end
    end
end

--[[
------------------------------------------------------------------------------------
-- deepReverseTable
--
-- This takes a table and returns a new table where the values are keys and vice versa
------------------------------------------------------------------------------------
--]]
local function deepReverseTable(t)
    local rev = {}
    for k, v in pairs(t) do
        if type(v) == 'table' then
            addMultipleKeys(rev, v, k)
        else
            rev[v] = k
        end
    end
    return rev
end

--[[
------------------------------------------------------------------------------------
-- makeLengthTable
--
-- This takes a table and returns a new table where keys are the lengths of the string in each subtable
------------------------------------------------------------------------------------
--]]
local function makeLengthTable(t)
    local result = {}
    local ulen = mw.ustring.len
    local ins = table.insert
    for _, v in pairs(t) do
        local len = ulen(v)
        local subr = result[len]
        if subr then
            ins(subr, v)
        else
            result[len] = { v }
        end
    end
    return result
end

--[[
------------------------------------------------------------------------------------
-- Object
--
-- Root object
------------------------------------------------------------------------------------
--]]
local Object = {}

function Object:new (o)
      o = o or {}   -- create object if user does not provide one
      o.super = self
      setmetatable(o, self)
      self.__index = self
      return o
end

function Object:tableInherit(name, k)
    repeat
        local field = rawget(self, name)
        local result = field and field[k]
        if result then
            return result
        else
            self = rawget(self, 'super')
        end
    until not self
    
    return nil
end

local parameters = {}

local parameter_lookup = {}

local Parameter = Object:new{ defaults = {} }

function Parameter:new (o)
    local result = self.super.new(self, o)
    result.lookup = deepReverseTable(result.values)
    local code = rawget(result, 'code')
    if code and code ~= '' then
        parameters[code] = result
    end
    parameter_lookup[code] = code
    addMultipleKeys(parameter_lookup, result.names, code)
    return result
end

function Parameter:default(lang, arg, arg2)
    if arg and arg ~= '' then
        return arg:lower()
    else
        if lang and lang ~= '' then
            local d = rawget(self, 'defaults')
            if d then
                local l = d[lang]
                if type(l) == 'table' then
                    l = l[arg2]
                end
                if l then
                    return l
                end
            end
        end
            
        local fallback = rawget(self, 'fallback')
        
        if type(fallback) == 'function' then
            return fallback()
        else
            return fallback
        end
    end
end

function Parameter:get(arg)
    local result = rawget(self, 'lookup')[arg]
    if result then
        return result
    else
        return nil
    end
end

function Parameter:getArgument(args)
    local code = self.code
    if code and code ~= '' then
        local result = args[code]
        if result then
            return result
        else
            local names = rawget(self, 'names')
            if names then
                for _, v in pairs(names) do
                    result = args[v]
                    if result then
                        return result
                    end
                end
            end
        end
    end
    return nil
end

local function setupParameters()
    for k, v in pairs(parameters) do
        parameter_lookup[k] = k
        local names = rawget(v, 'names')
        addMultipleKeys(parameter_lookup, names, k)
    end
end

local function translateParameters(args)
    local result = {}
    for k, v in pairs(args) do
        local c
        if tonumber(k) then
            c = k
        else
            c = parameter_lookup[k]
        end
        --[[
        if not c then
            return err('Illegal parameter: " .. k)
        end
        ]]
        if c then
            result[c] = v
        end
    end
    return result
end
        
--[[
wordify

Usage:
{{#invoke:Wordify | main | x | prec= | lk= | numsys= | lang= | state= | case= | class= | simplify= }}

--]]

function p.main(frame)
    local args = getArgs(frame)
    local translated = translateParameters(args)
    if type(translated) ~= 'table' then
        return err('Illegal argument: ' .. translated)
    end
    local x = args[1]
    local numsys = parameters.numsys:getArgument(translated)
    local prec =  parameters.prec:getArgument(translated)
    local lk = parameters.lk:getArgument(translated)
    local lang = parameters.lang:getArgument(translated)
    local script = parameters.script:getArgument(translated)
    local state = parameters.state:getArgument(translated)
    local case = parameters.case:getArgument(translated)
    local class = parameters.class:getArgument(translated)
    local possessed = parameters.possessed:getArgument(translated)
    local person = parameters.person:getArgument(translated)
    local plural = parameters.plural:getArgument(translated)
    local exclude = parameters.exclude:getArgument(translated)
    local simplify = parameters.simplify:getArgument(translated)
    return p._wordify(x, prec, yesno(lk), numsys, lang, script, state, case, class, yesno(possessed), tonumber(person), yesno(plural), tonumber(exclude), yesno(simplify))
end

local language = Parameter:new{ 
    code = 'lang',
    names = { 'language' },
    values = {},
    fallback = getCurrentLanguage
}

local precision = Parameter:new{ 
    code = 'prec',
    names = { 'precision' },
    values = {},
    fallback = 0
}

local link = Parameter:new{ 
    code = 'lk',
    names = { 'link' },
    values = {},
    fallback = false
}

local number_systems = Parameter:new{
    code = 'numsys',
    names = { 'scale' },
    values = {
        indian = { 'indian', 'ind' },
        myriad = { 'myriad' },
        long = { 'long', 'fra' },
        short = {'short', 'usa' }
    },
    defaults = {},
    fallback = 'long'
}

local possessed = Parameter:new{ 
    code = 'possessed',
    values = {},
    fallback = false
}

local person = Parameter:new{ 
    code = 'person',
    values = {},
    fallback = false
}

local plural = Parameter:new{ 
    code = 'plural',
    values = {},
    fallback = false
}

local exclude = Parameter:new{ 
    code = 'exclude',
    values = {},
    fallback = false
}

local simplify = Parameter:new{ 
    code = 'simplify',
    values = {},
    fallback = false
}

local scripts = Parameter:new{ 
    code = 'script',
    values = { 
        arab = { 'arabic' },
        aran = { 'nastaliq' },
        armn = { 'armenian' },
        bali = { 'balinese' },
        beng = { 'bengali' },
        bugi = { 'buginese', 'lontara' },
        cans = { 'cans' },
        cher = { 'cherokee' },
        cyrl = { 'cyrilic' },
        deva = { 'devanagari' },
        ethi = { 'ethiopic', "ge'ez" },
        geor = { 'georgian' },
        geok = { 'khutsuri' },
        grek = { 'greek' },
        gujr = { 'gujarati' },
        guru = { 'gurmunkhi' },
        hang = { 'hangul' },
        hani = { 'hanzi', 'kanji', 'hanja' },
        hans = { 'han simplified', 'simplified han', 'simplified chinese', 'chinese simplified' },
        hant = { 'han traditional', 'traditional han', 'traditional chinese', 'chinese traditional' },
        hebr = { 'hebrew' },
        java = { 'javanese' },
        khmr = { 'khmer' },
        knda = { 'kannada' },
        laoo = { 'lao'},
        latn = { 'latin' },
        mong = { 'mongolian' },
        mlym = { 'malayalam' },
        mymr = { 'myanmar', 'burmese' },
        orya = { 'oriya', 'odia' },
        rohg = { 'hanifi rohingya' },
        sinh = { 'sinhala' },
        sund = { 'sundanese' },
        syrc = { 'syriac' },
        syre = { 'ʾesṭrangēlā' },
        syrj = { 'western syriac' },
        syrn = { 'eastern syriac' },
        taml = { 'tamil'},
        tavt = { 'tai viet' },
        telu = { 'telugu' },
        tfng = { 'tifinagh' },
        tglg = { 'tagalog' },
        thaa = { 'thaana' },
        thai = { 'thai' },
        tibt = { 'tibetan' },
        yiii = { 'yi' }
    },
    defaults = {},
    fallback = 'latin'
}

local states = Parameter:new{ 
    code = 'state',
    values = { 
        c = { 'construct' },
        d = { 'definite' },
        i = { 'indefinite' }
    },
    fallback = 'indefinite'
}

local classes = Parameter:new{
    code = 'class',
    values = { 
        an = { 'animate' },
        ['cl-1'] = { 'class 1', 'class i' },
        ['cl-1a'] = { 'class 1a' },
        ['cl-2'] = { 'class 2', 'class ii' },
        ['cl-2a'] = { 'class 2a' },
        ['cl-3'] = { 'class 3', 'class iii' },
        ['cl-4'] = { 'class 4', 'class iv' },
        ['cl-5'] = { 'class 5', 'class v' },
        ['cl-6'] = { 'class 6', 'class vi' },
        ['cl-7'] = { 'class 7', 'class vii' },
        ['cl-8'] = { 'class 8', 'class viii' },
        ['cl-9'] = { 'class 9', 'class ix' },
        ['cl-10'] = { 'class 10', 'class x' },
        co = { 'common' },
        f = { 'feminine' },
        ['in'] = { 'inanimate' },
        m = { 'masculine' },
        ['m-an'] = { 'masculine animate' },
        ['m-i'] = { 'masculine inanimate' },
        n = { 'neuter' },
        ve = { 'vegetable' },
        x = { 'x' },
        y = { 'y' }
    },
    fallback = 'neuter',
    class_strings = {
        'kategória', 'κατηγορία', 
        'osztály', 'flokkur', 
        'classe', 'clâsse', 'klasse', 'luokka', 'darasa', 'razred', 'разред', 'разряд', 'ประเภท', 
        'clase', 'klase', 'klaso', 'klasa', 'clasă', 'class', 'klass', 'kelas', 'aicme', 'sinif', 'třída', 'είδος', 'класс', 'класа', 'קלאַס', 'พรรค์', 'မျိုး', 'صِنْف', 'صَنْف',
        'clas', 'klas', 'klad', 'rühm', 'τάξη', 'клас', 'しゅるい', 'ชนิด', 'ຊະນິດ', 'کلاس', 
        'դաս', 'صنف', 'رده', 
        'cl', '種類', '种类',
        '類', '类' 
    }
 }

classes.class_lengths = makeLengthTable(classes.class_strings)

function classes:get(class)
    local function matchSimilarClasses(class, classlen, pos, t, len, lr)
        local function makeNumberedClass(class, affixlen)
            local rest
            if lr then
                rest = mw.ustring.sub(class, affixlen+1)
            else
                rest = mw.ustring.sub(class, 1, -(affixlen+1))
                if  mw.ustring.sub(rest, -1, -1) == "." then
                    rest = mw.ustring.sub(rest, 1, -2) -- 1. Klasse
                end
            end
            local num = tonumber(rest)
            if num then
                return 'cl-' .. tostring(num)
            else
                return nil
            end
        end

        local len1 = len + 1
        if classlen > len1 then
            local cand
            if lr then      
                cand = mw.ustring.sub(class, 1, len)
            else
                cand = mw.ustring.sub(class, -len)
            end
            for _, v in pairs(t) do
                if cand == v then
                    return makeNumberedClass(class, len1)
                end
            end
        end
        return nil
    end

    local result = self.super.get(self, class)
    if result then
        return result
    else
        local l = mw.ustring.len(class)
        local bstart, bend = string.find(class, ' ')
        local dstart, dend = string.find(class, '-')
        if bstart or dstart then
            local pos
            if bstart and dstart then
                if bstart > dstart then
                    pos = dstart
                else 
                    pos = bstart
                end
            elseif bstart then
                pos = bstart
            else
                pos = dstart
            end
            if pos > 1 then
                local len = pos-1
                local v = self.class_lengths[len]
                if v then
                    local cls = matchSimilarClasses(class, l, pos, v, len, true) 
                    if cls then
                        return cls
                    end
                end
            end
            
            if bstart then
                bstart, bend = string.find(class, " [^ ]*$")
            end
            if dstart then
                dstart, dend = string.find(class, "-[^-]*$")
            end
            if bstart and dstart then
                if bstart > dstart then
                    pos = bstart
                else 
                    pos = dstart
                end
            elseif bstart then
                pos = bstart
            else
                pos = dstart
            end 
            if pos < l then
                local len = l-pos
                local v = self.class_lengths[len]
                if v then
                    local cls = matchSimilarClasses(class, l, pos, v, len, true) 
                    if cls then
                        return cls
                    end
                end
            end
        end
        local plain = mw.ustring.gsub(class, '-', '')
        return self.lookup[plain]
    end
end
        
local cases = Parameter:new{ 
    code = 'case',
    names = {},
    values = { 
        abe = { 'abessiv' },
        abl = { 'ablativ' },
        abs = { 'absolutiv' },
        accus = { 'accusativ', 'akkusativ', 'wenfall', 'acusativo', 'akuzativ', 'tožilnik', 'biernik', 'galininkas', 'þolfall', 'вини́тельный' },
        ['accus-n'] = { 'accusative-nominative', 'akkusativ-nominativ', 'acusativo-nominativo', 'nominatiiviakkusatiivi' }, 
        ['accus-g'] = { 'accusative-genitive', 'akkusativ-genitiv', 'acusativo-genitivo', 'genetiiviakkusatiivi' }, 
        accud = { 'accudativ', 'akkudativ' },
        adel = { 'adelativ' },
        ades = { 'adessiv', 'adesyvas' },
        adv = { 'adverbial' },
        al = { 'allativ', 'adlativ', 'direktiv', 'aliatyvas' },
        av = { 'aversiv' },
        ag = { 'agentiv' },
        an = { 'antessiv' },
        ap = { 'apudessiv' },
        b = { 'benefactiv', 'benefaktiv', 'destinativ' },
        ca = { 'causal' },
        ['ca-fi'] = { 'causal-final' },
        comi = { 'comitativ', 'komitativ', 'assoziativ' },
        comp = { 'comparativ' },
        da = { 'dativ', 'wemfall', 'dajalnik', 'celownik', 'naudininkas', 'þágufall', 'да́тельный' },
        de = { 'delativ' },
        di = { 'distributiv' },
        ['di-tem'] = { 'distributive-temporal' },
        eg = { 'egressiv' },
        el = { 'elativ' },
        eq = { 'equativ', 'äquativ' },
        er = { 'ergativ' },
        ['er-g'] = { 'ergative-genitive' },
        es = { 'essiv' },
        ['es-fo'] = { 'essive-formal' },
        ['es-mod'] = { 'essive-modal' },
        ex = { 'exessiv' },
        fo = { 'formal' },
        g = { 'genitiv', 'wesfall', 'wessenfall', 'genetiv', 'rodilnik', 'dopełniacz', 'kilmininkas', 'eignarfall', 'роди́тельный' },
        id = { 'identical' },
        il = { 'illativ', 'kryptininkas' },
        ine = { 'inessiv' },
        ini = { 'initiativ' },
        intran = { 'intransitiv' },
        intrat = { 'intrativ' },
        instruc = { 'instructiv', 'instruktiv' },
        instrum = { 'instrumental', 'instrumentál', 'orodnik', 'narzędnik', 'įnagininkas', 'твори́тельный' },
        ['instrum-comi'] = { 'instrumental-comitativ' },
        la = { 'lativ' },
        li = { 'limitativ' },
        lo = { 'locativ', 'lokativ', 'lokál', 'mestnik', 'miejscownik', 'vietininkas' }, 
        mod = { 'modal' },
        n = { 'nominativ', 'werfall', 'rektus', 'rectus', 'imenovalnik', 'mianownik', 'vardininkas', 'nefnifall', 'имени́тельный' },
        obl = { 'oblique', 'obliquus', 'oblik' },
        obj = { 'objectiv' },
        ['obj/obl'] = { 'objective/oblique' },
        ori = { 'orientativ' },
        orn = { 'ornativ' },
        pa = { 'partitiv' },
        peg = { 'pegativ' },
        perl = { 'perlativ' },
        pert = { 'pertingent' },
        possesse = { 'possessed' },
        possessi = { 'possessiv' },
        postel = { 'postelativ' },
        postes = { 'postessiv' },
        pre = { 'prepositional', 'präpositiv', 'предло́жный' },
        pri = { 'privativ' },
        pro = { 'prolativ' },
        r = { 'revertiv' },
        se = { 'semblativ' },
        so = { 'sociativ' },
        sube = { 'subessiv' },
        subl = { 'sublativ' },
        supere = { 'superessiv' },
        superl = { 'superlativ' },
        tem = { 'temporal' },
        ter = { 'terminativ' }, 
        tr = { 'translativ' },
        v = { 'vocativ', 'vokativ', 'wołacz', 'šauksmininkas' }
    },
    fallback = 'nominative'
}
                              

function cases:get(case)
    local result = self.super.get(self, case)
    if result then
        return result
    else
        local l = mw.ustring.len(case)
        local matched
        if l > 4 then
            local last = mw.ustring.sub(case, -4, -1)
            if last == 'ivus' then
                matched = true
                case = mw.ustring.sub(case, 1, -3)
                result = self.lookup[case]
                if result then
                    return result
                end
            elseif last == 'iivi' then
                matched = true
                case = mw.ustring.sub(case, 1, -4) .. 'v'
                result = self.lookup[case]
                if result then
                    return result
                end
            end
        end
        if l > 3 and not matched then
            local last = mw.ustring.sub(case, -3, -1)
            if last == 'ive' or last == 'ivo' then
                matched = true
                case = mw.ustring.sub(case, 1, -2)
                result = self.lookup[case]
                if result then
                    return result
                end
            end
        end
        if l > 2 and not matched then
            local last = mw.ustring.sub(case, -2, -1)
            if last == 'if' then
                matched = true
                case = mw.ustring.sub(case, 1, -2) .. 'v'
                result = self.lookup[case]
                if result then
                    return result
                end
            end
        end
        local plain = mw.ustring.gsub(case, '-', '')
        return self.lookup[plain]
    end
end

local function linkBegin(lang)
    if not lang or lang == '' then
        return "[["
    else
        return "[[:" .. lang .. ":"
    end
end

local function linkEnd(u)
    if not u or u == '' then
        return "]]"
    else
        return "|" .. u .. "]]"
    end
end

local function link_en(i, stem, u)
    return (i > 39 and "Names of large numbers" or ("Orders of magnitude (numbers)#10" .. i)) .. linkEnd(u)
end

local Adder = Object:new{}

function Adder:getAdditional(lang, i)
    local one, simplify
    
    local top = self:tableInherit('additional', i)
    if top then
        local entry = (top and lang and top[lang]) or top
        if entry then 
            one, simplify = entry[1], entry.simplify
        end
    end

    return one, simplify
end

--[[
Rules grammars

<possessive-rules> ::= "{}" | <string> | "{" <possessor-cases> "}" | <possessive-function>
<possessor-cases> ::= <singular-possessors> | <singular-posessors> "," <plural-posessors>
<singular-possessors> ::= "{" <singular-possessor-cases> "}"
<singular-posessor-cases> := <simple-posessor-case> | <simple-possessor-case> "," <singular-possessor-cases>
<simple-posessor-case> := "{ {" <singular-possessed> "," <plural-possessed> "} }" | <possessor-function>
<singular-possessed> ::= <possessed>
<plural-possessed> ::= <possessed>
<possessed> ::= <string> | "{" <possessed-cases> "}" | <possessed-function>
<plural-posessors> ::= "{" <plural-possessor-cases> "}"
<plural-possessor-cases> := <plural-posessor-case> | <plural-possessor-case> "," <plural-possessor-cases>
<plural-possessor-case> ::= "{" <clusivity-cases> "}"
<clusivity-cases> ::= <simple-possessor-case> | <simple-possessor-case> "," <clusivity-cases>

<inflection-rules> ::= "{}" | "{" <stem-statement> "}" | "{" <number-cases> "}" | "{" <stem-statement> "," <number-cases> "}" 
<stem-statement> ::= "stem" "=" <stem-function>
<number-cases> ::= <number-case> | <number-case> "," <number-cases>
<number-case> ::= <number-index> <number-rule>
<number-index> ::= "" | "[" <integer> "]"  "=" | | "[" "fraction" "]"  "="
<number-rule> ::= <string> | <ending-cases-list> | <state-cases> | <number-function>
<ending-cases-list> ::= "{" <ending-cases> "}" 
<ending-cases> ::= <ending-case> | <ending-case> "," <ending-cases>
<ending-case> ::= <string-index> "=" <ending-rule>
<ending-rule> ::= <string> | <class-cases-list> | <state-cases> | <ending-function>
<class-cases-list> ::= "{" <class-cases> "}" 
<class-cases> ::= <class-case> |  <class-case> "," <class-cases> 
<class-case> ::= <string-index> "=" <class-rule>
<class-rule> ::= <string> | <grammatical-cases-list> | <state-cases> | <class-function>
<state-cases> ::= "{" <indefinite-rule> "}" | "{" <indefinite-rule> "," <definite-rule> "}" | "{" <indefinite-rule> "," <definite-rule> "," <construct-rule> "}"
<indefinite-rule> ::= <state-rule>
<definite-rule> ::= <state-rule>
<construct-rule> ::= <state-rule>
<state-rule> ::= <string> | <grammatical-cases-list> | <state-function>
<grammatical-cases-list> ::= "{" <grammatical-cases> "}" 
<grammatical-cases> ::= <grammatical-case> |  <grammatical-case> "," <grammatical-cases> 
<grammatical-case> ::= <string-index> "=" <grammatical-rule>
<grammatical-rule> ::= <string> | <simple-class-cases-list> | "{" <possessed-cases> "}" | <grammatical-function>
<simple-class-cases-list> ::= "{" <simple-class-cases> "}" 
<simple-class-cases> ::= <simple-class-case> |  <simple-class-case> "," <simple-class-cases> 
<simple-class-case> ::= <string-index> "=" <simple-class-rule>
<simple-class-rule> ::= <string> | <simple-ending-cases-list> | <class-function>
<simple-ending-cases-list> ::= "{" <simple-ending-cases> "}" 
<simple-ending-cases> ::= <simple-ending-case> | <simple-ending-case> "," <simple-ending-cases>
<simple-ending-case> ::= <string-index> "=" <simple-ending-rule>
<simple-ending-rule> ::= <string> | <ending-function>

<possessed-cases> ::= <possessed-case> | <possessed-case> "," <possessed-cases>
<possessed-case> ::= <string-index> "=" <possessed-rule>
<string-index> ::= <identifier> | "[" <string> "]" 
<possessed-rule> ::= <string> | <possessed-function>
]]

local families = {
    indian = {},
    myriad = {},
    short = {},
    long = {}
}

local Family = Object:new{}

function Family:setDefaults(defscale)
    local name = self.code
    if name and name ~= '' then
        local langs = number_systems.defaults
        if not langs then
            langs = {}
            number_systems.defaults = langs
        end
        if defscale then
            langs[name] = defscale
        end
        local scale = self.scale
        if scale then
            for k, v in pairs(scale) do
                families[k][name] = self
                if not defscale and v.default then
                    langs[name] = k
                end
                local s = v.default_script
                if not s then
                    local script = v.script
                    if script then
                        s = singleEntry(script)
                    end
                end
                if s then
                    local scales = scripts.defaults
                    if not scales then
                        scales = {}
                        scripts.defaults = scales
                    end
                    local ls = scales[name]
                    if not ls then
                        ls = {}
                        scales[name] = ls
                    end
                    ls[k] = s
                end
            end
        end
    end
    return nil
end

function Family:new (o)
    local result = Object.new(self, o)
    local name = result.code
    if name and name ~= '' then
        local k = result.default_scale
        if not k then
            local scale = result.scale
            if scale then
                k = singleEntry(scale)
            end
        end
        result:setDefaults(k)
    end
    local pars = rawget(result, 'parameters')
    if pars then
        for k, v in pairs(pars) do
            local p = parameters[k]
            local names = v.names
            if names then
                for _, n in names do
                    local ns = p.names
                    if not ns then
                        ns = {}
                        p.names = ns
                    end
                    table.insert(ns, n)
                end
                addMultipleKeys(parameter_lookup, names, name)
            end
            local vales = v.values
            if values then
                for i, n in values do
                    local ns = p.lookup
                    if not ns then
                        ns = {}
                        p.lookup = ns
                    end
                    ns[n] = i
                end
            end
        end
    end
    return result
end

local Script = Adder:new{}

local hant_script = Script:new{
    code = 'hani',
    additional = {
        [4] = { '萬' },
        [8] = { '億' },
        [12] = { '兆' },
        [16] = { '京' },
        [20] = { '垓' },
        [24] = { '秭' },
        [28] = { '穣' },
        [32] = { '溝' },
        [36] = { '澗' },
        [40] = { '正' },
        [44] = { '載' },
        [48] = { '極' }
    }
}

local hanja_script = hant_script:new{}

local kanji_script = hant_script:new{
    additional = {
        [4] = { '万' }
    }
}

local hans_script = kanji_script:new{
    code = 'hans',
    additional = {
        [8] = { '亿' },
        [32] = { '沟' },
        [36] = { '涧' },
        [44] = { '载' },
        [48] = { '极' }
    }
}

local hangul_script = Script:new{
    code = 'hang',
    additional = {
        [4] = { '만' },
        [8] = { '억' },
        [12] = { '조' },
        [16] = { '경' },
        [20] = { '해' },
        [24] = { '자' },
        [28] = { '양' },
        [32] = { '구' },
        [36] = { '간' },
        [40] = { '정' },
        [44] = { '재' },
        [48] = { '극' }
    }
}

local language_ko = Family:new{
    code = 'ko',
    scale = { myriad = Adder:new{ 
                            top = 48, 
                            link = '큰_수의_이름',
                            script = { hang = hangul_script, hani = hanja_script },
                            default_script = 'hang' } }
}

local language_ja = Family:new{
    code = 'ja',
    scale = { myriad = Adder:new{ 
                            top = 48, 
                            link = '10の冪',
                            script = { hani = kanji_script } } }
}

local language_zh = Family:new{
    code = 'zh',
    scale = { myriad = Adder:new{ 
                            top = 48, 
                            link = '中文数字',
                            script = { hans = hans_script, hant = hant_script },
                            default_script = 'hans' } }
}

local latin = Family:new{
    code = 'la',
    
    space = true,
    
    root = 'lli',
        
    suffix = { 'o', 'ard' },

    prefix = { 'mi', 'bi', 'tri', 'quadri', 'quinti', 'sexti', 'septi', 'octi', 'noni', 'deci',
        [20] = 'viginti',
        [30] = 'triginti',
        [40] = 'quadraginti',
        [50] = 'quinquaginti',
        [60] = 'sexaginti',
        [70] = 'septuaginti',
        [80] = 'octoginti',
        [90] = 'nonaginti',
        [100] = 'centi'
    },
    
    unit_prefix = { 'un', 'duo', 'tre', 'quattuor', 'quin', 'sex', 'septen', 'octo', 'novem' },
    
    prefix_exception = { [16] = { la = 'sedeci' } },
    
    unit_prefix_exception = {},
    
    inflection = { { o = { { [''] = '', g = 'nis', da = 'ni', accus = { [''] = 'nem', n = ''}, abl = 'ni' } },
                     [''] = { { [''] = 'um', g = 'i', da = 'o', abl = 'o' } } },
                   { o = { { [''] = { [''] = 'nes', n = 'nia' }, g = 'nium', da = 'nibus', abl = 'nibus' } },
                     [''] = { { [''] = 'a', g = 'orum', da = 'is', abl = 'is' } } } },
    
    scale = { long = Adder:new{ top = 306, link = 'Nomina permagnorum numerorum' } }
}

local function makeArdMaker(minval, thousand)
    return function (scale, i, step, args, r, simplify)
                if i < minval then
                    return nil
                end
                local j = math.floor(i / step)
                if (j*step)+3 == i then
                    local u, simp, stem = scale:unit(i-3, args, 1000, false)
                    return thousand .. " " .. u, (r == 1), stem
                else
                    return nil
                end
           end
end

local language_ia = latin:new{
    code = 'ia',
    suffix = { 'on', 'ardo' },
    inflection = { stem = makeArdMaker(15, "mille"),
                   '',
                   function (u, args, r, simplify) 
                        if mw.ustring.sub(u, 1, 6) == "mille " then
                            return u
                        else
                            local n = mw.ustring.len(u) 
                            if mw.ustring.sub(u, n, n) == "o" then return u .. "s" else return u .. "es" end 
                        end
                   end },
    scale = { long = Adder:new{ 
                        top = 18,
                        link = function(i, stem, u)
                                    local start, finish = string.find(u, ' ')
                                    if not start then
                                        return stem .. linkEnd(u)
                                    else
                                        local first = mw.ustring.sub(u, 1, start - 1)
                                        local second = mw.ustring.sub(u, finish + 1)
                                        local secondlink =  linkBegin('ia') .. stem .. linkEnd(second)
                                        return first .. " " .. secondlink, true
                                    end
                               end } }
}

local language_is = latin:new{
    code = 'is',
    root = 'llj',
    suffix = { 'ón', 'arð' },
    prefix = { [4] = 'kvaðri', [5] = 'kvinti' },
    inflection = { { n = { { [''] = '',  g = 'ar' }, 
                           { n = 'in', accus = 'ina', da = 'inni',  g = 'arinnar' } }, 
                     [''] = { { n = 'ur', accus = '', da = 'i',  g = 's' },
                              { n = 'urinn', accus = 'inn', da = 'inum',  g = 'sins' } } },
                   { n = { { [''] = 'ir', da = 'um',  g = 'a' }, 
                           { [''] = 'irnar', da = 'unum',  g = 'anna' } }, 
                     [''] = { { n = 'ar', accus = 'a', da = 'um',  g = 'a' } , 
                              { n = 'arnir', accus = 'ana', da = 'unum',  g = 'anna' } } } },
    scale = { long = Adder:new{ top = 30, link = 'Stórar tölur' } }
}

local language_nl = latin:new{
    code = 'nl',
    root = 'lj',
    suffix = { 'oen', 'ard' },
    prefix_exception = { [16] = { nl = 'sedeci' } },
    inflection = {},
    scale = { long = Adder:new{ top = 141, link = 'Lijst van machten van tien' } }
}

local language_af = language_nl:new{
    code = 'af',
    prefix = { [4] = 'kwadri', [5] = 'kwinti', [6] = 'seksti', [8] = 'okti', [10] = 'desi' },
    scale = { long = Adder:new{ top = 63, link = 'Kort en lang skaalverdeling' } }
}

local c_family = latin:new{
    code = '',
    capitalize = true
}

local language_de = c_family:new{
    code = 'de',
    suffix = { 'on', 'arde' },
    prefix = { [8] = 'Okti', [10] = 'dezi' },
    unit_prefix = { [2] = 'Do', [6] = 'Se', [8] = 'Okto' },
    inflection = { '', { e = 'n',
                         [''] = 'en' } },
    scale = { long = Adder:new{ top = 306, link = 'Zahlennamen' } }
}

local language_lb = c_family:new{
    code = 'lb',
    suffix = { 'oun', 'ard' },
    inflection = { '', 'en' },
    scale = { long = Adder:new{ top = 27, link = 'Lëscht vun de Prefixe fir Moosseenheeten' } }
}

local li_family = latin:new{
    code = '',
    root = 'li'
}

local language_ca = li_family:new{
    code = 'ca',
    suffix = { 'ó', 'ard' },
    inflection = { '', { d = 's',
                   [''] = function (u, args, r, simplify) return mw.ustring.sub(u, 1, -2) .. "ons" end } },
    scale = { long = Adder:new{ top = 306, link = 'Escales curta i llarga' } }
}

local language_eo = li_family:new{
    code = 'eo',
    suffix = { 'ono', 'ardo' },
    prefix = { [2] = 'dui', [4] = 'kvari', [5] = 'kvini', [6] = 'sesi', [7] = 'sepi', [8] = 'oki', [9] = 'naŭi', [10] = 'deki' },
    unit_prefix = { [4] = 'kvatuor', [5] = 'kvin', [6] = 'seks', [8] = 'okto' },
    inflection = { '', 'j' },
    scale = { long = Adder:new{ top = 177, link = 'Vortoj por grandegaj nombroj' } }
}

local language_it = li_family:new{
    code = 'it',
    suffix = { 'one', 'ardo' },
    inflection = { '', function (u, args, r, simplify) return mw.ustring.sub(u, 1, -2) .. "i" end },
    scale = { long = Adder:new{ 
                        top = 63, 
                        link = function(i, stem, u) return ("Ordini di grandezza (numeri)#10" .. i) .. linkEnd(u) end,
                        exception = {
                            [36] = 'none',                              
                            [39] = 'none',
                            [42] = 'none',
                            [45] = 'none',
                            [48] = 'none',
                            [51] = 'none',
                            [54] = 'none',
                            [57] = 'none'
                        } } }
}

local language_scn = li_family:new{
    code = 'scn',
    suffix = { 'uni', 'ardu' },
    inflection = { '', function (u, args, r, simplify) 
                            local n = mw.ustring.len(u)
                            if mw.ustring.sub(u, n, n) == "i" then return u else return mw.ustring.sub(u, 1, -2) .. "i" end 
                       end},
    scale = { long = Adder:new{ 
                        top = 63,
                        exception = {
                            [30] = 'none',
                            [33] = 'none',
                            [36] = 'none',                              
                            [39] = 'none',
                            [42] = 'none',
                            [45] = 'none',
                            [48] = 'none',
                            [51] = 'none',
                            [54] = 'none',
                            [57] = 'none'
                        } } }
}

local language_pl = li_family:new{
    code = 'pl',
    suffix = { 'on', 'ard' },
    prefix = { [3] = 'try', [4] = 'kwadry', [5] = 'kwinty', [6] = 'seksty', [7] = 'septy', [8] = 'okty', [10] = 'decy' },
    inflection = { { { n = '', g = 'a', da = 'owi', accus = '', instrum = 'em', [''] = { n = 'ie', [''] = 'zie' } } },
                   { { [''] = 'y', g = 'ów', da = 'om', instrum = 'ami', lo = 'ach' } },
                   [5] =  { { [''] = function (u, args, r, simplify) 
                                        local floor = math.floor
                                        local n = floor(r/100)
                                        local m = r - n * 100
                                        if m < 2 or (m > 4 and m < 22) then
                                            return u .. 'ów'
                                        elseif m < 5 then
                                            return u .. 'y'
                                        else 
                                            local o = floor(m/10)
                                            local p = m - o * 10
                                            if p > 1  and p < 5 then
                                                return u .. 'y'
                                            else
                                                return u .. 'ów'
                                            end
                                        end
                                     end,
                              g = 'ów', da = 'om', instrum = 'ami', lo = 'ach', v = 'y' } },
                   fraction = 'a' },
    scale = { long = Adder:new{ top = 75, link = 'Liczebniki główne potęg tysiąca' } }
}

local language_pt = li_family:new{
    code = 'pt',
    suffix = { 'ão' },
    inflection = { stem = makeArdMaker(9, "mil"),
                  '',
                  function (u, args, r, simplify) 
                        if mw.ustring.sub(u, 1, 4) == "mil " then
                            return u
                        else
                            return mw.ustring.sub(u, 1, -3) .. "ões" 
                        end
                  end },
    scale = { long = Adder:new{ 
                        top = 60,
                        exception = { [6] = { 'milhão' } } } }
}

local ib_family = latin:new{
    code = '',
    root = 'll',
    suffix = { 'ón', 'ardo' }
}

local milArdMaker = makeArdMaker(9, "mil")

local language_an = ib_family:new{
    code = 'an',
    prefix = { [4] = 'quatri' },
    inflection = { stem = milArdMaker,
                   '',
                   function (u, args, r, simplify) 
                        if mw.ustring.sub(u, 1, 4) == "mil " then
                            return u
                        else
                            local n = mw.ustring.len(u) 
                            if mw.ustring.sub(u, n, n) == "o" then return u .. "s" else return mw.ustring.sub(u, 1, -3) .. "ons" end 
                        end
                   end },
    scale = { long = Adder:new{ top = 24, link = 'Potencia de diez' } }
}

local language_gl = ib_family:new{
    code = 'gl',
    inflection = { stem = milArdMaker,
                   '',
                   function (u, args, r, simplify) 
                        if mw.ustring.sub(u, 1, 4) == "mil " then
                            return u
                        else
                            return u .. "s"
                        end
                   end },
    scale = { long = Adder:new{ top = 18 } }
}

local es_family = ib_family:new{
    code = '',
    prefix = { [4] = 'cuatri' },
    plural = function (u, args, r, simplify) 
                if mw.ustring.sub(u, 1, 4) == "mil " then
                    return u
                else
                    local n = mw.ustring.len(u) 
                    if mw.ustring.sub(u, n, n) == "o" then return u .. "s" else return mw.ustring.sub(u, 1, -3) .. "ones" end 
                end
             end
}
                   
local language_ast = es_family:new{
    code = 'ast',
    inflection = { stem = milArdMaker,
                   '',
                   es_family.plural },
    scale = { long = Adder:new{ top = 30, link = 'Escales numbériques llarga y curtia' } }
}

local language_es = es_family:new{
    code = 'es',
    unit_prefix = { [4] = 'cuatro', [9] = 'noven' },
    inflection = { stem = makeArdMaker(15, "mil"),
                   '',
                   es_family.plural },
    scale = { long = Adder:new{ top = 120, link = function(i, stem, u) return ("Orden de magnitud (números)#10" .. i) .. linkEnd(u) end } }
}


local onard_family = latin:new{
    code = '',
    suffix = { 'on', 'ard' },
}

local language_en = onard_family:new{
    code = 'en',
    inflection = { { { n = '', possessi = "'s" } },
                   { { n = function (u, args, r, simplify) 
                                if simplify then
                                    return u .. 's'
                                else
                                    return u
                                end
                           end, 
                       possessi = function (u, args, r, simplify) 
                                    if simplify then
                                        return u .. "s'"
                                    else
                                        return u .. "'s"
                                    end
                                  end } } },
    default_scale = 'short',
    scale = { 
        long   = Adder:new{ top = 306, link = link_en, additional = { [100] = { 'googol' } } },
        short  = Adder:new{ top = 303, link = link_en, additional = { [100] = { 'googol' } } },
        indian = Adder:new{ 
                    top = 14, 
                    link = function(i, stem, u)
                                local start, finish = string.find(u, ' ')
                                if not start then
                                    return u .. linkEnd(u)
                                else
                                    local first = mw.ustring.sub(u, 1, start - 1)
                                    local second = mw.ustring.sub(u, finish + 1)
                                    local firstlink =  linkBegin('en') .. first .. linkEnd(first)
                                    if first == second then
                                        return firstlink .. " " .. second, true
                                    else
                                        return firstlink .. " " .. linkBegin('en') .. second .. linkEnd(second), true
                                    end
                                end
                           end,
                    additional = {
                        [5] = { 'lakh' },
                        [7] = { 'crore' },
                        [12] = { 'lakh crore' },
                        [14] = { 'crore crore' }
                    } }
    }
}

local language_fr = onard_family:new{
    code = 'fr',
    prefix = { [10] = 'déci' },
    unit_prefix = { [3] = 'tré', [9] = 'noni' },
    inflection = { '', 's' },
    scale = { long = Adder:new{ top = 57, link = function(i, stem, u) return (i > 36 and "Noms des grands nombres" or ("Ordres de grandeur de nombres#10" .. i)) .. linkEnd(u) end } }
}

local k_family = onard_family:new{
    code = '',
    prefix = { [4] = 'kvadri', [5] = 'kvinti', [8] = 'okti' }
}

local language_hu = k_family:new{
    code = 'hu',
    suffix = { 'ó', 'árd' },
    prefix = { [6] = 'szexti', [7] = 'szepti' },
    unit_prefix = { [3] = 'tri', [4] = 'kva', [5] = 'kvint', [6] = 'szex' },
    inflection = { { d = { { n = '', accus = 'ot', da = 'nak', instrum = 'dal', ['ca-fi'] = 'ért', tr = 'dá', ter = 'ig', ['es-fo'] = 'ként', ine = 'ban', supere = 'on', ades = 'nál', il = 'ba', subl = 'ra', al = 'hoz', el = 'ból', de = 'ról', abl = 'tól', possessi = { 'é', 'éi' } } }, 
                     [''] = { { n = '', accus = 't', da = 'nak', instrum = 'val', ['ca-fi'] = 'ért', tr = 'vá', ter = 'ig', ['es-fo'] = 'ként', ine = 'ban', supere = 'n', ades = 'nál', il = 'ba', subl = 'ra', al = 'hoz', el = 'ból', de = 'ról', abl = 'tól', possessi = { 'é', 'éi' } } } },
                   { d = { { n = 'ok', accus = 'okat', da = 'oknak', instrum = 'okkal', ['ca-fi'] = 'okért', tr = 'okká', ter = 'okig', ['es-fo'] = 'okként', ine = 'okban', supere = 'okon', ades = 'oknál', il = 'okba', subl = 'okra', al = 'okhoz', el = 'okból', de = 'okról', abl = 'októl', possessi = { 'oké', 'okéi' } } },
                     [''] = { { n = 'k', accus = 'kat', da = 'knak', instrum = 'kkal', ['ca-fi'] = 'kért', tr = 'kká', ter = 'kig', ['es-fo'] = 'kként', ine = 'kban', supere = 'kon', ades = 'knál', il = 'kba', subl = 'kra', al = 'khoz', el = 'kból', de = 'król', abl = 'któl', possessi = { 'ké', 'kéi' } } } }
                 },
    possessive = { d = { { { { 'om', 'aim' } }, { { 'od', 'jaid' } }, { { 'ja', 'jai'} } },
                         { { { 'unk', 'jaink' } }, { { 'otok', 'jaitok' } }, { { 'juk', 'jaik'} } } },
                   [''] = { { { { 'm', 'im' } }, { { 'd', 'id' } }, { { 'ja', 'i'} } },
                            { { { 'nk', 'ink' } }, { { 'tok', 'itok' } }, { { 'juk', 'ik'} } } } },
    scale = { long = Adder:new{ top = 96, link = 'Tíz hatványai' } }
}

local language_sv = k_family:new{
    code = 'sv',
    root = 'lj',
    inflection = { { { n = '', g = 's' }, 
                     { n = 'en', g = 'ens' } }, 
                   { { n = 'er', g = 'ers' }, 
                     { n = 'erna', g = 'ernas' } } },
    scale = { long = Adder:new{ top = 177, link = 'Namn på stora tal' } }
}

local s_family = k_family:new{
    code = '',
    prefix = { [6] = 'seksti' }
}

local language_da = s_family:new{
    code = 'da',
    inflection = { { { n = '', possessi = "s" }, 
                     { n = 'en', possessi = "ens" } }, 
                   { { n = 'er', possessi = "ers" }, 
                     { n = 'erne', possessi = "ernes" } } },
    scale = { long = Adder:new{ top = 63, link = 'Store tal' } }
}

local language_fi = s_family:new{
    code = 'fi',
    root = 'lj',
    suffix = { 'oon' },
    prefix = { [9] = 'novi', [10] = 'deki' },
    inflection = { stem =  makeArdMaker(9, "tuhat"),
                   function (u, args, r, simplify) 
                        local case = args.case
                        if mw.ustring.sub(u, 1, 6) == "tuhat " then
                            local base = 'tuha'
                            local cases = { n = 't', ['accus-n'] = 't', ['accus-g'] = 'nnen', g = 'nnen', pa = 'tta', ine = 'nnessa', el = 'nnesta', il = 'nteen', ades = 'nnella', abl = 'nnelta', al = 'nnelle', es = 'netena', tr = 'nneksi', abe = 'nnetta' }
                            return base .. cases[case] .. mw.ustring.sub(u, 6)
                        else
                            local cases = { n = 'a', ['accus-n'] = 'a', ['accus-g'] = 'an', g = 'an', pa = 'aa', ine = 'assa', el = 'asta', il = 'aan', ades = 'alla', abl = 'alta', al = 'alle', es = 'ana', tr = 'aksi', abe = 'atta', }
                            return u .. cases[case]
                        end
                   end,
                   function (u, args, r, simplify) 
                        local case = args.case
                        if mw.ustring.sub(u, 1, 6) == "tuhat " then
                            local base = 'tuha'
                            local cases = { n = 'tta', ['accus-n'] = 'nnet', g = 'nsien', pa = 'nsia', ine = 'nsissa', el = 'nsista', il = 'nsiin', ades = 'nsilla', abl = 'nsilta', al = 'nsille', es = 'nsina', tr = 'nsiksi', instruc = 'nsin', abe = 'nsitta', comi = 'nsine' }
                            if simplify and (case == '' or case == 'n') then
                                return u
                            else
                                return base .. cases[case] .. mw.ustring.sub(u, 6)
                            end
                        else
                            local cases = { n = 'aa', ['accus-n'] = 'at', g = 'ien', pa = 'ia', ine = 'issa', el = 'ista', il = 'iin', ades = 'illa', abl = 'ilta', al = 'ille', es = 'ina', tr = 'iksi', instruc = 'in', abe = 'itta', comi = 'ine' }
                            if simplify and (case == '' or case == 'n') then
                                return u .. 'at'
                            else
                                return u .. cases[case]
                            end
                        end
                   end },
    scale = { long = Adder:new{ top = 63, link = 'Suurten lukujen nimet' } }
}

local language_no = s_family:new{
    code = 'no',
    prefix = { [10] = 'desi' },
    unit_prefix = { [2] = 'do', [5] = 'kvin', [6] = 'seks', [8] = 'okto' },
    inflection = { { { n = '', g = 's' }, 
                     { n = 'en', g = 'ens' } }, 
                   { { n = 'er', g = 'ers' }, 
                     { n = 'ene', g = 'enes' } } },
    scale = { long = Adder:new{ top = 81, link = 'Navn på store tall' } }
}

local language_sl = s_family:new{
    code = 'sl',
    root = 'lij',
    inflection = { { n = '',
                     [''] = 'a' },    
                   { d = 'i',
                     [''] = 'a' },
                   'e',
                   [5] = { d = '',
                           [''] = 'ov' },
                   fraction = { d = 'e',
                                [''] = 'a' } },
    scale = { long = Adder:new{ top = 63, link = 'Imena velikih števil' } }
}

local u_family = k_family:new{
    code = '',
    root = 'li',
    unit_prefix = { [4] = 'kvadro', [5] = 'kvin' }
}

local language_cs = u_family:new{
    code = 'cs',
    prefix = { [50] = 'kvinkvaginti' },
    inflection = { { n = { { n = '', [''] = 'u', accus = '', instrum = 'em', v = 'e'} },
                     [''] = { { n = 'a', g = 'y', [''] = 'ě', accus = 'u', instrum = 'ou', v = 'o'} } },
                   { n = { { [''] = 'y', g = 'ů', da = 'ům', lo = 'ech'} },
                     [''] = { { [''] = 'y', g = '', da = 'ám', instrum = 'ami', lo = 'ách'} } },
                   [5] = { n = { { [''] = 'ů', da = 'ům', instrum = 'y', lo = 'ech', v = 'y' } },
                           [''] = { { [''] = '', da = 'ám', instrum = 'ami', lo = 'ách', v = 'y' } } },
                   fraction = { n = 'u',
                                [''] = 'y' } },
    scale = { long = Adder:new{ top = 99, link = 'Desítková soustava#Názvy velkých čísel' } }
}

local function ardInflectGenitivePlural_sk(u, args, r, simplify) 
    return mw.ustring.sub(u, 1, -4) .. "árd"
end

local language_sk = u_family:new{
    code = 'sk',
    suffix = { 'ón', 'ard' },
    prefix = { [40] = 'kvadraginti', [50] = 'kvinginti', [60] = 'seksaaginti', [80] = 'oktoginti', [90] = 'nonaginti' },
    unit_prefix = { [8] = 'okto' },
    inflection = { { n = { { [''] = '', g = 'a', da = 'u', instrum = 'om', lo = 'e' } },
                     [''] = { { n = 'a', g = 'y', accus = 'u', instrum = 'ou', [''] = 'e' } } },
                   { n = { { [''] = 'y', g = 'ov', da = 'om', instrum = 'mi', lo = 'och' } },
                     [''] = { { n = 'y', da = 'ám', accus = 'y', instrum = 'ami', lo = 'ách',
                              g = ardInflectGenitivePlural_sk } } },
                   [5] = { n = { { [''] = 'ov', da = 'om', instrum = 'mi', lo = 'och' } },
                           [''] = { { [''] = ardInflectGenitivePlural_sk,
                                   da = 'ám', instrum = 'ami', lo = 'ách' } } },
                   fraction = { n = 'a',
                                [''] = ardInflectGenitivePlural_sk } },
    scale = { long = Adder:new{ top = 306, link = 'Veľké čísla' } }
}

function Family:getPrefix(i)
    return self:tableInherit('prefix', i)
end

function Family:getUnitPrefix(i)
    return self:tableInherit('unit_prefix', i)
end

function Family:makePrefix(j)
    local entry = self.prefix_exception[j] and self.prefix_exception[j][self.code]
    local p
    if entry then
        p = entry
    else
        p = self:getPrefix(j)
        if not p then
            if j < 11 then
                return nil
            elseif j < 100 then
                local k = math.floor(j / 10)
                local d = k * 10
                local l = j - d
                local unitentry = self.unit_prefix_exception[l] and self.unit_prefix_exception[l][self.code]
                local u
                if unitentry then
                    u = unitentry
                else
                    u = self:getUnitPrefix(l)
                end
                local decentry = self.prefix_exception[d] and self.prefix_exception[d][self.code]
                local x
                if decentry then
                    x = decentry
                else
                    x = self:getPrefix(d)
                end
                p = u .. x
            else
                return nil
            end
        end
    end
    if self.capitalize then
        return mw.ustring.upper(mw.ustring.sub(p, 1, 1)) .. mw.ustring.sub(p, 2)
    else
        return p
    end
end

function Family:buildOne(i, step)
    local k = i / 3
    if k ~= _round(k, 0) then
        return nil
    end
    local j
    if step == 3 then
        j = math.floor((i - 3)/ step)
    else
        j = math.floor(i / step)
    end
    if j < 1 then
        return nil
    else
        local pref = self:makePrefix(j)
        local suf 
        if step == 3 then
            suf = self.suffix[1]
        else
            suf = (j * step) == i and self.suffix[1] or self.suffix[2]
        end
        return pref ..  self.root .. suf
    end
end

local Scale = Adder:new{}

local indian_scale = Scale:new{
    name = 'indian',
    step = 1,
    family = families['indian']
}

local myriad_scale = Scale:new{
    name = 'myriad',
    step = 4,
    family = families['myriad']
}

local scale3 = Scale:new{
    build = true
}

local short_scale = scale3:new{
    name = 'short',
    step = 3,
    family = families['short']
}

local long_scale = scale3:new{
    name = 'long',
    step = 6,
    family = families['long']
}

local scales = {
    indian = indian_scale,
    long = long_scale,
    myriad = myriad_scale,
    short = short_scale,
    
    ind = indian_scale,
    fra = long_scale,
    usa = short_scale
}


function Scale:getLanguage(lang)
    local family = self.family
    return family and family[lang]
end

function Scale:getField(name, lang, top)
    local family = self.family
    local language = family and family[lang]
    local scales = language and language.scale
    local special = scales and scales[self.name]
    local result = special and special[name]
    if result then
        return result, true -- overriden
    else
        local v = self[name]
        if top then
            return v, false
        else
            return v and v[lang], false
        end
    end
end

function Scale:getOne(i, args, r)
    local lang = args.lang
    local one, simplify
    
    local function getAdditional(o, lang)
        local one, simplify
        local script = o.script
        if script then
            local top = (lang and script[lang]) or script
            local scr = top and top[args.script]
            if scr then
                one, simplify = scr:getAdditional(nil, i)
            end
        end
        
        if not one then
            one, simplify = o:getAdditional(lang, i)
        end
        
        return one, simplify
    end
        
    do
        local exception, over = self:getField('exception', lang, true)
        if exception then
            local top = exception[i]
            local entry = top and ((over or type(top) == 'string') and top or top[lang])
            if entry then 
                one, simplify = entry[1], entry.simplify
            end
        end
    end
    
    local language = args.language
    
    if not one then
        local scales = language.scale
        local special = scales and scales[self.name]
        if special then
            one, simplify = getAdditional(special, nil)
        end
    end
    
    if not one then
        one, simplify = getAdditional(self, lang)
    end
    
    if one then
        return one, simplify
    elseif language.inflection and language.inflection.stem then
        local d = language.inflection.stem
        if type(d) == 'function' then
            return d(self, i, self.step, args, r, simplify)
        end
    else
        return nil
    end
end

function Scale:unit(i, args, r, simplify)
    local language, one, stem, entry, inflectState, inflectEndings, inflectClass
    
    local function makeApplier(simplify, f)
        return function (d, arg)
                    if not d then
                        return nil
                    elseif type(d) == 'function' then
                        return d(one, args, r, simplify), simplify, stem
                    elseif type(d) == 'table' then
                        return f(d, arg)
                    else
                        return one .. d, simplify, stem
                    end
               end
    end

    local function makeDoer(topn, ensure, arr, simplify, f)
        return function (n, arg)
                    local d = arr[n]
                    if d then
                        return f(d, arg)
                    else
                        if topn > 1 and n > 2 then
                            d = arr[2]
                            if d then
                                return f(d, arg)
                            end
                        end
                        if topn > 0 and n > 1 then
                            d = arr[1]
                            if d then
                                return f(d, arg)
                            end
                        end
                        if ensure then
                            return one, simplify, stem
                        else
                            return nil
                        end
                    end
               end
    end
    
    local function doMain(arr, simplify, ensure, applier, doer, f)
        if arr then
            return f(applier, doer)
        elseif ensure then
            return one, simplify, stem
        else
            return nil
        end
    end
    
    local function doFun(arr, simplify, topn, ensure, application, main)
        local applier = makeApplier(simplify, application)
        local doer
        if topn then
            doer = makeDoer(topn, ensure, arr, simplify, applier)
        end
        return doMain(arr, implify, ensure, applier, doer, main)
    end
    
    local function inflectPossessed(arr, other, simplify)
        return doFun(arr, simplify, 1, true, 
                     function(d, arg) return inflectEndings(d, simplify, false, false, false) end,
                     function (applier, doer)
                        if (other and args.possessed) or ((not other) and r > 1) then
                            return doer(2)
                        else
                            return doer(1)
                        end
                     end)
        
    end
    
    local function declineCase(d, simplify, docla)
        local applyCase = makeApplier(simplify, 
                                      function(d, arg) 
                                        if docla then
                                            local result, simp, s = inflectClass(d, simplify, false, true, false)
                                            if result then
                                                return result, simp, s
                                            end
                                        end
                                        return inflectPossessed(d, true, simplify)
                                      end)
        local c = d[args.case]
        if not c then
            c = d[''] -- default inflection
        end
        return applyCase(c)
    end

    inflectClass = function(t, simplify, dosta, dosub, docas)
                        local class = args.class
                        return doFun(t, simplify, 0, false, 
                                     function(v, arg) 
                                        local result, simp, s
                                        if dosub then
                                            result, simp, s = inflectEndings(v, simplify, false, false, false)
                                        end
                                        if result then
                                            return result, simp, s
                                        elseif docas then
                                            result, simp, s = declineCase(v, simplify, false)
                                        end
                                        if result then
                                            return result, simp, s
                                        elseif dosta then
                                            return inflectState(v, simplify, false)
                                        else
                                            return nil
                                        end
                                     end,
                                     function(applier, doer)
                                        local done, elsevalue
                                        for k, v in pairs(t) do
                                            if k == '' then
                                                elsevalue = v
                                                if done then
                                                    break
                                                end
                                            elseif not done and type(k) == 'string' then
                                                local result, simp, s = doer(class)
                                                if result then
                                                    return result, simp, s
                                                else
                                                    if elsevalue then
                                                        break
                                                    else
                                                        done = true
                                                    end
                                                end
                                            end
                                        end
                                        return applier(elsevalue)
                                     end)
                   end

    inflectState = function(arr, simplify, docla)
                        return doFun(arr, simplify, 1, true, 
                                     function(d, arg) return declineCase(d, simplify, docla) end,
                                     function(applier, doer)
                                        local state = args.state
                                        if state == 'c' then
                                            return doer(3)
                                        elseif state == 'd' then
                                            return doer(2)
                                        else
                                            return doer(1)
                                        end
                                     end)
                                     
                    end
    
    local function possessiveClusivity(arr, n, plural, simplify)
        return doFun(arr, simplify, 1, true, 
                     function(d, arg) return inflectPossessed(d, false, simplify) end,
                     function(applier, doer)
                        if plural then
                            local exclude = args.exclude
                            if not exclude or n ~= 2 then
                                return doer(1)
                            elseif exclude == 3 then
                                return doer(2)
                            elseif exclude == 2 then
                                return doer(3)
                            else
                                return doer(1)
                            end
                        else
                            return doer(1)
                        end
                     end)
    end
                    
    local function possessivePerson(arr, plural, arg, simplify)
        return doFun(arr, simplify, 2, true, 
                     function(d, arg) return possessiveClusivity(d, arg, plural, simplify) end,
                     function(applier, doer) return doer(args.person, args.person) end)
                      
    end
    
    local function possessivePlural(arr, simplify)
        return doFun(arr, simplify, 1, true, 
                     function(d, arg) return possessivePerson(d, arg, nil, simplify) end,
                     function(applier, doer)
                        if args.plural then
                            return doer(2, true)
                        else
                            return doer(1, false)
                        end
                     end)
    end
    
    inflectEndings = function(t, simplify, docla, dosta, dopos)
                        return doFun(t, simplify, nil, true, 
                                     function(v, arg) 
                                        local result, simp, s
                                        if docla then
                                            result, simp, s = inflectClass(v, simplify, true, false, true)
                                        end
                                        if result then
                                            return result, simp, s
                                        elseif dosta then
                                            return inflectState(v, simplify, docla)
                                        elseif dopos then
                                            return possessivePlural(v, simplify)
                                        else
                                            return nil
                                        end
                                     end,
                                     function(applier, doer)
                                        local olen, elsevalue
                                        local ulen = mw.ustring.len
                                        local usub = mw.ustring.sub
                                        for k, v in pairs(t) do
                                            if k == '' then
                                                elsevalue = v
                                            elseif type(k) == 'string' then
                                                local l = ulen(k)
                                                if not olen then
                                                    olen = ulen(one)
                                                end
                                                if olen >= l then
                                                    if k == usub(one, -l, -1) then
                                                        return applier(v)
                                                    end
                                                end
                                            end
                                        end
                                        return applier(elsevalue)
                                     end)
                     end

    local function inflectNumberCase(number_case, simplify)
        local applyNumberCase = makeApplier(simplify, 
                                            function(d, arg) 
                                                local result, simp, s = inflectEndings(d, simplify, true, true, false)
                                                if result then
                                                    return result, simp, s
                                                else
                                                    return inflectState(d, simplify, true)
                                                end
                                            end)
        if entry and entry[number_case] then
            return entry[number_case], simplify, entry[1] or stem
        elseif not one then
            return nil
        elseif language.inflection then
            return applyNumberCase(language.inflection[number_case])
        end
    end

    local function inflectNumber(number_case, simplify)
        local result, simp, st
        if number_case == 5 then
            result, simp, st = inflectNumberCase(5, simplify)
        end
        if result then
            return result, simp, st
        end
        if number_case == 5 or number_case == 3 then
            result, simp, st = inflectNumberCase(3, simplify)
        end
        if result then
            return result, simp, st
        end
        if number_case == 5 or number_case == 3 or number_case == 2 then
            result, simp, st = inflectNumberCase(2, simplify)
        end
        if result then
            return result, simp, st
        else
            result, simp, st = inflectNumberCase(1, simplify)
            if result then
                return result, simp, st
            else
                return one, simplify, stem
            end
        end
    end
    
    local function possessive(simplify)
        local applyPossessive = makeApplier(simplify, 
                                            function(d, arg) 
                                                local result, simp, s = inflectEndings(d, simplify, false, false, true)
                                                if result then
                                                    return result, simp, s
                                                else 
                                                    return possessivePlural(d, simplify)
                                                end
                                            end)
        if entry and entry.possessive then
            return entry.possessive, simplify, entry.possessive
        elseif not one then
            return nil
        else
            return applyPossessive(language.possessive)
        end
    end
    
    language = args.language
    local s
    one, s, stem = self:getOne(i, args, r)
    local simp = simplify or s
    if not one then
        one = self.build and language and language:buildOne(i, self.step)
        stem = one
    end
    local exception, over = self:getField('exception', lang, true)
    local top = exception and exception[i]
    entry = top and ((over or type(top) == 'string') and top or top[lang])
    if args.person then
        return possessive(simplify)
    else
        if r == 1 then
            return inflectNumber(1, simp)
        elseif r == 2 then
            return inflectNumber(2, simplify)
        elseif r == 3 or r == 4 then
            return inflectNumber(3, simplify)
        elseif r == _round(r, 0) then
            return inflectNumber(5, simplify)
        else
            local result, rs, st = inflectNumberCase('fraction', simp) 
            if result then
                return result, rs, st
            else
                return inflectNumber(5, simplify)
            end
        end
    end
end

function Scale:unitLink(lk, i, stem, u, lang)
    if lk then
        local f = self:getField('link', lang) 
        if f then
            if type(f) == 'function' then
                local l, full = f(i, stem, u)
                if full then
                    return l
                else
                    return linkBegin(lang) .. l
                end
            else
                return linkBegin(lang) .. f .. linkEnd(u)
            end
        else
            return linkBegin('en') .. link_en(i, stem, u)
        end
    else
        return u
    end
end

function Scale:found(args, i, r)
    local lk = args.lk
    local u, simp, stem = self:unit(i, args, r, args.simplify)
    if not u then
        return nil
    elseif args.simplify or simp then
        return self:unitLink(lk, i, stem, u, args.lang) 
    else
        local lang = args.lang
        return mf.formatNum(r, lang, args.prec) .. ((args.language.space and " ") or "") .. self:unitLink(lk, i, stem, u, lang)
    end
end
        
function Scale:binary(x, args, top, bottom)
    local target, target_r
    local lang = args.lang
    local prec = args.prec
    local floor = math.floor
    local pow = math.pow
    local rou = _round
    local block, iblock
    if self.step == 4 then
        block = 10000
        iblock = 4
    else
        block = 1000
        iblock = 3
    end
    while top >= bottom do  
        local i
        if top == bottom then
            i = top
        else
            local diff = top - bottom
            local d = floor(diff / 2)
            local m = bottom + d
            if m/iblock == floor(m/iblock) then
                i = m
            else
                i = m + 2
            end 
        end
        local y = x / pow(10,i)
        local r = rou(y, prec)
        local exception, over = self:getField('exception', lang, true)
        local entry = exception and exception[i]
        if not entry or (entry ~= 'none' and (over or entry[lang] ~= "none")) then
            if r >= block and top > i then
                bottom = i + iblock
                target = i
                target_r = r
            elseif r >= 1 then
                local result = self:found(args, i, r)
                if result then
                    return result
                else
                    top = i - iblock
                end
            else 
                top = i - iblock
            end
        elseif r < block or top == i then
            top = i - iblock
        else
            local result
            if top > i and r >= block then
                result = self:binary(x, args, top, i + iblock)
            end
            if result then
                return result
            elseif i > bottom then
                result = self:binary(x, args, i - iblock, bottom)
                if result then
                    return result
                end
            end
            break
        end
    end
    
    if target then
        local result = self:found(args, target, target_r)
        if result then
            return result
        else
            return nil
        end
    end
    
    return nil 
end

function Scale:wordify(x, args)
    local prec = args.prec
    return mf.formatNum(_round(x, prec), args.lang, prec) 
end

function scale3:wordify(x, args)
    local toptop = self:getField('top', args.lang)
    if toptop then
        do
            local top = toptop
            local bottom = 102
            local result = self:binary(x, args, top, bottom)
            if result then
                return result
            end
        end
    
        if 100 <= toptop then -- allow googol
            local top = 100
            local bottom = 100
            local result = self:binary(x, args, top, bottom)
            if result then
                return result
            end
        end
        
        do
            local top = (99 <= toptop and 99) or toptop
            local bottom = 6
            local result = self:binary(x, args, top, bottom)
            if result then
                return result
            end
        end
    end
    
    return Scale.wordify(self, x, args)
end

function myriad_scale:wordify(x, args)
    local toptop = self:getField('top', args.lang)
    if toptop then
        do
            local top = toptop
            local bottom = 4
            local result = self:binary(x, args, top, bottom)
            if result then
                return result
            end
        end
    end
    
    return Scale.wordify(self, x, args)
end

function indian_scale:wordify(x, args)
    local pow = math.pow
    local rou = _round
    local prec = args.prec
    local scales = args.language.scale
    local special = scales and scales[self.name]
    local scripts = special and special.script
    local scr = (scripts and scripts[lang]) or special
    for i, _ in reverseSparseIpairs((scr and scr.additional) or self.additional) do
        local y = x / pow(10,i)
        local r = rou(y, prec)
        if r >= 1 then
            return self:found(args, i, r) 
        end
    end
    
    return Scale.wordify(self, x, args)
end
            
function p._wordify(x, prec, lk, numsys, lang, script, state, case, class, possessed, person, plural, exclude, simplify)
    local deflang = language:default(nil, lang)
    local defsys = number_systems:default(deflang, numsys)
    local canonsys = number_systems:get(defsys) or defsys
    
    if tonumber(x) then
        local scale = scales[canonsys]
        if scale then
            local language = scale:getLanguage(deflang)
            if language then
                local defscript = scripts:default(deflang, script, canonsys)
                local canonscript = scripts:get(defscript) or defscript
                local defstate = states:default(deflang, state)
                local canonstate = states:get(defstate) or defstate
                local defcase = cases:default(deflang, case)
                local canoncase = cases:get(defcase) or defcase
                local defclass = classes:default(deflang, class)
                local canonclass = classes:get(defclass) or defclass
                
                local args = {
                    prec = prec,
                    lk = lk,
                    lang = deflang, 
                    language = language,
                    script = canonscript,
                    state = canonstate, 
                    case = canoncase, 
                    class = canonclass, 
                    possessed = possessed,
                    person = person,
                    plural = plural, 
                    exclude = exclude, 
                    simplify = simplify
                }
        
                return scale:wordify(x, args)
            else
                return err("Language '" .. deflang .. "' not supported by number system '" .. canonsys .. "'")
            end
        else
            return err("number system '" .. canonsys .. "' not supported")
        end 
    else
      return err("Not a number: " .. x)
    end
end

--[[
Helper function that interprets the input numerically.  If the
input does not appear to be a number, attempts evaluating it as
a parser functions expression.
]]

function p._cleanNumber(number_string)
    if type(number_string) == 'number' then
        -- We were passed a number, so we don't need to do any processing.
        return number_string, tostring(number_string)
    elseif type(number_string) ~= 'string' or not number_string:find('%S') then
        -- We were passed a non-string or a blank string, so exit.
        return nil, nil;
    end

    -- Attempt basic conversion
    local number = tonumber(number_string)

    -- If failed, attempt to evaluate input as an expression
    if number == nil then
        local success, result = pcall(mw.ext.ParserFunctions.expr, number_string)
        if success then
            number = tonumber(result)
            number_string = tostring(number)
        else
            number = nil
            number_string = nil
        end
    else
        number_string = number_string:match("^%s*(.-)%s*$") -- String is valid but may contain padding, clean it.
        number_string = number_string:match("^%+(.*)$") or number_string -- Trim any leading + signs.
        if number_string:find('^%-?0[xX]') then
            -- Number is using 0xnnn notation to indicate base 16; use the number that Lua detected instead.
            number_string = tostring(number)
        end
    end

    return number, number_string
end

return p