Module:Convert: Difference between revisions

update from sandbox per Template talk:Convert#Module v2 soon
m (Protected Module:Convert: High-risk Lua module ([Edit=Allow only template editors and admins] (indefinite) [Move=Allow only template editors and admins] (indefinite)))
(update from sandbox per Template talk:Convert#Module v2 soon)
Line 16:
local numdot, numsep -- each must be a single byte for simple regex search/replace
local default_exceptions, link_exceptions, all_units
local text_code
local SIprefixes, all_categories, all_messages, customary_units, disp_joins
local varname -- can be a code to use variable names that depend on value
local en_option_name, en_option_value, eng_scales, range_aliases, range_types
local from_en_table -- to translate an output string of en digits to local language
local to_en_table -- to translate an input string of digits in local language to en
-- Use translation_table in convert/text to change the following.
local group_method = 3 -- code for how many digits are in a group
local per_word = 'per' -- for units like "miles per gallon"
local plural_suffix = 's' -- only other useful value is probably '' to disable plural unit names
local from_en_table -- to translate an output string of en digits to local language
local to_en_table -- to translate an input string of digits in local language to en
 
-- All units should be defined in the data module. However, to cater for quick changes
Line 84 ⟶ 85:
-- Scribunto sets the global variable 'mw'.
-- A testing program can set the global variable 'is_test_run'.
local data_module, text_module, data_code, text_code
if is_test_run then
local langcode = mw.language.getContentLanguage().code
Line 103 ⟶ 104:
link_exceptions = data_code.link_exceptions
all_units = data_code.all_units
SIprefixes = text_code.SIprefixes
all_categories = text_code.all_categories
all_messages = text_code.all_messages
customary_units = text_code.customary_units
disp_joins = text_code.disp_joins
en_option_name = text_code.en_option_name
en_option_value = text_code.en_option_value
eng_scales = text_code.eng_scales
range_aliases = text_code.range_aliases
range_types = text_code.range_types
local translation = text_code.translation_table
if translation then
Line 124 ⟶ 115:
plural_suffix = translation.plural_suffix
end
varname = translation.varname
from_en_table = translation.from_en
local use_workaround = true
Line 152 ⟶ 144:
end,
}
end
 
local function divide(numerator, denominator)
-- Return integers quotient, remainder resulting from dividing the two
-- given numbers, which should be unsigned integers.
local quotient, remainder = floor(numerator / denominator), numerator % denominator
if not (0 <= remainder and remainder < denominator) then
-- Floating point limits may need this, as in {{convert|160.02|Ym|ydftin}}.
remainder = 0
end
return quotient, remainder
end
 
Line 177 ⟶ 180:
-- Return cat if it is wanted in current namespace, otherwise return nil.
-- This is so tracking categories only include pages that need correction.
-- Default wanted namespaces are 0 (article) and 10 (template).
local title = mw.title.getCurrentTitle()
if title then
local nsdefault = '0' -- default namespace: '0' = article; '0,10' = article and template
local namespace = title.namespace
for _, v in ipairs(split(config.nscat or '0,10'nsdefault, ',')) do
if namespace == tonumber(v) then
return cat
Line 197 ⟶ 200:
-- mcode[3] = 'parm2' (string to replace second %s if any in message)
-- mcode[4] = 'parm3' (string to replace third %s if any in message)
local msg = text_code.all_messages[mcode[1]]
local nowiki = mw.text.nowiki
if msg then
local titleparts = format(msg[1] or 'Missing message',{}
mcode[2] or '?',
mcode[3] or '?',
mcode[4] or '?')
local text = msg[2] or 'Missing message'
local cat = wanted_category(all_categories[msg[3]]) or ''
local anchor = msg[4] or ''
local fmt = all_messages['cvt_format'] or 'convert: bug'
local regex, replace = msg.regex, msg.replace
iffor regexi and= replace1, then3 do
local limit = 40
title = title:gsub(regex, replace)
local s = mcode[i + 1]
if s then
if regex and replace then
s = s:gsub(regex, replace)
limit = nil -- allow long "should be" messages
end
-- Escape user input so it does not break the message.
-- To avoid reference tags (like {{convert|1<ref>xyz</ref>|m}}) or other tags
-- breaking the mouseover title, any strip marker starting with char(127) is
-- replaced with escaped '<ref>...</ref>' or '...' (text not needing i18n).
local append = ''
local pos = s:find(string.char(127), 1, true)
if pos then
if s:find('-ref-', 1, true) then
append = '&lt;ref&gt;...&lt;/ref&gt;'
else
append = '...'
end
s = s:sub(1, pos - 1)
end
if limit and ulen(s) > limit then
s = usub(s, 1, limit)
if append == '' then
append = '...'
end
end
s = nowiki(s) .. append
else
s = '?'
end
parts[i] = s
end
local title = format(msg[1] or 'Missing message', parts[1], parts[2], parts[3])
local text = msg[2] or 'Missing message'
local cat = wanted_category(text_code.all_categories[msg[3]]) or ''
local anchor = msg[4] or ''
local fmt = text_code.all_messages['cvt_format'] or 'convert: bug'
title = title:gsub('"', '&quot;')
return format(fmt, anchor, title, text, cat)
Line 229 ⟶ 262:
end
 
