Module:Range

From blackwiki
Jump to navigation Jump to search

Template:Template for discussion/dated Returns one function called "iswitch", which takes its inspiration from {{#switch, but is limited to numeric values, and allows for ranges.

usage:

{{#invoke:Range | iswitch
| value = 12
| 1 2 3..7 50..70 = one, two, three to seven, or fifty to seventy
| 9..40 = between nine and forty (inclusive)
| [41..48] = between 41 and 48 (inclusive)
| (80..100 = larger (but not equal) than 80, and less or equal than 100
| (120..140) = larger (but not equal) than 120 and smaller than 140
| ..-2e9) = any number smaller (but not equal) than minus two billion
| -2e9..0.75 = between minus two billion and three quarters
| 1e6.. = equal or larger than 1 million
| default = None of the ranges matches the value
}}

syntax: {{#invoke:Range | iswitch | value = <Number> | ( <Ranges> = Result | )... [default=Result] }}

<Number> ::= [-][digits][.][digits][e digits]
<Ranges> ::= [ <Number> | <Range> ]...
<Range> ::= [ [ [ | ( ] [<Number>] .. [<Number>] [ ] | ) ]
Result ::= any string (can be multiline) that does not contain | or }} (if needed, use {{!}}, and Template:T. )
When a range begins with (, it means the value has to be larger than the left limit. otherwise, it can be equal to the left limit. For convenience, [ can be used to mark "larger or equal", but it's not required (i.e. "1..100" is the same as "[1..100"
Similarly, when a range ends with ) it means the value has to be strictly less than the right limit.
A range may begin or end with ".."
Such that ..100 means "100 or smaller", and 60.. means "60 or larger".
"..100)" means smaller than 100, and "(60.." means larger than 60

if value matches a ranges, the function will return the "Result" of one of the matching ranges. If more than one range matches, it is unpredictable which of the results will be returned. The function does not test for overlap.

if none of the ranges match, and there is a "default", this value will return. otherwise, the function will return an empty string.

For tests see Module talk:Range/testcases


local function ranges( args )
    local value
    
    function inrange( v )
        if tonumber( v ) then return tonumber( v ) == value end -- simple number - just compare
        local lp, a, b, rp = v:match( '^([([]?)(.-)%.%.(.-)([)%]]?)$' ) -- a range
        local na, nb = tonumber( a ), tonumber( b )
        if  a ~= '' and not na -- both a and b must be either empty or a valid number
            or b ~= '' and not nb 
            or not ( na or nb ) -- at least one of na, nb must be a number
        then 
            error(string.format( 'Bad parameter calling Range:iswitch: "%s" is not a number or valid range', v ), 0 )
            return false
        end 
        
        local llok = not na or na < value or lp ~= '(' and na == value 
        -- left-limit-ok: no ll or ll < value or ll == value and not open range
            
        local rlok = not nb or value < nb or rp ~= ')' and value == nb 
        -- right-limit-ok: no rl or value < rl or value == rl and not open range
        
        return llok and rlok
    end

    function match( s )
        if type( s ) == 'number' then return s == value end
        local res = false
        s:gsub("%S+", function( c ) res = res or inrange( c ) end)
        return res
    end
    
    value = tonumber( args.value )
    if not value then error('Range:iswitch: should have a numeric "value" parameter', 0) end
    for k, v in pairs( args ) do
        if k ~= 'value' and k~= 'default' and match( k ) then return v end
    end
    return args['default'] or ''
end

return { iswitch = function( frame ) return ranges( frame.args ) end, }