end
end  
local function clean_link(link, name)
-- Return link, customary where:
-- link = given link after removing any '...' wiki formatting
-- and removing any leading '+' or '*' or '@';
-- customary = 1 if leading '+', or 2 if '*' or 3 if '@', or nil
-- (for extra "US" or "U.S." or "Imperial" customary units link).
-- Result has leading/trailing whitespace removed, and is nil if empty
-- or if link matches the name, if a name is specified.
-- Exception: If the link is empty and the name starts with '[[',
-- the link is stored as '' (for a unit name which is always linked).
-- If the resulting link is nil, no link field is stored, and
-- if a link is required, it will be set from the unit's name.
local original = link
if empty(link) then
return (name and name:sub(1, 2) == '[[') and '' or nil
end
local prefixes = { ['+'] = 1, ['*'] = 2, ['@'] = 3 }
local customary = prefixes[link:sub(1, 1)]
if customary then
link = strip(link:sub(2))
end
if link:sub(1, 2) == '' then
link = link:sub(3)
end
if link:sub(-2) == '' then
link = link:sub(1, -3)
end
link = strip(link)
if link:sub(1, 1) == '[' or link:sub(-1) == ']' then
quit('m_lnk_brack', original)
end
if link == '' then
link = nil
elseif name then
local l = ulower(usub(link, 1, 1)) .. usub(link, 2)
local n = ulower(usub(name, 1, 1)) .. usub(name, 2)
if l == n then
link = nil -- link == name, ignoring case of first letter
end
end
return link, customary
end  
local function make_link(cfg, data)
-- Return a function which, when called, stores a table that defines a
-- link exception. The table is stored in data (also a table).
local links_index = {} -- to detect attempts to define a link twice
return function (utype, fields)
-- Store a table defining a unit.
-- This is for a unit such as 'kg' that has a linked article
-- different from what is defined for the base unit ('g').
-- Throw an error if a problem occurs.
local ucode = fields[1]
local link = clean_link(fields[2])
if empty(ucode) then
quit('m_lnk_miss')
end
if empty(link) then
quit('m_lnk_none', ucode)
end
if #fields ~= 2 then
quit('m_lnk_two', ucode)
end
local unit_table = get_unit(ucode)
if not unit_table then
quit('m_lnk_undef', ucode)
end
if utype ~= unit_table.utype then
quit('m_lnk_type', ucode)
end
local symbol = unit_table.symbol
if empty(symbol) then
quit('m_lnk_sym', ucode)
end
if links_index[ucode] then
quit('m_lnk_dup', ucode)
end
links_index[ucode] = link
table.insert(data, { symbol = symbol, link = link })
end
end  
local function clean_scale(scale)
-- Return cleaned scale as a string, after evaluating any expression.
-- It would be better to retain scale expressions like "5/9" so that
-- the expression is evaluated on the server and maintains the full
-- resolution of the server. However, there are many such expressions
-- in the table of all units, and it seems pointless to require the
-- server to evaluate all of them just to do one convert.
if empty(scale) then
quit('m_scl_miss')
end
assert(type(scale) == 'string', 'Bug: scale has an unexpected type')
scale = string.gsub(scale, ',', '') -- remove comma separators
if tonumber(scale) then -- not an expression
return scale
end
local status, value = pcall(evaluate, scale)
if not (status and type(value) == 'number') then
quit('m_scl_bad', scale)
end
local result = string.format('%.17g', value)
if result:find('[#n]') then
-- Lua can give results like "#INF" while Scribunto gives "inf". Either is an error.
quit('m_scl_oflow', scale)
end
-- Omit redundant zeros from results like '1.2e-005'.
-- Do not bother looking for results like '1.2e+005' as occur in practice.
local lhs, zeros, rhs = result:match('^(.-e%-)(0+)(.*)')
if zeros then
result = lhs .. rhs
end
return result
end  
local function add_alias_optional_fields(unit, start, fields, target)
-- Inspect fields[i] for i = start, start+1 ..., and extract any
-- definitions appropriate for an alias or "per", and add them to unit.
-- For an alias, target is a valid unit; for a "per", target is nil.
-- Throw error if encounter an invalid entry.
for i = start, #fields do
local field = fields[i]
if not empty(field) then
local lhs, rhs = field:match('^%s*(.-)%s*=%s*(.-)%s*$')
local good
if not empty(rhs) then
for _, item in ipairs({ 'sp', 'default', 'link', 'multiplier', 'symbol', 'symlink' }) do
if lhs == item then
if item == 'sp' then
if rhs == 'us' then
unit.sp_us = true
good = true
end
elseif item == 'link' then
local tlink
if target then
tlink = target[item]
end
local link, customary = clean_link(rhs, tlink)
if link then
unit[item] = link
end
if customary then
unit.customary = customary
end
good = true
elseif item == 'symlink' then
local pos1 = rhs:find('', 1, true)
local pos2 = rhs:find('', 1, true)
if not (pos1 and pos2 and (pos1 < pos2)) then
quit('m_als_link', unit.unitcode)
end
unit.symlink = rhs
good = true
elseif item == 'multiplier' then
if not tonumber(rhs) then
quit('m_als_mul', unit.unitcode, rhs)
end
unit[item] = rhs
good = true
else
if target and rhs == target[item] then
quit('m_als_same', item, unit.unitcode)
end
unit[item] = rhs
good = true
end
break
end
end
end
if not good then
quit('m_als_bad', field)
end
end
end
end  
local function make_alias(fields, ucode, utype, symbol)
-- Return a new alias unit, or return nil if symbol is not already
-- defined as the unit code of the target unit.
-- Throw an error if invalid.
local target = get_unit(symbol)
if not target then
return nil
end
local unit = { unitcode = ucode, utype = utype, target = symbol }
add_alias_optional_fields(unit, 3, fields, target)
if alias_index[ucode] then
quit('m_als_dup', ucode)
else
alias_index[ucode] = unit
end
if target.utype ~= utype then
quit('m_als_type', ucode)
end
return unit
end  
local function make_per(fields, ucode, utype, symbol)
-- Return a new "per" unit, or return nil if symbol is not of form "x/y".
-- Throw an error if invalid.
-- The top, bottom unit codes are checked later, after all units are defined.
local top, bottom = symbol:match('^(.-)/(.*)$')
if not top then
return nil
end
local unit = { unitcode = ucode, utype = utype, per = { strip(top), strip(bottom) } }
add_alias_optional_fields(unit, 3, fields)
if per_index[ucode] then
quit('m_per_dup', ucode)
else
per_index[ucode] = unit
end
return unit
end  
local function make_unit(cfg, data)
-- Return a function which, when called, stores a table that defines a
-- single unit. The table is stored in data (also a table).
local fieldnames = {
-- Fields in the Conversions section are assumed to be in the following order.
'unitcode',
'symbol',
'sym_us',
'scale',
'extra',
'name1',
'name2',
'name1_us',
'name2_us',
'prefixes',
'default',
'link',
}
return function (utype, fields)
-- Store a table defining a unit.
-- Throw an error if a problem occurs.
local ucode, symbol = fields[1], fields[2]
if empty(utype) then
quit('m_miss_type')
end
if empty(ucode) then
quit('m_miss_code')
end
if empty(symbol) then
quit('m_miss_sym')
end
local prefix = symbol:sub(1, 1)
if prefix == '~' or prefix == '=' or prefix == '!' or prefix == '*' then
if symbol:sub(1, 2) == '==' then
prefix = symbol:sub(1, 2)
end
symbol = strip(symbol:sub(#prefix + 1)) -- omit prefix and any following whitespace
fields[2] = symbol
else
prefix = nil -- not a valid prefix
end
if prefix == '=' or prefix == '==' then
-- ucode is an alias (a fake unit code used in a convert template), or
-- defines a "per" unit like "$/acre" or "BTU/h".
-- For an alias, symbol is the unit code of the actual unit.
-- For a "per", symbol is of form "x/y" where x and y are unit codes,
-- or x is a recognized currency symbol and y is a unit code.
-- Checking that x and y are valid is deferred until all units have
-- been defined so, for example, "BTU/h" can be defined before "h".
local unit
if prefix == '=' then
unit = make_alias(fields, ucode, utype, symbol)
else
unit = make_per(fields, ucode, utype, symbol)
end
if not unit then
-- Do not define an alias in terms of another alias.
quit('m_als_undef', symbol)
end
insert_unique_unit(data, unit, units_index)