local function spell_number(parms, inout, number, numerator, denominator)
-- Return result of spelling (number, numerator, denominator), or
-- return nil if spelling is not available or not supported for given text.
Line 249 ⟶ 282:
end
end
local case = parms.opt_spell_upper
if parms.spell_upper == inout then
parms.opt_spell_upper = nil -- only uppercase first number in a multiple unit
case = true
parms.spell_upper = nil -- only uppercase first word in a multiple unit
end
local sp = not parms.opt_sp_us
local adj = parms.opt_adjectival
Line 337 ⟶ 373:
-- In the latter case, and before use, fields symbol, name1, name1_us
-- must be set from _symbol, _name1, _name1_us respectively.
-- Warning: The boolean value 'false' is returned for any missing field
-- so __index is not called twice for the same field in a given unit.
__index = function (self, key)
local value
Line 358 ⟶ 396:
elseif key == 'link' then
value = self.name1
elseif key == 'builtin' then
value = false
else
returnvalue nil= false
end
rawset(self, key, value)
Line 401 ⟶ 437:
elseif key == 'link' then
value = self.name1
else
elseif key == 'builtin' then
value = false
else
return nil
end
rawset(self, key, value)
Line 431 ⟶ 465:
local unit1, unit2 = per[1], per[2]
value = (unit1 and unit1.scale or 1) * self.scalemultiplier / unit2.scale
else
elseif key == 'builtin' then
value = false
else
return nil
end
rawset(self, key, value)
Line 504 ⟶ 536:
local prefix
for i, v in ipairs(per) do
if i == 1 and (text_code.currency[v == '$' or v == '£')] then
prefix = v
else
Line 526 ⟶ 558:
if combo then
local multiple = t.multiple
if what == 'no_combination' or (what == 'only_multiple' and not multiple == nil) then
return false, { 'cvt_bad_unit', unitcode }
end
Line 549 ⟶ 581:
return true, setmetatable(result, unit_mt)
end
local SIprefixes = text_code.SIprefixes
for plen = SIprefixes[1] or 2, 1, -1 do
-- Look for an SI prefix; should never occur with an alias.
Line 578 ⟶ 611:
-- and not if the unit has an offset or is a built-in.
-- Only en digits are accepted.
local exponent, baseunithas_plus = unitcode:matchfind('^e(%d+)(.*)', 1, true)
if exponentnot has_plus then
local exponent, baseunit = unitcode:match('^e(%d+)(.*)')
local engscale = eng_scales[exponent]
if engscaleexponent then
local engscale = text_code.eng_scales[exponent]
local success, result = lookup(baseunit, opt_sp_us, 'no_combination', utable, fails, depth)
if not successengscale then return false, result end
local success, result = lookup(baseunit, opt_sp_us, 'no_combination', utable, fails, depth)
if not (result.offset or result.builtin or result.engscale) then
if not success then return false, result end
result.defkey = unitcode -- key to lookup default exception
if not (result.engscaleoffset =or result.builtin or result.engscale) then
result.scaledefkey = result.scaleunitcode -- key *to 10lookup ^default tonumber(exponent)exception
result.engscale = engscale
return true, result
result.scale = result.scale * 10 ^ tonumber(exponent)
return true, result
end
end
end
Line 598 ⟶ 634:
local err_is_fatal
local combo = collection()
if unitcode:find('+', 1, true)has_plus then
err_is_fatal = true
for item in (unitcode .. '+'):gmatch('%s*(.-)%s*%+') do
Line 618 ⟶ 654:
local cvt = result.combination
for i, v in ipairs(combo) do
local success, t = lookup(v, opt_sp_us, 'no_combinationonly_multiple', utable, fails, depth)
if not success then return false, t end
if i == 1 then
Line 843 ⟶ 879:
 
