Line 1: |
Line 1: |
| --[[ | | --[[ |
− | | + | |
| This module provides a number of basic mathematical operations. | | This module provides a number of basic mathematical operations. |
− | | + | |
| ]] | | ]] |
− | | + | |
− | local yesno, getArgs -- lazily initialized | + | local yesno = require('Module:Yesno') |
− | | + | local getArgs = require('Module:Arguments').getArgs |
| + | |
| local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules. | | local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules. |
| local wrap = {} -- Holds wrapper functions that process arguments from #invoke. These act as intemediary between functions meant for #invoke and functions meant for Lua. | | local wrap = {} -- Holds wrapper functions that process arguments from #invoke. These act as intemediary between functions meant for #invoke and functions meant for Lua. |
− | | + | |
| --[[ | | --[[ |
| Helper functions used to avoid redundant code. | | Helper functions used to avoid redundant code. |
| ]] | | ]] |
− | | + | |
| local function err(msg) | | local function err(msg) |
| -- Generates wikitext error messages. | | -- Generates wikitext error messages. |
| return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg) | | return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg) |
| end | | end |
− | | + | |
| local function unpackNumberArgs(args) | | local function unpackNumberArgs(args) |
| -- Returns an unpacked list of arguments specified with numerical keys. | | -- Returns an unpacked list of arguments specified with numerical keys. |
Line 29: |
Line 30: |
| return unpack(ret) | | return unpack(ret) |
| end | | end |
− | | + | |
| local function makeArgArray(...) | | local function makeArgArray(...) |
| -- Makes an array of arguments from a list of arguments that might include nils. | | -- Makes an array of arguments from a list of arguments that might include nils. |
Line 48: |
Line 49: |
| return ret | | return ret |
| end | | end |
− | | + | |
− | local function fold(func, ...) | + | local function applyFuncToArgs(func, ...) |
| -- Use a function on all supplied arguments, and return the result. The function must accept two numbers as parameters, | | -- Use a function on all supplied arguments, and return the result. The function must accept two numbers as parameters, |
| -- and must return a number as an output. This number is then supplied as input to the next function call. | | -- and must return a number as an output. This number is then supplied as input to the next function call. |
− | local vals = makeArgArray(...) | + | local vals = makeArgArray(...) |
| local count = #vals -- The number of valid arguments | | local count = #vals -- The number of valid arguments |
| if count == 0 then return | | if count == 0 then return |
| -- Exit if we have no valid args, otherwise removing the first arg would cause an error. | | -- Exit if we have no valid args, otherwise removing the first arg would cause an error. |
| nil, 0 | | nil, 0 |
− | end | + | end |
| local ret = table.remove(vals, 1) | | local ret = table.remove(vals, 1) |
| for _, val in ipairs(vals) do | | for _, val in ipairs(vals) do |
Line 64: |
Line 65: |
| return ret, count | | return ret, count |
| end | | end |
− | | + | |
− | --[[
| |
− | Fold arguments by selectively choosing values (func should return when to choose the current "dominant" value).
| |
− | ]]
| |
− | local function binary_fold(func, ...)
| |
− | local value = fold((function(a, b) if func(a, b) then return a else return b end end), ...)
| |
− | return value
| |
− | end
| |
− | | |
| --[[ | | --[[ |
| random | | random |
− | | + | |
| Generate a random number | | Generate a random number |
− | | + | |
| Usage: | | Usage: |
| {{#invoke: Math | random }} | | {{#invoke: Math | random }} |
Line 83: |
Line 76: |
| {{#invoke: Math | random | minimum value | maximum value }} | | {{#invoke: Math | random | minimum value | maximum value }} |
| ]] | | ]] |
− | | + | |
| function wrap.random(args) | | function wrap.random(args) |
| local first = p._cleanNumber(args[1]) | | local first = p._cleanNumber(args[1]) |
Line 89: |
Line 82: |
| return p._random(first, second) | | return p._random(first, second) |
| end | | end |
− | | + | |
| function p._random(first, second) | | function p._random(first, second) |
| math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000)) | | math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time() + math.floor(os.clock() * 1000000000)) |
Line 103: |
Line 96: |
| end | | end |
| end | | end |
− | | + | |
| --[[ | | --[[ |
| order | | order |
− | | + | |
| Determine order of magnitude of a number | | Determine order of magnitude of a number |
− | | + | |
| Usage: | | Usage: |
| {{#invoke: Math | order | value }} | | {{#invoke: Math | order | value }} |
| ]] | | ]] |
− | | + | |
| function wrap.order(args) | | function wrap.order(args) |
| local input_string = (args[1] or args.x or '0'); | | local input_string = (args[1] or args.x or '0'); |
Line 120: |
Line 113: |
| else | | else |
| return p._order(input_number) | | return p._order(input_number) |
− | end | + | end |
| end | | end |
− | | + | |
| function p._order(x) | | function p._order(x) |
| if x == 0 then return 0 end | | if x == 0 then return 0 end |
| return math.floor(math.log10(math.abs(x))) | | return math.floor(math.log10(math.abs(x))) |
| end | | end |
− | | + | |
| --[[ | | --[[ |
| precision | | precision |
− | | + | |
| Detemines the precision of a number using the string representation | | Detemines the precision of a number using the string representation |
− | | + | |
| Usage: | | Usage: |
| {{ #invoke: Math | precision | value }} | | {{ #invoke: Math | precision | value }} |
| ]] | | ]] |
− | | + | |
| function wrap.precision(args) | | function wrap.precision(args) |
| local input_string = (args[1] or args.x or '0'); | | local input_string = (args[1] or args.x or '0'); |
| local trap_fraction = args.check_fraction; | | local trap_fraction = args.check_fraction; |
| local input_number; | | local input_number; |
− | | + | |
− | if not yesno then
| |
− | yesno = require('Module:Yesno')
| |
− | end
| |
| if yesno(trap_fraction, true) then -- Returns true for all input except nil, false, "no", "n", "0" and a few others. See [[Module:Yesno]]. | | if yesno(trap_fraction, true) then -- Returns true for all input except nil, false, "no", "n", "0" and a few others. See [[Module:Yesno]]. |
| local pos = string.find(input_string, '/', 1, true); | | local pos = string.find(input_string, '/', 1, true); |
Line 154: |
Line 144: |
| return math.log10(denom_value); | | return math.log10(denom_value); |
| end | | end |
− | end | + | end |
| end | | end |
− | end | + | end |
− | | + | |
| input_number, input_string = p._cleanNumber(input_string); | | input_number, input_string = p._cleanNumber(input_string); |
| if input_string == nil then | | if input_string == nil then |
Line 163: |
Line 153: |
| else | | else |
| return p._precision(input_string) | | return p._precision(input_string) |
− | end | + | end |
| end | | end |
− | | + | |
| function p._precision(x) | | function p._precision(x) |
| if type(x) == 'number' then | | if type(x) == 'number' then |
Line 171: |
Line 161: |
| end | | end |
| x = string.upper(x) | | x = string.upper(x) |
− | | + | |
| local decimal = x:find('%.') | | local decimal = x:find('%.') |
| local exponent_pos = x:find('E') | | local exponent_pos = x:find('E') |
| local result = 0; | | local result = 0; |
− | | + | |
| if exponent_pos ~= nil then | | if exponent_pos ~= nil then |
| local exponent = string.sub(x, exponent_pos + 1) | | local exponent = string.sub(x, exponent_pos + 1) |
| x = string.sub(x, 1, exponent_pos - 1) | | x = string.sub(x, 1, exponent_pos - 1) |
| result = result - tonumber(exponent) | | result = result - tonumber(exponent) |
− | end | + | end |
− | | + | |
| if decimal ~= nil then | | if decimal ~= nil then |
| result = result + string.len(x) - decimal | | result = result + string.len(x) - decimal |
| return result | | return result |
| end | | end |
− | | + | |
| local pos = string.len(x); | | local pos = string.len(x); |
| while x:byte(pos) == string.byte('0') do | | while x:byte(pos) == string.byte('0') do |
Line 195: |
Line 185: |
| end | | end |
| end | | end |
− | | + | |
| return result | | return result |
| end | | end |
− | | + | |
− | | |
| --[[ | | --[[ |
| max | | max |
− | | + | |
| Finds the maximum argument | | Finds the maximum argument |
− | | + | |
| Usage: | | Usage: |
| {{#invoke:Math| max | value1 | value2 | ... }} | | {{#invoke:Math| max | value1 | value2 | ... }} |
− | | + | |
| Note, any values that do not evaluate to numbers are ignored. | | Note, any values that do not evaluate to numbers are ignored. |
| ]] | | ]] |
− | | + | |
| function wrap.max(args) | | function wrap.max(args) |
| return p._max(unpackNumberArgs(args)) | | return p._max(unpackNumberArgs(args)) |
| end | | end |
− | | + | |
| function p._max(...) | | function p._max(...) |
− | local max_value = binary_fold((function(a, b) return a > b end), ...) | + | local function maxOfTwo(a, b) |
| + | if a > b then |
| + | return a |
| + | else |
| + | return b |
| + | end |
| + | end |
| + | local max_value = applyFuncToArgs(maxOfTwo, ...) |
| if max_value then | | if max_value then |
| return max_value | | return max_value |
| end | | end |
| end | | end |
− | | + | |
| --[[ | | --[[ |
− | median
| + | min |
− | | + | |
− | Find the median of set of numbers
| |
− | | |
− | Usage:
| |
− | {{#invoke:Math | median | number1 | number2 | ...}}
| |
− | OR
| |
− | {{#invoke:Math | median }}
| |
− | ]]
| |
− | | |
− | function wrap.median(args)
| |
− | return p._median(unpackNumberArgs(args))
| |
− | end
| |
− | | |
− | function p._median(...)
| |
− | local vals = makeArgArray(...)
| |
− | local count = #vals
| |
− | table.sort(vals)
| |
− | | |
− | if count == 0 then
| |
− | return 0
| |
− | end
| |
− | | |
− | if p._mod(count, 2) == 0 then
| |
− | return (vals[count/2] + vals[count/2+1])/2
| |
− | else
| |
− | return vals[math.ceil(count/2)]
| |
− | end
| |
− | end
| |
− | | |
− | --[[
| |
− | min | |
− | | |
| Finds the minimum argument | | Finds the minimum argument |
− | | + | |
| Usage: | | Usage: |
| {{#invoke:Math| min | value1 | value2 | ... }} | | {{#invoke:Math| min | value1 | value2 | ... }} |
| OR | | OR |
| {{#invoke:Math| min }} | | {{#invoke:Math| min }} |
− | | + | |
| When used with no arguments, it takes its input from the parent | | When used with no arguments, it takes its input from the parent |
| frame. Note, any values that do not evaluate to numbers are ignored. | | frame. Note, any values that do not evaluate to numbers are ignored. |
| ]] | | ]] |
− | | + | |
| function wrap.min(args) | | function wrap.min(args) |
| return p._min(unpackNumberArgs(args)) | | return p._min(unpackNumberArgs(args)) |
| end | | end |
− | | + | |
| function p._min(...) | | function p._min(...) |
− | local min_value = binary_fold((function(a, b) return a < b end), ...) | + | local function minOfTwo(a, b) |
| + | if a < b then |
| + | return a |
| + | else |
| + | return b |
| + | end |
| + | end |
| + | local min_value = applyFuncToArgs(minOfTwo, ...) |
| if min_value then | | if min_value then |
| return min_value | | return min_value |
| end | | end |
| end | | end |
− | | + | |
| --[[ | | --[[ |
− | sum
| + | average |
− | | + | |
− | Finds the sum
| |
− | | |
− | Usage:
| |
− | {{#invoke:Math| sum | value1 | value2 | ... }}
| |
− | OR
| |
− | {{#invoke:Math| sum }}
| |
− | | |
− | Note, any values that do not evaluate to numbers are ignored.
| |
− | ]]
| |
− | | |
− | function wrap.sum(args)
| |
− | return p._sum(unpackNumberArgs(args))
| |
− | end
| |
− | | |
− | function p._sum(...)
| |
− | local sums, count = fold((function(a, b) return a + b end), ...)
| |
− | if not sums then
| |
− | return 0
| |
− | else
| |
− | return sums
| |
− | end
| |
− | end
| |
− | | |
− | --[[
| |
− | average | |
− | | |
| Finds the average | | Finds the average |
− | | + | |
| Usage: | | Usage: |
| {{#invoke:Math| average | value1 | value2 | ... }} | | {{#invoke:Math| average | value1 | value2 | ... }} |
| OR | | OR |
| {{#invoke:Math| average }} | | {{#invoke:Math| average }} |
− | | + | |
| Note, any values that do not evaluate to numbers are ignored. | | Note, any values that do not evaluate to numbers are ignored. |
| ]] | | ]] |
− | | + | |
| function wrap.average(args) | | function wrap.average(args) |
| return p._average(unpackNumberArgs(args)) | | return p._average(unpackNumberArgs(args)) |
| end | | end |
− | | + | |
| function p._average(...) | | function p._average(...) |
− | local sum, count = fold((function(a, b) return a + b end), ...) | + | local function getSum(a, b) |
| + | return a + b |
| + | end |
| + | local sum, count = applyFuncToArgs(getSum, ...) |
| if not sum then | | if not sum then |
| return 0 | | return 0 |
Line 329: |
Line 278: |
| end | | end |
| end | | end |
− | | + | |
| --[[ | | --[[ |
| round | | round |
− | | + | |
| Rounds a number to specified precision | | Rounds a number to specified precision |
− | | + | |
| Usage: | | Usage: |
| {{#invoke:Math | round | value | precision }} | | {{#invoke:Math | round | value | precision }} |
− | | + | |
| --]] | | --]] |
− | | + | |
| function wrap.round(args) | | function wrap.round(args) |
| local value = p._cleanNumber(args[1] or args.value or 0) | | local value = p._cleanNumber(args[1] or args.value or 0) |
Line 347: |
Line 296: |
| else | | else |
| return p._round(value, precision) | | return p._round(value, precision) |
− | end | + | end |
| end | | end |
− | | + | |
| function p._round(value, precision) | | function p._round(value, precision) |
| local rescale = math.pow(10, precision or 0); | | local rescale = math.pow(10, precision or 0); |
| return math.floor(value * rescale + 0.5) / rescale; | | return math.floor(value * rescale + 0.5) / rescale; |
| end | | end |
− | | + | |
− | --[[
| |
− | log10
| |
− | | |
− | returns the log (base 10) of a number
| |
− | | |
− | Usage:
| |
− | {{#invoke:Math | log10 | x }}
| |
− | ]]
| |
− | | |
− | function wrap.log10(args)
| |
− | return math.log10(args[1])
| |
− | end
| |
− | | |
| --[[ | | --[[ |
| mod | | mod |
− | | + | |
| Implements the modulo operator | | Implements the modulo operator |
− | | + | |
| Usage: | | Usage: |
| {{#invoke:Math | mod | x | y }} | | {{#invoke:Math | mod | x | y }} |
− | | + | |
| --]] | | --]] |
− | | + | |
| function wrap.mod(args) | | function wrap.mod(args) |
| local x = p._cleanNumber(args[1]) | | local x = p._cleanNumber(args[1]) |
Line 387: |
Line 323: |
| else | | else |
| return p._mod(x, y) | | return p._mod(x, y) |
− | end | + | end |
| end | | end |
− | | + | |
| function p._mod(x, y) | | function p._mod(x, y) |
| local ret = x % y | | local ret = x % y |
Line 397: |
Line 333: |
| return ret | | return ret |
| end | | end |
− | | + | |
| --[[ | | --[[ |
| gcd | | gcd |
− | | + | |
| Calculates the greatest common divisor of multiple numbers | | Calculates the greatest common divisor of multiple numbers |
− | | + | |
| Usage: | | Usage: |
| {{#invoke:Math | gcd | value 1 | value 2 | value 3 | ... }} | | {{#invoke:Math | gcd | value 1 | value 2 | value 3 | ... }} |
| --]] | | --]] |
− | | + | |
| function wrap.gcd(args) | | function wrap.gcd(args) |
| return p._gcd(unpackNumberArgs(args)) | | return p._gcd(unpackNumberArgs(args)) |
| end | | end |
− | | + | |
| function p._gcd(...) | | function p._gcd(...) |
| local function findGcd(a, b) | | local function findGcd(a, b) |
Line 424: |
Line 360: |
| return oldr | | return oldr |
| end | | end |
− | local result, count = fold(findGcd, ...) | + | local result, count = applyFuncToArgs(findGcd, ...) |
| return result | | return result |
| end | | end |
− | | + | |
| --[[ | | --[[ |
| precision_format | | precision_format |
− | | + | |
− | Rounds a number to the specified precision and formats according to rules | + | Rounds a number to the specified precision and formats according to rules |
| originally used for {{template:Rnd}}. Output is a string. | | originally used for {{template:Rnd}}. Output is a string. |
− | | + | |
| Usage: | | Usage: |
| {{#invoke: Math | precision_format | number | precision }} | | {{#invoke: Math | precision_format | number | precision }} |
| ]] | | ]] |
− | | + | |
| function wrap.precision_format(args) | | function wrap.precision_format(args) |
| local value_string = args[1] or 0 | | local value_string = args[1] or 0 |
Line 443: |
Line 379: |
| return p._precision_format(value_string, precision) | | return p._precision_format(value_string, precision) |
| end | | end |
− | | + | |
| function p._precision_format(value_string, precision) | | function p._precision_format(value_string, precision) |
| -- For access to Mediawiki built-in formatter. | | -- For access to Mediawiki built-in formatter. |
| local lang = mw.getContentLanguage(); | | local lang = mw.getContentLanguage(); |
− | | + | |
| local value | | local value |
| value, value_string = p._cleanNumber(value_string) | | value, value_string = p._cleanNumber(value_string) |
| precision = p._cleanNumber(precision) | | precision = p._cleanNumber(precision) |
− | | + | |
| -- Check for non-numeric input | | -- Check for non-numeric input |
| if value == nil or precision == nil then | | if value == nil or precision == nil then |
| return err('invalid input when rounding') | | return err('invalid input when rounding') |
| end | | end |
− | | + | |
| local current_precision = p._precision(value) | | local current_precision = p._precision(value) |
| local order = p._order(value) | | local order = p._order(value) |
− | | + | |
| -- Due to round-off effects it is neccesary to limit the returned precision under | | -- Due to round-off effects it is neccesary to limit the returned precision under |
| -- some circumstances because the terminal digits will be inaccurately reported. | | -- some circumstances because the terminal digits will be inaccurately reported. |
| if order + precision >= 14 then | | if order + precision >= 14 then |
− | if order + p._precision(value_string) >= 14 then | + | orig_precision = p._precision(value_string) |
− | precision = 13 - order; | + | if order + orig_precision >= 14 then |
− | end | + | precision = 13 - order; |
| + | end |
| end | | end |
− | | + | |
| -- If rounding off, truncate extra digits | | -- If rounding off, truncate extra digits |
| if precision < current_precision then | | if precision < current_precision then |
| value = p._round(value, precision) | | value = p._round(value, precision) |
| current_precision = p._precision(value) | | current_precision = p._precision(value) |
− | end | + | end |
− | | + | |
| local formatted_num = lang:formatNum(math.abs(value)) | | local formatted_num = lang:formatNum(math.abs(value)) |
| local sign | | local sign |
− | | + | |
| -- Use proper unary minus sign rather than ASCII default | | -- Use proper unary minus sign rather than ASCII default |
| if value < 0 then | | if value < 0 then |
Line 482: |
Line 419: |
| else | | else |
| sign = '' | | sign = '' |
− | end | + | end |
− | | + | |
| -- Handle cases requiring scientific notation | | -- Handle cases requiring scientific notation |
| if string.find(formatted_num, 'E', 1, true) ~= nil or math.abs(order) >= 9 then | | if string.find(formatted_num, 'E', 1, true) ~= nil or math.abs(order) >= 9 then |
Line 491: |
Line 428: |
| formatted_num = lang:formatNum(math.abs(value)) | | formatted_num = lang:formatNum(math.abs(value)) |
| else | | else |
− | order = 0; | + | order = 0; |
| end | | end |
| formatted_num = sign .. formatted_num | | formatted_num = sign .. formatted_num |
− | | + | |
− | -- Pad with zeros, if needed | + | -- Pad with zeros, if needed |
| if current_precision < precision then | | if current_precision < precision then |
| local padding | | local padding |
Line 502: |
Line 439: |
| local zero_sep = lang:formatNum(1.1) | | local zero_sep = lang:formatNum(1.1) |
| formatted_num = formatted_num .. zero_sep:sub(2,2) | | formatted_num = formatted_num .. zero_sep:sub(2,2) |
− | | + | |
| padding = precision | | padding = precision |
| if padding > 20 then | | if padding > 20 then |
| padding = 20 | | padding = 20 |
| end | | end |
− | | + | |
| formatted_num = formatted_num .. string.rep('0', padding) | | formatted_num = formatted_num .. string.rep('0', padding) |
− | end | + | end |
− | else | + | else |
| padding = precision - current_precision | | padding = precision - current_precision |
| if padding > 20 then | | if padding > 20 then |
Line 518: |
Line 455: |
| end | | end |
| end | | end |
− | | + | |
| -- Add exponential notation, if necessary. | | -- Add exponential notation, if necessary. |
| if order ~= 0 then | | if order ~= 0 then |
Line 526: |
Line 463: |
| else | | else |
| order = lang:formatNum(order) | | order = lang:formatNum(order) |
− | end | + | end |
− | | + | |
| formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>' | | formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>' |
| end | | end |
− | | + | |
| return formatted_num | | return formatted_num |
| end | | end |
− | | + | |
| --[[ | | --[[ |
− | Helper function that interprets the input numerically. If the | + | Helper function that interprets the input numerically. If the |
| input does not appear to be a number, attempts evaluating it as | | input does not appear to be a number, attempts evaluating it as |
| a parser functions expression. | | a parser functions expression. |
| ]] | | ]] |
− | | + | |
| function p._cleanNumber(number_string) | | function p._cleanNumber(number_string) |
| if type(number_string) == 'number' then | | if type(number_string) == 'number' then |
Line 548: |
Line 485: |
| return nil, nil; | | return nil, nil; |
| end | | end |
− | | + | |
| -- Attempt basic conversion | | -- Attempt basic conversion |
| local number = tonumber(number_string) | | local number = tonumber(number_string) |
− | | + | |
| -- If failed, attempt to evaluate input as an expression | | -- If failed, attempt to evaluate input as an expression |
| if number == nil then | | if number == nil then |
− | local success, result = pcall(mw.ext.ParserFunctions.expr, number_string) | + | local frame = mw.getCurrentFrame() |
− | if success then | + | local attempt = frame:callParserFunction('#expr', number_string) |
− | number = tonumber(result) | + | attempt = tonumber(attempt) |
| + | if attempt ~= nil then |
| + | number = attempt |
| number_string = tostring(number) | | number_string = tostring(number) |
| else | | else |
Line 570: |
Line 509: |
| end | | end |
| end | | end |
− | | + | |
| return number, number_string | | return number, number_string |
| end | | end |
− | | + | |
| --[[ | | --[[ |
| Wrapper function that does basic argument processing. This ensures that all functions from #invoke can use either the current | | Wrapper function that does basic argument processing. This ensures that all functions from #invoke can use either the current |
| frame or the parent frame, and it also trims whitespace for all arguments and removes blank arguments. | | frame or the parent frame, and it also trims whitespace for all arguments and removes blank arguments. |
| ]] | | ]] |
− | | + | |
− | local mt = { __index = function(t, k) | + | local function makeWrapper(funcName) |
− | return function(frame) | + | return function (frame) |
− | if not getArgs then | + | local args = getArgs(frame) -- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed. |
− | getArgs = require('Module:Arguments').getArgs
| + | return wrap[funcName](args) |
− | end
| |
− | return wrap[k](getArgs(frame)) -- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed.
| |
| end | | end |
− | end } | + | end |
− | | + | |
− | return setmetatable(p, mt)
| + | for funcName in pairs(wrap) do |
| + | p[funcName] = makeWrapper(funcName) |
| + | end |
| + | |
| + | return p |