local function with_separator(parms, text)
-- Input text is a number in en digits and withoptional '.' decimal mark.
-- Return an equivalent of text, formatted for display:
-- with a custom decimal mark instead of '.', if wanted
Line 911 ⟶ 947:
return string.rep('0', sigfig), 1
end
local exp, fracfracpart = math.modf(log10(value))
if fracfracpart >= 0 then
fracfracpart = fracfracpart - 1
exp = exp + 1
end
local digits = format('%.0f', 10^(fracfracpart + sigfig))
if #digits > sigfig then
-- Overflow (for sigfig=3: like 0.9999 rounding to "1000"; need "100").
Line 924 ⟶ 960:
assert(#digits == sigfig, 'Bug: rounded number has wrong length')
return digits, exp
end
 
-- Fraction output format.
local fracfmt = {
{ -- Like {{frac}} (fraction slash).
-- 1/2 : sign, numerator, denominator
-- 1+2/3 : signed_wholenumber, numerator, denominator
'<span class="frac nowrap">%s<sup>%s</sup>&frasl;<sub>%s</sub></span>',
'<span class="frac nowrap">%s<span class="visualhide">&nbsp;</span><sup>%s</sup>&frasl;<sub>%s</sub></span>',
},
{ -- Like {{sfrac}} (fraction horizontal bar).
-- 1//2 : sign, numerator, denominator (sign should probably be before the fraction, but then it can wrap, and html is already too long)
-- 1+2//3 : signed_wholenumber, numerator, denominator
'<span class="sfrac nowrap" style="display:inline-block; vertical-align:-0.5em; font-size:85%%; text-align:center;"><span style="display:block; line-height:1em; padding:0 0.1em;">%s%s</span><span class="visualhide">/</span><span style="display:block; line-height:1em; padding:0 0.1em; border-top:1px solid;">%s</span></span>',
'<span class="sfrac nowrap">%s<span class="visualhide">&nbsp;</span><span style="display:inline-block; vertical-align:-0.5em; font-size:85%%; text-align:center;"><span style="display:block; line-height:1em; padding:0 0.1em;">%s</span><span class="visualhide">/</span><span style="display:block; line-height:1em; padding:0 0.1em; border-top:1px solid;">%s</span></span></span>',
},
}
 
local function format_fraction(parms, inout, negative, wholestr, numstr, denstr, do_spell, style)
-- Return wikitext for a fraction, possibly spelled.
-- Inputs use en digits and have no sign; output uses digits in local language.
local wikitext
if not style then
style = parms.opt_fraction_horizontal and 2 or 1
end
if wholestr == '' then
wholestr = nil
end
if wholestr then
local decorated = with_separator(parms, wholestr)
if negative then
decorated = MINUS .. decorated
end
local fmt = fracfmt[style][2]
wikitext = format(fmt, decorated, from_en(numstr), from_en(denstr))
else
local sign = negative and MINUS or ''
wikitext = format(fracfmt[style][1], sign, from_en(numstr), from_en(denstr))
end
if do_spell then
if negative then
if wholestr then
wholestr = '-' .. wholestr
else
numstr = '-' .. numstr
end
end
wikitext = spell_number(parms, inout, wholestr, numstr, denstr) or wikitext
end
return wikitext
end
 
local function format_number(parms, show, exponent, isnegative)
-- Parameter show is a numberstring inor ena digitstable andcontaining with '.' decimal markstrings.
-- Each string is a formatted number in en digits and optional '.' decimal mark.
-- A table represents a fraction: integer, numerator, denominator;
-- if a table is given, exponent must be nil.
-- Return t where t is a table with fields:
-- show = wikitext formatted to display implied value
Line 952 ⟶ 1,041:
-- The formatted result:
-- * Is for an output value and is spelled if wanted and possible.
-- * Includes a Unicode minus if isnegative and not spelled.
-- * Uses a custom decimal mark, if wanted.
-- * Has digits grouped where necessary, if wanted.
Line 961 ⟶ 1,050:
local sign = isnegative and MINUS or ''
local maxlen = maxsigfig
local tfrac
if exponent == nil then
if type(show) == 'table' then
local integer, dot, fraction = show:match('^(%d*)(%.?)(.*)')
tfrac = show
show = tfrac.wholestr
assert(exponent == nil, 'Bug: exponent given with fraction')
end
if not tfrac and not exponent then
local integer, dot, decimals = show:match('^(%d*)(%.?)(.*)')
if #integer >= 10 then
show = integer .. fractiondecimals
exponent = #integer
elseif integer == '0' or integer == '' then
local zeros, figs = fractiondecimals:match('^(0*)([^0]?.*)')
if #figs == 0 then
if #zeros > maxlen then
Line 1,007 ⟶ 1,102:
end
end
local formatted_show
if isnegative and show:match('^0.?0*$') then
if tfrac then
sign = '' -- don't show minus if result is negative but rounds to zero
show = tostring(tfrac.value) -- to set clean in returned table
end
formatted_show = format_fraction(parms, 'out', isnegative, tfrac.wholestr, tfrac.numstr, tfrac.denstr, parms.opt_spell_out)
local formatted_show = sign .. with_separator(parms, show)
else
if parms.opt_spell_out then
if isnegative and show:match('^0.?0*$') then
formatted_show = spell_number(parms, sign .. show) or formatted_show
sign = '' -- don't show minus if result is negative but rounds to zero
end
formatted_show = sign .. with_separator(parms, show)
if parms.opt_spell_out then
formatted_show = spell_number(parms, 'out', sign .. show) or formatted_show
end
end
return {
Line 1,018 ⟶ 1,119:
sign = sign,
show = formatted_show,
is_scientific = false, -- to avoid calling __index
}
end
 
-- Fraction output format.
-- 2013-07-20 Trying new styles proposed at [[Template talk:Convert]].
local fracfmt = {
{ -- Like {{frac}} (fraction slash).
-- 1/2 : sign, numerator, denominator
-- 1+2/3 : signed_wholenumber, numerator, denominator
'<span class="frac nowrap">%s<sup>%s</sup>⁄<sub>%s</sub></span>',
'<span class="frac nowrap">%s<sup> %s</sup>⁄<sub>%s</sub></span>',
},
{ -- Like {{sfrac}} (fraction horizontal bar).
-- 1//2 : sign, numerator, denominator (sign should probably be before the fraction, but then it can wrap, and html is already too long)
-- 1+2//3 : signed_wholenumber, numerator, denominator
'<span class="sfrac nowrap" style="display:inline-block; vertical-align:-0.5em; font-size:85%%; text-align:center;"><span style="display:block; line-height:1em; padding:0 0.1em;">%s%s</span><span style="display:none;">/</span><span style="display:block; line-height:1em; padding:0 0.1em; border-top:1px solid;">%s</span></span>',
'<span class="sfrac nowrap">%s<span style="display:none;">&nbsp;</span><span style="display:inline-block; vertical-align:-0.5em; font-size:85%%; text-align:center;"><span style="display:block; line-height:1em; padding:0 0.1em;">%s</span><span style="display:none;">/</span><span style="display:block; line-height:1em; padding:0 0.1em; border-top:1px solid;">%s</span></span></span>',
},
{ -- Like old {{convert}} template.
-- 1///2 : sign, numerator, denominator
-- 1+2///3: signed_wholenumber, sign, numerator, denominator
'<span style="white-space:nowrap">%s<sup>%s</sup>⁄<sub>%s</sub></span>',
'<span class="frac nowrap">%s<s style="display:none">%s</s><sup>%s</sup>⁄<sub>%s</sub></span>',
},
}
 
local function extract_fraction(parms, text, negative)
Line 1,071 ⟶ 1,150:
-- Wherever an integer appears above, numbers like 1.25 or 12.5e-3
-- (which may be negative) are also accepted (like old template).
-- TemplateOld template interprets '1.23e+2+12/24' as '123(12/24)' = 123.5!
local numstr, whole, value, altvalue
local lhs, slash, denstr = text:match('^%s*([^/]-)%s*(/+)%s*(.-)%s*$')
Line 1,111 ⟶ 1,190:
numstr = use_minus(numstr)
denstr = use_minus(denstr)
local style = #slash -- kludge: 1, 2, or 32 slashes can be used to select style
if style > 32 then style = 32 end
local wikitext = format_fraction(parms, 'in', negative, wholestr, numstr, denstr, do_spell, style)
local wikitext
if wholestr then
if negative then
wholestr = change_sign(wholestr)
end
local fmt = fracfmt[style][2]
if style < 3 then
wikitext = format(fmt, use_minus(from_en(wholestr)), from_en(numstr), from_en(denstr))
else
local sign = negative and MINUS or '+'
wikitext = format(fmt, use_minus(from_en(wholestr)), sign, from_en(numstr), from_en(denstr))
end
else
local sign = negative and MINUS or ''
wikitext = format(fracfmt[style][1], sign, from_en(numstr), from_en(denstr))
end
if do_spell then
local numsign = (wholestr or not negative) and '' or '-'
wikitext = spell_number(parms, wholestr, numsign .. numstr, denstr) or wikitext
end
return value, altvalue, wikitext, do_spell, denominator
end
Line 1,208 ⟶ 1,268:
end
if value <= 1 then
singular = true -- for example, "¹/₂½ mile" or "one half mile" (singular unit)
end
end
Line 1,219 ⟶ 1,279:
local precision = parms.input_precision
if precision and 0 <= precision and precision <= 8 then
value = value + 2e-14 -- fudge for some common cases of bad rounding
local fmt = '%.' .. format('%d', precision) .. 'f'
show = fmt:format(value + 2e-14) -- fudge for some common cases of bad rounding
else
show = clean
Line 1,227 ⟶ 1,286:
show = propersign .. with_separator(parms, show)
if parms.opt_spell_in then
show = spell_number(parms, 'in', propersign .. clean) or show
end
end
local altvalue = altvalue or value
if isnegative and (value ~= 0) then
value = -value
altvalue = -altvalue
end
return true, {
value = value,
altvalue = altvalue or value,
singular = singular,
clean = clean,
Line 1,254 ⟶ 1,315:
local number = tonumber(to_en(text))
if number then
local integer, fractionfracpart = math.modf(number)
return number, (fractionfracpart == 0)
end
end
end
 
local function gcd(a, b)
-- Return the greatest common denominator for the given values,
-- which are known to be positive integers.
if a > b then
a, b = b, a
end
if a <= 0 then
return b
end
local r = b % a
if r <= 0 then
return a
end
if r == 1 then
return 1
end
return gcd(r, a)
end
 
local function fraction_table(value, denominator)
-- Return value as a string or a table:
-- * If result is a string, there is no fraction, and the result
-- is value formatted as a string of en digits.
-- * If result is a table, it represents a fraction with named fields:
-- wholestr, numstr, denstr (strings of en digits for integer, numerator, denominator).
-- The result is rounded to the nearest multiple of (1/denominator).
-- If the multiple is zero, no fraction is included.
-- No fraction is included if value is very large as the fraction would
-- be unhelpful, particularly if scientific notation is required.
-- Input value is a non-negative number.
-- Input denominator is a positive integer for the desired fraction.
if value <= 0 then
return '0'
end
if denominator <= 0 or value > 1e8 then
return format('%.2f', value)
end
local integer, decimals = math.modf(value)
local numerator = floor((decimals * denominator) +
0.5 + 2e-14) -- add fudge for some common cases of bad rounding
if numerator >= denominator then
integer = integer + 1
numerator = 0
end
local wholestr = tostring(integer)
if numerator > 0 then
local div = gcd(numerator, denominator)
if div > 1 then
numerator = numerator / div
denominator = denominator / div
end
return {
wholestr = (integer > 0) and wholestr or '',
numstr = tostring(numerator),
denstr = tostring(denominator),
value = value,
}
end
return wholestr
end
 
Line 1,424 ⟶ 1,546:
end
for loc_name, loc_value in pairs(kv_pairs) do
local en_name = text_code.en_option_name[loc_name]
if en_name then
local en_value
if en_name == 'frac' or en_name == 'sigfig' then
if loc_value == '' then
add_warning(parms, 2, 'cvt_empty_option', loc_name)
else
local minimum
local number, is_integer = get_number(loc_value)
if numberen_name and== is_integer and number > 0'frac' then
minimum = 2
if number and number < 0 then
parms.opt_fraction_horizontal = true
number = -number
end
else
minimum = 1
end
if number and is_integer and number >= minimum then
en_value = number
else
add_warning(parms, 1, (en_name == 'frac' and 'cvt_bad_frac' or 'cvt_bad_sigfig'), loc_value)
end
end
else
en_value = text_code.en_option_value[en_name][loc_value]
if en_value == nil then
if loc_value == '' then
Line 1,465 ⟶ 1,597:
if parms.adj then
if parms.adj:sub(1, 2) == 'ri' then
-- It is known that adj is 'ri1riN' orwhere 'ri2'N oris 'ri3'a single digit, so precision is valid.
-- Only a single en digitsdigit areis accepted.
parms.input_precision = tonumber(parms.adj:sub(-1))
parms.adj = nil
end
end
local cfg_abbr = config.abbr
if cfg_abbr then
if cfg_abbr == 'on always' then
parms.abbr = 'on'
elseif cfg_abbr == 'on default' then
if parms.abbr == nil then
parms.abbr = 'on'
end
end
end
if parms.abbr then
parms.abbr_org = parms.abbr -- original abbr that was set, before any flip
elseif parms.opt_hand_hh then
parms.abbr_org = 'on'
parms.abbr = 'on'
else
parms.abbr = 'out' -- default is to abbreviate output only (use symbol, not name)
Line 1,487 ⟶ 1,632:
swap_in_out('abbr')
swap_in_out('lk')
if parms.opt_spell_in and not parms.opt_spell_out then
-- For simplicity, and because it does not appear to be needed,
-- user cannot set an option to spell the output only.
parms.opt_spell_in = nil
parms.opt_spell_out = true
end
end
if parms.opt_spell_upper then
parms.spell_upper = parms.opt_flip and 'out' or 'in'
end
if parms.opt_table or parms.opt_tablecen then
Line 1,512 ⟶ 1,660:
-- i = index to next entry in parms after those processed here
-- or return false, t where t is an error message table.
local ranges = text_code.ranges
local valinfo = collection() -- numbered table of input values
local range = collection() -- numbered table of range items (having, for example, 2 range items requires 3 input values)
Line 1,520 ⟶ 1,669:
parms.opt_nocomma = true
had_nocomma = true
end
local function extractor(i)
-- If the parameter is not a value, try unpacking it as a range ("1-23" for "1 to 23").
-- However, "-1-2/3" is a negative fraction (-1⅔), so it must be extracted first.
-- Unpacked items are inserted into the parms table.
local valstr = strip(parms[i]) -- trim so any '-' as a negative sign will be at start
local success, result = extract_number(parms, valstr, i > 1)
if not success and valstr and i < 20 then -- check i to limit abuse
for _, sep in ipairs(ranges.words) do
local start, stop = valstr:find(sep, 2, true) -- start at 2 to skip any negative sign for range '-'
if start then
parms[i] = valstr:sub(stop + 1)
table.insert(parms, i, sep)
table.insert(parms, i, valstr:sub(1, start - 1))
return extractor(i) -- this allows combinations like "1 x 2 to 3 x 4"
end
end
end
return success, result
end
local i = 1
local is_change
while true do
local success, info = extract_numberextractor(parms, parms[i], i > 1) -- need to set parms.opt_nocomma before calling this
if not success then return false, info end
i = i + 1
if is_change then
info.is_change = true -- value is after "±" and so is a change (significant for range like {{convert|5|±|5|°C}})
is_change = nil
end
valinfo:add(info)
local next = strip(parms[i])
local range_item = range_typesranges.types[next] or range_typesranges.types[range_aliasesranges.aliases[next]]
if not range_item then
break
Line 1,534 ⟶ 1,707:
i = i + 1
range:add(range_item)
parms.is_range_xif = (type(range_item) == 'table') and range_item.is_range_x or nilthen
parms.is_range_x = range_item.is_range_x
is_change = range_item.is_range_change
end
end
if range.n > 0 then
Line 1,545 ⟶ 1,721:
end
return true, valinfo, i
end
 
local function simple_get_values(parms)
-- If input is like "{{convert|valid_value|valid_unit|...}}",
-- return true, v, 3, in_unit, in_unit_table
-- (as for get_values(), but with a unit name and table for a valid unit;
-- 3 = index in parms of whatever follows valid_unit, if anything).
-- The valid_value is not negative and does not use a fraction, and
-- no options requiring further processing of the input are used.
-- Otherwise, return nothing and caller will reparse the input.
-- Testing shows this function is successful for 96% of converts in articles,
-- and that on average it speeds up converts by 8%.
if parms.input_precision or parms.opt_spell_in then return end
local clean = to_en(strip(parms[1] or ''))
if #clean > 10 or not clean:match('^[0-9.]+$') then return end
local value = tonumber(clean)
if not value then return end
local info = {
value = value,
altvalue = value,
singular = (value == 1),
clean = clean,
show = with_separator(parms, clean),
}
local in_unit = strip(parms[2])
local success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination')
if not success then return end
return true, { info }, 3, in_unit, in_unit_table
end
 
Line 1,570 ⟶ 1,774:
local success, msg = translate_parms(parms, kv_pairs)
if not success then return false, msg end
local success, valinfo, i, in_unit, in_unit_table = get_valuessimple_get_values(parms)
if not success then return false, valinfo end
local in_unit = strip(parms[i])
i = i + 1
local success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination')
if not success then
success, valinfo, i = get_values(parms)
if in_unit == nil then
if not success then return false, valinfo end
in_unit = ''
in_unit = strip(parms[i])
i = i + 1
success, in_unit_table = lookup(in_unit, parms.opt_sp_us, 'no_combination')
if not success then
if in_unit == nil then
in_unit = ''
end
if parms.opt_ignore_error then -- display given unit code with no error (for use with {{val}})
in_unit_table = nil
end
in_unit_table = setmetatable({ symbol = in_unit, name2 = in_unit, utype = "length", scale = 1, bad_mcode = in_unit_table, default = "m" }, unit_mt)
end
in_unit_table = setmetatable({ symbol = in_unit, name2 = in_unit, utype = "length", scale = 1, bad_mcode = in_unit_table }, unit_mt)
end
if parms.test == 'msg' then
Line 1,685 ⟶ 1,895:
end
 
local function default_precisionrecord_default_precision(invalue, inclean, denominator, outvalue, in_currentparms, out_current, extraprecision)
-- If necessary, adjust parameters and return a possibly adjusted precision.
-- When converting a range of values where a default precision is required,
-- that default is calculated for each value because the result sometimes
-- depends on the precise input and output values. This function may cause
-- the entire convert process to be repeated in order to ensure that the
-- same default precision is used for each individual convert.
-- If that were not done, a range like 1000 to 1000.4 may give poor results
-- because the first output could be heavily rounded, while the second is not.
-- For range 1000.4 to 1000, this function can give the second convert the
-- same default precision that was used for the first.
if not parms.opt_round_each then
local maxdef = out_current.max_default_precision
if maxdef then
if maxdef < precision then
parms.do_convert_again = true
out_current.max_default_precision = precision
else
precision = out_current.max_default_precision
end
else
out_current.max_default_precision = precision
end
end
return precision
end
 
local function default_precision(parms, invalue, inclean, denominator, outvalue, in_current, out_current, extra)
-- Return a default value for precision (an integer like 2, 0, -2).
-- If denominator is not nil, it is the value of the denominator in inclean.
Line 1,706 ⟶ 1,943:
-- Count digits after decimal mark, handling cases like '12.345e6'.
local exponent
local integer, dot, fractiondecimals, expstr = inclean:match('^(%d*)(%.?)(%d*)(.*)')
local e = expstr:sub(1, 1)
if e == 'e' or e == 'E' then
Line 1,714 ⟶ 1,951:
prec = subunit_ignore_trailing_zero and 0 or -integer:match('0*$'):len()
else
prec = #fractiondecimals
end
if exponent then
Line 1,737 ⟶ 1,974:
-- We are never called with a negative outvalue, but it might be zero.
-- This is special-cased to avoid calculation exceptions.
return record_default_precision(parms, out_current, 0)
return 0
end
if out_current.exception == 'integer_more_precision' and floor(invalue) == invalue then
Line 1,763 ⟶ 2,000:
minprec = extra.minprec or minprec
end
return record_default_precision(parms, out_current, math.max(floor(prec + adjust), minprec))
end
 
local function convert(parms, invalue, incleaninfo, in_current, out_current)
-- Convert given input value from one unit to another.
-- Return output_value (a number) if a simple convert, or
Line 1,777 ⟶ 2,014:
return invalue * (inscale / outscale) -- minimize overhead for most common case
end
if in_current.invert or out_current.invert then
-- Inverted units, such as inverse length, inverse time, or
-- Fuel efficiency (there are no built-ins for this type of unit).
-- fuel efficiency. Built-in units do not have invert set.
if in_current.invert * out_current.invert < 0 then
if (in_current.invert or 1) * (out_current.invert or 1) < 0 then
return 1 / (invalue * inscale * outscale)
end
Line 1,785 ⟶ 2,023:
elseif in_current.offset then
-- Temperature (there are no built-ins for this type of unit).
if info.is_change then
return invalue * (inscale / outscale)
end
return (invalue - in_current.offset) * (inscale / outscale) + out_current.offset
else
Line 1,813 ⟶ 2,054:
elseif in_builtin == 'hand' then
-- 1 hand = 4 inches; 1.2 hands = 6 inches.
-- FractionsDecimals of a hand are only defined for the first digit, and
-- the first fractional digit should be a number of inches (1, 2 or 3).
-- However, this code interprets the entire fractionfractional part as the number
-- of inches / 10 (so 1.75 inches would be 0.175 hands).
-- A value like 12.3 hands is exactly 12*4 + 3 inches; base default precision on that.
local integer, fractionfracpart = math.modf(invalue)
local outvalueinch_value = (4 * integer + 2.510 * fraction)fracpart * (inscale-- equivalent number /of outscale)inches
local factor = inscale / outscale
local inch_value = 4 * integer + 10 * fraction -- equivalent number of inches
if factor == 4 then
local fracstr = inclean:match('%.(.*)') or ''
-- Am converting to inches: show exact result, and use "inches" not "in" by default.
if parms.abbr_org == nil then
out_current.usename = true
end
local show = format('%g', abs(inch_value)) -- show and clean are unsigned
if not show:find('e', 1, true) then
return true, {
invalue = inch_value,
outvalue = inch_value,
clean = show,
show = show,
}
end
end
local outvalue = (integer + 2.5 * fracpart) * factor
local fracstr = info.clean:match('%.(.*)') or ''
local fmt
if fracstr == '' then
Line 1,830 ⟶ 2,087:
return true, {
invalue = inch_value,
incleanclean = format(fmt, inch_value),
outvalue = outvalue,
minprec = 0,
Line 1,838 ⟶ 2,095:
return false, { 'cvt_bug_convert' } -- should never occur
end
 
local cvt_to_hand
 
local function cvtround(parms, info, in_current, out_current)
Line 1,850 ⟶ 2,109:
-- Input info.clean uses en digits (it has been translated, if necessary).
-- Output show uses en or non-en digits as appropriate, or can be spelled.
local invalue, inclean
if info then
invalue, inclean = info.value, info.clean
if in_current.builtin == 'hand' then
invalue = info.altvalue
Line 1,861 ⟶ 2,120:
end
if out_current.builtin == 'hand' then
return cvt_to_hand(parms, info, in_current, out_current)
-- Convert to hands, then convert the fractional part to inches.
-- Code is not correct when output is spelled, and it ignores any requested
-- precision if the output uses scientific notation (very large, or very
-- small). Not worth more complexity as these cases should be very rare.
if parms.abbr_org == nil then
out_current.usename = true -- default is to show name not symbol
end
local dummy_unit_table = { scale = out_current.scale }
local success, outinfo = cvtround(parms, info, in_current, dummy_unit_table)
if not success then return false, outinfo end
local fmt
if outinfo.is_scientific then
fmt = '%.1f'
else
local fraction = (outinfo.show):match('[' .. numdot .. '](.*)') or '' -- outinfo.show is in local language
if fraction == '' then
if not outinfo.use_default_precision then
return true, outinfo
end
fmt = '%.0f'
else
fmt = '%.' .. format('%d', ulen(fraction) - 1) .. 'f'
end
end
local hands, inches = math.modf(outinfo.raw_absvalue)
inches = format(fmt, inches * 4)
if inches:sub(1, 1) == '4' then
hands = hands + 1
inches = '0' .. inches:sub(2)
if tonumber(inches) == 0 then
inches = '0'
end
end
if inches:sub(2, 2) == '.' then
inches = inches:sub(1, 1) .. inches:sub(3)
end
outinfo.show = outinfo.sign .. with_separator(parms, format('%d', hands)) .. numdot .. from_en(inches)
return true, outinfo
end
local outvalue, extra = convert(parms, invalue, incleaninfo, in_current, out_current)
if extra then
if not outvalue then return false, extra end
invalue = extra.invalue or invalue
inclean = extra.inclean or inclean
outvalue = extra.outvalue
end
Line 1,915 ⟶ 2,136:
outvalue = -outvalue
end
local successnumerator, use_default_precisionprecision, success, show, exponent
local precisiondenominator = parmsout_current.precisionfrac
if not precisiondenominator then
show = fraction_table(outvalue, denominator)
local sigfig = parms.sigfig
else
if sigfig then
precision = parms.precision
show, exponent = make_sigfig(outvalue, sigfig)
elseifif parms.opt_round5not precision then
local sigfig = parms.sigfig
show = format('%.0f', floor((outvalue / 5) + 0.5) * 5)
if sigfig then
else
show, exponent = make_sigfig(outvalue, sigfig)
use_default_precision = true
elseif parms.opt_round5 or parms.opt_round25 then
precision = default_precision(invalue, inclean, info.denominator, outvalue, in_current, out_current, extra)
local n = parms.opt_round5 and 5 or 25
show = format('%.0f', floor((outvalue / n) + 0.5) * n)
else
local inclean = info.clean
if extra then
inclean = extra.clean or inclean
show = extra.show
end
if not show then
precision = default_precision(parms, invalue, inclean, info.denominator, outvalue, in_current, out_current, extra)
end
end
end
end
if precision then
if precision >= 0 then
local fudge
if precision <= 8 then
-- Add a fudge to handle common cases of bad rounding due to inability
Line 1,936 ⟶ 2,170:
-- Old template uses #expr round, which invokes PHP round().
-- LATER: Investigate how PHP round() works.
outvaluefudge = outvalue + 2e-14
else
fudge = 0
end
local fmt = '%.' .. format('%d', precision) .. 'f'
local success
success, show = pcall(format, fmt, outvalue + fudge)
if not success then
return false, { 'cvt_big_prec', tostring(precision) }
Line 1,955 ⟶ 2,191:
local t = format_number(parms, show, exponent, isnegative)
-- Set singular using match because on some systems 0.99999999999999999 is 1.0.
t.singular = (type(show) == 'string' and (show == '1' or show:match('^1%.0*$') ~= nil) and not isnegative)
t.fraction_table = (type(show) == 'table') and show or nil
t.raw_absvalue = outvalue -- absolute value before rounding
t.use_default_precision = use_default_precision
return true, setmetatable(t, {
__index = function (self, key)
Line 1,971 ⟶ 2,207:
end
end })
end
 
function cvt_to_hand(parms, info, in_current, out_current)
-- Convert input to hands, inches.
-- Return true, t where t is a table with the conversion results;
-- or return false, t where t is an error message table.
if parms.abbr_org == nil then
out_current.usename = true -- default is to show name not symbol
end
local precision = parms.precision
local frac = out_current.frac
if not frac and precision and precision > 1 then
frac = (precision == 2) and 2 or 4
end
local out_next = out_current.out_next
if out_next then
-- Use magic knowledge to determine whether the next unit is inches without requiring i18n.
-- The following ensures that when the output combination "hand in" is used, the inches
-- value is rounded to match the hands value. Also, displaying say "61½" instead of 61.5
-- is better as 61.5 implies the value is not 61.4.
if out_next.exception == 'subunit_more_precision' then
out_next.frac = frac
end
end
-- Convert to inches; calculate hands from that.
local dummy_unit_table = { scale = out_current.scale / 4, frac = frac }
local success, outinfo = cvtround(parms, info, in_current, dummy_unit_table)
if not success then return false, outinfo end
local tfrac = outinfo.fraction_table
local inches = outinfo.raw_absvalue
if tfrac then
inches = floor(inches) -- integer part only; fraction added later
else
inches = floor(inches + 0.5) -- a hands measurement never shows decimals of an inch
end
local hands, inches = divide(inches, 4)
outinfo.absvalue = hands + inches/4 -- supposed to be the absolute rounded value, but this is close enough
local inchstr = tostring(inches) -- '0', '1', '2' or '3'
if precision and precision <= 0 then -- using negative or 0 for precision rounds to nearest hand
hands = floor(outinfo.raw_absvalue/4 + 0.5)
inchstr = ''
elseif tfrac then
-- Always show an integer before fraction (like "15.0½") because "15½" means 15-and-a-half hands.
inchstr = numdot .. format_fraction(parms, 'out', false, inchstr, tfrac.numstr, tfrac.denstr)
else
inchstr = numdot .. from_en(inchstr)
end
outinfo.show = outinfo.sign .. with_separator(parms, format('%.0f', hands)) .. inchstr
return true, outinfo
end
 
Line 2,033 ⟶ 2,318:
-- Input must use en digits and '.' decimal mark.
local default = default_exceptions[unit_table.defkey or unit_table.symbol] or unit_table.default
if not default == nil then
return false, { 'cvt_no_default', unit_table.symbol }
end
Line 2,063 ⟶ 2,348:
-- * link has previously been used during the current convert (to avoid overlinking).
-- Linking with a unit uses the unit table as the link key, which fails to detect
-- overlinking for conversions like the following (each links "mile" twice):
-- {{convert|1|impgal/mi|USgal/mi|lk=on}}
-- {{convert|1|l/km|impgal/mi USgal/mi|lk=on}}
link_key = link_key or link -- use key if given (the key, but not the link, may be known when need to cancel a link record)
if not link == nil or link == '' or linked_pages[link_key] then
return id
end
Line 2,083 ⟶ 2,368:
end
 
local function linked_idvariable_name(unit_tableclean, key_id, want_linkunit_table)
-- For sl.wiki (Slovenian Wikipedia), a unit name depends on the value.
-- Parameter clean is the unsigned rounded value in en digits, as a string.
-- Value Source Example for "m"
-- integer 1: name1 meter (also is the name of the unit)
-- integer 2: var{1} metra
-- integer 3 and 4: var{2} metri
-- integer else: var{3} metrov (0 and 5 or more)
-- real/fraction: var{4} metra
-- var{i} means the i'th field in unit_table.varname if it exists and has
-- an i'th field, otherwise name2.
-- Fields are separated with "!" and are not empty.
-- A field for a unit using an SI prefix has the prefix name inserted,
-- replacing '#' if found, or before the field otherwise.
local vname
if clean == '1' then
vname = unit_table.name1
elseif unit_table.varname then
local i
if clean == '2' then
i = 1
elseif clean == '3' or clean == '4' then
i = 2
elseif clean:find('.', 1, true) then
i = 4
else
i = 3
end
vname = split(unit_table.varname, '!')[i]
end
if vname then
local si_name = rawget(unit_table, 'si_name') or ''
local pos = vname:find('#', 1, true)
if pos then
vname = vname:sub(1, pos - 1) .. si_name .. vname:sub(pos + 1)
else
vname = si_name .. vname
end
return vname
end
return unit_table.name2
end
 
local function linked_id(unit_table, key_id, want_link, clean)
-- Return final unit id (symbol or name), optionally with a wikilink,
-- and update unit_table.sep if required.
Line 2,127 ⟶ 2,455:
end
if want_link and unit_table.link then
if abbr_on or not varname then
result = (unit1 and unit1[key_id] or '') .. result .. unit2[key_id2]
result = (unit1 and unit1[key_id] or '') .. result .. unit2[key_id2]
else
result = (unit1 and variable_name(clean, unit1) or '') .. result .. variable_name('1', unit2)
end
return make_link(unit_table.link, result, unit_table)
end
if unit1 then
result = linked_id(unit1, key_id, want_link, clean) .. result
end
return result .. linked_id(unit2, key_id2, want_link, '1')
end
if multiplier then
Line 2,151 ⟶ 2,483:
multiplier = ''
end
local id = unit_table.fixed_name or ((varname and not abbr_on) and variable_name(clean, unit_table) or unit_table[key_id])
if want_link then
local link = link_exceptions[unit_table.linkey or unit_table.symbol] or unit_table.link
if link then
local before = ''
Line 2,163 ⟶ 2,495:
i = 4 -- abbreviate "imperial" to "imp"
end
local customary = text_code.customary_units[i]
if customary then
-- LATER: This works for language en only, but it's esoteric so ignore for now.
Line 2,208 ⟶ 2,540:
end
local inout = unit_table.inout
local valinfoinfo = unit_table.valinfo[which]
local abbr_org = parms.abbr_org
local adjectival = parms.opt_adjectival
local disp = parms.disp
local lk = parms.lk
local want_link = (lk == 'on' or lk == inout)
local usename = unit_table.usename
local singular = valinfo[which]info.singular
if usename then
-- Old template does something like this.
if lk == 'on' or lk == inoutwant_link then
-- A linked unit uses the standard singular.
else
Line 2,224 ⟶ 2,557:
if inout == 'in' then
if not adjectival and (abbr_org == 'out' or flipped) then
local value = valinfo[which]info.value
singular = (0 < value and value < 1.0001)
end
Line 2,231 ⟶ 2,564:
(not flipped and (abbr_org == nil or abbr_org == 'out')) or
(flipped and abbr_org == 'in') then
singular = (valinfo[which]info.absvalue < 1.0001 and
not valinfo[which]info.is_scientific)
end
end
Line 2,260 ⟶ 2,593:
local key
if want_name then
if lk == nil and unit_table.builtin == 'hand' then
want_link = true
end
if parms.opt_use_nbsp then
unit_table.sep = '&nbsp;'
Line 2,268 ⟶ 2,604:
local value
if inout == 'in' then
value = valinfo[which]info.value
else
value = valinfo[which]info.absvalue
end
if value then -- some unusual units do not always set value field
Line 2,287 ⟶ 2,623:
end
else
if unit_table.builtin == 'hand' then
if parms.opt_hand_hh then
unit_table.symbol = 'hh' -- LATER: might want i18n applied to this
end
end
unit_table.sep = '&nbsp;'
key = unit_table.sp_us and 'sym_us' or 'symbol'
end
return linked_id(unit_table, key, lkwant_link, == 'on' or lk == inoutinfo.clean), want_name
end
 
Line 2,298 ⟶ 2,639:
-- the style to display powers of 10 includes "display:none" to allow some
-- browsers to copy, for example, "10³" as "10^3", rather than as "103".
local info
local engscale = unit_table.engscale
local prefix = unit_table.vprefix
if engscale or prefix then
info = unit_table.valinfo[which]
if info.decorated then
return -- do not redecorate if repeating convert
end
info.decorated = true
end
if engscale then
local inout = unit_table.inout
local info = unit_table.valinfo[which]
local abbr = parms.abbr
if abbr == 'on' or abbr == inout then
Line 2,321 ⟶ 2,670:
end
end
local prefix = unit_table.vprefix
if prefix then
local info = unit_table.valinfo[which]
info.show = prefix .. info.show
end
Line 2,366 ⟶ 2,713:
return preunit .. id1
end
local disp_joins = text_code.disp_joins
local abbr = parms.abbr
local disp = parms.disp
Line 2,600 ⟶ 2,948:
end
end
local functiondo_spell make_result(info)= parms.opt_spell_out
parms.opt_spell_out = nil -- so the call to cvtround does not spell the value
local function make_result(info, isfirst)
local fmt, outvalue, sign
local results = {}
for i = 1, #combos do
local tfrac, thisvalue, strforce
local out_current = combos[i]
out_current.inout = 'out'
local scale = multiple[i]
if i == 1 then -- least significant unit ('in' from 'ftin')
local fractiondecimals
out_current.frac = out_unit_table.frac
local success, outinfo = cvtround(parms, info, in_unit_table, out_current)
if not success then return false, outinfo end
if isfirst then
out_unit_table.valinfo = { outinfo } -- in case output value of first least significant unit is needed
end
sign = outinfo.sign
tfrac = outinfo.fraction_table
if outinfo.is_scientific then
strforce = outinfo.show
fractiondecimals = ''
elseif tfrac then
decimals = ''
else
fractiondecimals = (outinfo.show):match('[' .. numdot .. '](.*)') or '' -- outinfo.show is in local language
end
fmt = '%.' .. ulen(fractiondecimals) .. 'f' -- to reproduce precision
if fractiondecimals == '' then
if tfrac then
outvalue = floor(outinfo.raw_absvalue + 0.5) -- keep all integer digits of least significant unit
outvalue = floor(outinfo.raw_absvalue) -- integer part only; fraction added later
else
outvalue = floor(outinfo.raw_absvalue + 0.5) -- keep all integer digits of least significant unit
end
else
outvalue = outinfo.absvalue
Line 2,627 ⟶ 2,988:
end
if scale then
outvalue, thisvalue = floordivide(outvalue /, scale), outvalue % scale
else
thisvalue = outvalue
Line 2,633 ⟶ 2,994:
local id
if want_name then
if varname then
id = out_current[(thisvalue == 1) and 'name1' or 'name2']
local clean
if strforce or tfrac then
clean = '.1' -- dummy value to force name for floating point
else
clean = format(fmt, thisvalue)
end
id = variable_name(clean, out_current)
else
id = out_current[(thisvalue == 1) and 'name1' or 'name2']
end
else
id = out_current['symbol']
Line 2,644 ⟶ 3,015:
end
local strval
local inout = (i == #combos or outvalue == 0) and 'out' or '' -- trick so the last value processed (first displayed) has uppercase, if requested
if strforce and outvalue == 0 then
sign = '' -- any sign is in strforce
strval = strforce -- show small values in scientific notation; will only use least significant unit
elseif tfrac then
local wholestr = (thisvalue > 0) and tostring(thisvalue) or nil
strval = format_fraction(parms, inout, false, wholestr, tfrac.numstr, tfrac.denstr, do_spell)
else
strval = (thisvalue == 0) and from_en('0') or with_separator(parms, format(fmt, thisvalue))
if do_spell then
strval = spell_number(parms, inout, strval) or strval
end
end
table.insert(results, strval .. sep1 .. id)
Line 2,654 ⟶ 3,032:
break
end
fmt = '%.0f' -- only least significant unit can have a fractionnon-integral value
end
local reversed, count = {}, #results
Line 2,663 ⟶ 3,041:
end
local valinfo = in_unit_table.valinfo
local success, result = make_result(valinfo[1], true)
if not success then return false, result end
local range = parms.range
Line 2,676 ⟶ 3,054:
end
 
local function process(parms, in_unit_table, out_unit_table)
-- Return true, s where s = final wikitext result,
-- or return false, t where t is an error message table.
linked_pages = {}
local success, bad_output, out_unit_tableout_first
local bad_input_mcode = in_unit_table.bad_mcode -- nilfalse if input unit is valid
local invalue1 = in_unit_table.valinfo[1].value
local out_unit = parms.out_unit
Line 2,689 ⟶ 3,067:
else
success, out_unit = get_default(invalue1, in_unit_table)
parms.out_unit = out_unit
if not success then
bad_output = out_unit
Line 2,694 ⟶ 3,073:
end
end
if not bad_output and not out_unit_table then
success, out_unit_table = lookup(out_unit, parms.opt_sp_us, 'any_combination')
if success then
Line 2,720 ⟶ 3,099:
local outputs = {}
local combos -- nil (for 'ft' or 'ftin'), or table of unit tables (for 'm ft')
if not out_unit_table.multiple == nil then -- nil/false ('ft' or 'm ft'), or table of factors ('ftin')
combos = out_unit_table.combination
end
local frac = parms.frac -- nil or denominator of fraction for output values
if frac then
-- Apply fraction to the unit (if only one), or to non-SI units (if a combination),
-- except that if a precision is also specified, the fraction only applies to
-- the hand unit; that allows the following result:
-- {{convert|156|cm|in hand|1|frac=2}} → 156 centimetres (61.4 in; 15.1½ hands)
-- However, the following is handled elsewhere as a special case:
-- {{convert|156|cm|hand in|1|frac=2}} → 156 centimetres (15.1½ hands; 61½ in)
if combos then
local precision = parms.precision
for _, unit in ipairs(combos) do
if unit.builtin == 'hand' or (not precision and not unit.prefixes) then
unit.frac = frac
end
end
else
out_unit_table.frac = frac
end
end
local imax = combos and #combos or 1 -- 1 (single unit) or number of unit tables
Line 2,728 ⟶ 3,126:
local out_current = combos and combos[i] or out_unit_table
out_current.inout = 'out'
if out_current.multiplei == nil1 then
success, itemout_first = make_output_single(parms, in_unit_table, out_current)
if imax > 1 and out_current.builtin == 'hand' then
else
out_current.out_next = combos[2] -- built-in hand can influence next unit in a combination
end
end
if out_current.multiple then
success, item = make_output_multiple(parms, in_unit_table, out_current)
else
success, item = make_output_single(parms, in_unit_table, out_current)
end
if not success then return false, item end
table.insert(outputs, item)
end
parts[part]local sep = parms.opt_input_unit_onlytable_joins and ''parms.table_joins[2] or table.concat(outputs, '; ')
parts[part] = parms.opt_input_unit_only and '' or table.concat(outputs, sep)
end
end
if parms.opt_sortableopt_sortable_in or parms.opt_sortable_out then
local value
parts[1] = ntsh(invalue1, parms.opt_sortable_debug) .. parts[1]
if parms.opt_sortable_in then
value = invalue1
else
local info = out_first and out_first.valinfo
if info then
info = info[1]
value = info.raw_absvalue
if value and info.sign == MINUS then
value = -value
end
end
end
parts[1] = ntsh((value or 0), parms.opt_sortable_debug) .. parts[1]
end
local wikitext
Line 2,753 ⟶ 3,171:
wikitext = wikitext .. parms.warnings
end
return true, wikitext, out_unit_table
end
 
local function main_convert(frame)
-- Do convert, and if needed, do it again with higher default precision.
set_config(frame)
local result, out_unit_table
local success, parms, in_unit_table = get_parms(frame:getParent())
if success then
for i = 1, 2 do -- use counter so cannot get stuck repeating convert
success, result = process(parms, in_unit_table)
success, result, out_unit_table = process(parms, in_unit_table, out_unit_table)
if success and parms.do_convert_again then
parms.do_convert_again = false
else
break
end
end
else
result = parms
Anonymous user