-- For Template:Val, output a number and optional unit. -- Format options include scientific and uncertainty notations.  local data_module = 'মডিউল:Val/units' local convert_module = 'মডিউল:Convert'  local function valerror(msg, nocat, iswarning) 	-- Return formatted message text for an error or warning. 	-- Can append "#FormattingError" to URL of a page with a problem to find it. 	local anchor = '' 	local body, category 	if nocat or mw.title.getCurrentTitle():inNamespaces(1, 2, 3, 5) then 		-- No category in Talk, User, User_talk, or Wikipedia_talk. 		category = 	else 		category = 'বিষয়শ্রেণী:পাতাসমূহ ভুল ফর্ম্যাটিং টেমপ্লেট ব্যবহার করছে' 	end 	iswarning = false  -- problems are infrequent so try showing large error so editor will notice 	if iswarning then 		body = '' .. 			'warning' 	else 		body = '' .. 			'Error in {{val}}: ' .. 			msg .. 			'' 	end 	return anchor .. body .. category end  local range_types = { 	-- No need for ' ' because nowrap applies to all output. 	[","]   = ", ", 	["by"]  = " by ", 	["-"]   = "–", 	["–"]   = "–", 	["and"] = " and ", 	["or"]  = " or " , 	["to"]  = " to " , 	["x"]   = " × ", 	["×"]   = " × ", 	["/"]   = "/", } local range_repeat_unit = { 	-- WP:UNIT wants unit repeated when a "multiply" range is used. 	["x"]   = true, 	["×"]   = true, }  local function extract_item(index, numbers, arg) 	-- Extract an item from arg and store the result in numbers[index]. 	-- If no argument or if argument is valid, return nil (no error); 	-- otherwise, return an error message. 	-- The stored result is: 	-- * a table for a number (empty if there was no specified number); or 	-- * a string for range text 	-- Input like 1e3 is regarded as invalid for all except argument 1 	-- which accepts e notation as an alternative to the 'e' argument. 	-- Input commas are removed so 1,234 is the same as 1234. 	local which = index 	local function fail(msg) 		local description 		if which == 'e' then 			description = 'exponent parameter (e)' 		else 			description = 'parameter ' .. which 		end 		return description .. ' ' .. (msg or 'is not a valid number') .. '.' 	end 	local result = {} 	local range = range_types[arg] 	if range then 		if type(index) == 'number' and (index % 2 == 0) then 			if index == 2 then 				if numbers[1] and numbers[1].exp then 					return fail('cannot use a range if the first parameter includes "e"') 				end 				numbers.has_ranges = true 			else 				if not numbers.has_ranges then 					return fail('needs a range in parameter 2') 				end 			end 			numbers[index] = range 			if range_repeat_unit[arg] then 				-- Any "repeat" range forces unit (if any) to be repeated for all items. 				numbers.isrepeat = true 			end 			return nil 		end 		return fail('does not accept a range') 	end 	if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then 		return fail('should be a range') 	end 	if index == 'e' then 		local e = numbers[1] and numbers[1].exp 		if e then 			if arg then 				return fail('cannot be used if the first parameter includes "e"') 			end 			arg = e 			which = 1 		end 	end 	if arg and arg ~=  then 		arg = arg:gsub(',', ) 		if arg:sub(1, 1) == '(' and arg:sub(-1) == ')' then 			result.parens = true 			arg = arg:sub(2, -2) 		end 		local a, b = arg:match('^(.+)[Ee](.+)$') 		if a then 			if index == 1 then 				arg = a 				result.exp = b 			else 				return fail('cannot use e notation') 			end 		end 		local isnegative, propersign, prefix 		local minus = '−' 		prefix, arg = arg:match('^(.-)([%d.]+)$') 		local value = tonumber(arg) 		if not value then 			return fail() 		end 		if arg:sub(1, 1) == '.' then 			arg = '0' .. arg 		end 		if prefix ==  then 			-- Ignore. 		elseif prefix == '±' then 			-- Display for first number, ignore for others. 			if index == 1 then 				propersign = '±' 			end 		elseif prefix == '+' then 			propersign = '+' 		elseif prefix == '-' or prefix == minus then 			propersign = minus 			isnegative = true 		else 			return fail() 		end 		result.clean = arg 		result.sign = propersign or 		result.value = isnegative and -value or value 	end 	numbers[index] = result 	return nil  -- no error end  local function get_args(numbers, args) 	-- Extract arguments and store the results in numbers. 	-- Return nothing (no error) if ok; otherwise, return an error message. 	for index = 1, 99 do 		local which = index 		local arg = args[which]  -- has been trimmed 		if not arg then 			which = 'e' 			arg = args[which] 		end 		local msg = extract_item(which, numbers, arg) 		if msg then 			return msg 		end 		if which == 'e' then 			break 		end 		if index > 19 then 			return 'too many parameters' 		end 	end 	if numbers.has_ranges and (#numbers % 2 == 0) then 		return 'need a number after the last parameter because it is a range.' 	end end  local function get_scale(text, ucode) 	-- Return the value of text as a number, or throw an error. 	-- This supports extremely basic expressions of the form: 	--   a / b 	--   a ^ b 	-- where a and b are numbers or 'pi'. 	local n = tonumber(text) 	if n then 		return n 	end 	n = text:gsub('pi', math.pi) 	for _, op in ipairs({ '/', '^' }) do 		local a, b = n:match('^(.-)' .. op .. '(.*)$') 		if a then 			a = tonumber(a) 			b = tonumber(b) 			if a and b then 				if op == '/' then 					return a / b 				elseif op == '^' then 					return a ^ b 				end 			end 			break 		end 	end 	error('Unit "' .. ucode .. '" has invalid scale "' .. text .. '"') end  local function get_builtin_unit(ucode, definitions) 	-- Return table of information for the specified built-in unit, or nil if not known. 	-- Each defined unit code must be followed by two spaces (not tab characters). 	local _, pos = definitions:find('\n' .. ucode .. '  ', 1, true) 	if pos then 		local endline = definitions:find('%s*\n', pos) 		if endline then 			local result = {} 			local n = 0 			local text = definitions:sub(pos + 1, endline - 1):gsub('%s%s+', '\t') 			for item in (text .. '\t'):gmatch('(%S.-)\t') do 				if item == 'ALIAS' then 					result.alias = true 				elseif item == 'ANGLE' then 					result.isangle = true 					result.nospace = true 				elseif item == 'NOSPACE' then 					result.nospace = true 				elseif item == 'SI' then 					result.si = true 				else 					n = n + 1 					if n == 1 then 						local link, symbol = item:match('^%[%[([^|]+)|(.+)%]%]$') 						if link then 							result.symbol = symbol 							result.link = link 							n = 2 						else 							result.symbol = item 						end 					elseif n == 2 then 						result.link = item 					elseif n == 3 then 						result.scale_text = item 						result.scale = get_scale(item, ucode) 					else 						result.more_ignored = item 						break 					end 				end 			end 			if result.si then 				local s = result.symbol 				if ucode == 'mc' .. s or ucode == 'mu' .. s then 					result.ucode = 'µ' .. s  -- unit code for convert should be this 				end 			end 			if n >= 2 or (n >= 1 and result.alias) then 				return result 			end 			-- Ignore invalid definition, treating it as a comment. 		end 	end end  local function convert_lookup(ucode, value, scaled_top, want_link, si, options) 	local lookup = require(convert_module)._unit 	return lookup(ucode, { 			value = value, 			scaled_top = scaled_top, 			link = want_link, 			si = si, 			sort = options.sortable, 		}) end  local function get_unit(ucode, value, scaled_top, options) 	local want_link = options.want_link 	if scaled_top then 		want_link = options.want_per_link 	end 	local data = mw.loadData(data_module) 	local result = options.want_longscale and 		get_builtin_unit(ucode, data.builtin_units_long_scale) or 		get_builtin_unit(ucode, data.builtin_units) 	local si, use_convert 	if result then 		if result.alias then 			ucode = result.symbol 			use_convert = true 		end 		if result.scale then 			-- Setting si means convert will use the unit as given, and the sort key 			-- will be calculated from the value without any extra scaling that may 			-- occur if convert found the unit code. For example, if val defines the 			-- unit 'year' with a scale and if si were not set, convert would also apply 			-- its own scale because convert knows that a year is 31,557,600 seconds. 			si = { result.symbol, result.link } 			value = value * result.scale 		end 		if result.si then 			ucode = result.ucode or ucode 			si = { result.symbol, result.link } 			use_convert = true 		end 	else 		result = {} 		use_convert = true 	end 	local convert_unit = convert_lookup(ucode, value, scaled_top, want_link, si, options) 	result.sortkey = convert_unit.sortspan 	if use_convert then 		result.text = convert_unit.text 		result.scaled_top = convert_unit.scaled_value 	else 		if want_link then 			result.text = '' .. result.symbol .. '' 		else 			result.text = result.symbol 		end 		result.scaled_top = value 	end 	return result end  local function makeunit(value, options) 	-- Return table of information for the requested unit and options, or 	-- return nil if no unit. 	options = options or {} 	local unit 	local ucode = options.u 	local percode = options.per 	if ucode then 		unit = get_unit(ucode, value, nil, options) 	elseif percode then 		unit = { nospace = true, scaled_top = value } 	else 		return nil 	end 	local text = unit.text or  	local sortkey = unit.sortkey 	if percode then 		local function bracketed(code, text) 			return code:find('[*./]') and '(' .. text .. ')' or text 		end 		local perunit = get_unit(percode, 1, unit.scaled_top, options) 		text = (ucode and bracketed(ucode, text) or ) .. 				'/' .. bracketed(percode, perunit.text) 		sortkey = perunit.sortkey 	end 	if not (unit.nospace or options.nospace) then 		text = ' ' .. text 	end 	return { text = text, isangle = unit.isangle, sortkey = sortkey } end  local function list_units(mode) 	-- Return wikitext to list the built-in units. 	-- A unit code should not contain wikimarkup so don't bother escaping. 	local data = mw.loadData(data_module) 	local definitions = data.builtin_units .. data.builtin_units_long_scale 	local last_was_blank = true 	local n = 0 	local result = {} 	local function add(line) 		if line ==  then 			last_was_blank = true 		else 			if last_was_blank and n > 0 then 				n = n + 1 				result[n] = 			end 			last_was_blank = false 			n = n + 1 			result[n] = line 		end 	end 	local si_prefixes = { 		-- These are the prefixes recognized by convert; u is accepted for micro. 		y = 'y', 		z = 'z', 		a = 'a', 		f = 'f', 		p = 'p', 		n = 'n', 		u = 'µ', 		['µ'] = 'µ', 		m = 'm', 		c = 'c', 		d = 'd', 		da = 'da', 		h = 'h', 		k = 'k', 		M = 'M', 		G = 'G', 		T = 'T', 		P = 'P', 		E = 'E', 		Z = 'Z', 		Y = 'Y', 	} 	local function is_valid(ucode, unit) 		if unit and not unit.more_ignored then 			assert(type(unit.symbol) == 'string' and unit.symbol ~= ) 			if unit.alias then 				if unit.link or unit.scale_text or unit.si then 					return false 				end 			end 			if unit.si then 				if unit.scale_text then 					return false 				end 				ucode = unit.ucode or ucode 				local base = unit.symbol 				if ucode == base then 					unit.display = base 					return true 				end 				local plen = #ucode - #base 				if plen > 0 then 					local prefix = si_prefixes[ucode:sub(1, plen)] 					if prefix and ucode:sub(plen + 1) == base then 						unit.display = prefix .. base 						return true 					end 				end 			else 				unit.display = unit.symbol 				return true 			end 		end 		return false 	end 	local lookup = require(convert_module)._unit 	local function show_convert(ucode, unit) 		-- If a built-in unit defines a scale or sets the SI flag, any unit defined in 		-- convert is not used (the scale or SI prefix's scale is used for a sort key). 		-- If there is no scale or SI flag, and the unit is not defined in convert, 		-- the sort key may not be correct; this allows such units to be identified. 		if not (unit.si or unit.scale_text) then 			if mode == 'convert' then 				unit.show = not lookup(unit.alias and unit.symbol or ucode).unknown 				unit.show_text = 'CONVERT' 			elseif mode == 'unknown' then 				unit.show = lookup(unit.alias and unit.symbol or ucode).unknown 				unit.show_text = 'UNKNOWN' 			elseif not unit.alias then 				-- Show convert's scale in square brackets ('[1]' for an unknown unit). 				-- Don't show scale for an alias because it's misleading for temperature 				-- and an alias is probably not useful for anything else. 				local scale = lookup(ucode, {value=1, sort='on'}).scaled_value 				if type(scale) == 'number' then 					scale = string.format('%.5g', scale):gsub('e%+?(%-?)0*(%d+)', 'e%1%2') 				else 					scale = '?' 				end 				unit.show = true 				unit.show_text = '[' .. scale .. ']' 			end 		end 	end 	for line in definitions:gmatch('([^\n]*)\n') do 		local pos, _ = line:find('  ', 1, true) 		if pos then 			local ucode = line:sub(1, pos - 1) 			local unit = get_builtin_unit(ucode, '\n' .. line .. '\n') 			if is_valid(ucode, unit) then 				show_convert(ucode, unit) 				local flags, text 				if unit.alias then 					text = unit.symbol 				else 					text = '' .. unit.display .. '' 				end 				if unit.isangle then 					unit.nospace = nil  -- don't show redundant flag 				end 				for _, f in ipairs({ 						{ 'alias', 'ALIAS' }, 						{ 'isangle', 'ANGLE' }, 						{ 'nospace', 'NOSPACE' }, 						{ 'si', 'SI' }, 						{ 'scale_text', unit.scale_text }, 						{ 'show', unit.show_text }, 					}) do 					if unit[f[1]] then 						local t = f[2] 						if t:match('^%u+$') then 							t = '' .. t .. '' 						end 						if flags then 							flags = flags .. ' ' .. t 						else 							flags = t 						end 					end 				end 				if flags then 					text = text .. ' • ' .. flags 				end 				add(ucode .. ' = ' .. text .. '') 			else 				add(line .. ' ◆ invalid definition') 			end 		else 			add(line) 		end 	end 	return table.concat(result, '\n') end  local delimit_groups = require('Module:Gapnum').groups local function delimit(sign, numstr, fmt) 	-- Return sign and numstr (unsigned digits or '.' only) after formatting. 	-- Four-digit integers are not formatted with gaps. 	fmt = (fmt or ):lower() 	if fmt == 'none' or (fmt ==  and #numstr == 4 and numstr:match('^%d+$')) then 		return sign .. numstr 	end 	-- Group number by integer and decimal parts. 	-- If there is no decimal part, delimit_groups returns only one table. 	local ipart, dpart = delimit_groups(numstr) 	local result 	if fmt == 'commas' then 		result = sign .. table.concat(ipart, ',') 		if dpart then 			result = result .. '.' .. table.concat(dpart) 		end 	else 		-- Delimit with a small gap by default. 		local groups = {} 		groups[1] = table.remove(ipart, 1) 		for _, v in ipairs(ipart) do 			table.insert(groups, '' .. v .. '') 		end 		if dpart then 			table.insert(groups, '.' .. (table.remove(dpart, 1) or )) 			for _, v in ipairs(dpart) do 				table.insert(groups, '' .. v .. '') 			end 		end 		result = sign .. table.concat(groups) 	end 	return result end  local function sup_sub(sup, sub, align) 	-- Return the same result as Module:Su except val defaults to align=right. 	if align == 'l' or align == 'left' then 		align = 'left' 	elseif align == 'c' or align == 'center' then 		align = 'center' 	else 		align = 'right' 	end 	return '' .. sup .. '' .. sub .. '' end  local function range_text(items, unit_table, options) 	local fmt = options.fmt 	local nend = items.nend or  	if items.isrepeat or unit_table.isangle then 		nend = nend .. unit_table.text 	end 	local text = 	for i = 1, #items do 		if i % 2 == 0 then 			text = text .. items[i] 		else 			text = text .. delimit(items[i].sign, items[i].clean, fmt) .. nend 		end 	end 	return text end  local function uncertainty_text(uncertainty, unit_table, options) 	local angle, text, need_parens 	if unit_table.isangle then 		angle = unit_table.text 	end 	local upper = uncertainty.upper or {} 	local lower = uncertainty.lower or {} 	local uncU = upper.clean 	if uncU then 		local fmt = options.fmt 		local uncL = lower.clean 		if uncL then 			uncU = delimit('+', uncU, fmt) .. (upper.errend or ) 			uncL = delimit('−', uncL, fmt) .. (lower.errend or ) 			if angle then 				uncU = uncU .. angle 				uncL = uncL .. angle 			end 			text = (angle or ) .. 				'' .. 				sup_sub(uncU, uncL, options.align) .. 				'' 		else 			if upper.parens then 				text = '(' .. uncU .. ')'  -- old template did not delimit 			else 				text = (angle or ) .. 					'±' .. 					delimit(, uncU, fmt) 				need_parens = true 			end 			if uncertainty.errend then 				text = text .. uncertainty.errend 			end 			if angle then 				text = text .. angle 			end 		end 	else 		if angle then 			text = angle 		end 	end 	return text, need_parens end  local function _main(values, unit_spec, options) 	if options.sandbox then 		data_module = data_module .. '/sandbox' 		convert_module = convert_module .. '/sandbox' 	end 	local action = options.action 	if action then 		if action == 'list' then 			-- Kludge: am using the align parameter (a=xxx) for type of list. 			return list_units(options.align) 		end 		return valerror('invalid action "' .. action .. '".', options.nocat) 	end 	local number = values.number or (values.numbers and values.numbers[1]) or {} 	local e_10 = options.e or {} 	local novalue = (number.value == nil and e_10.clean == nil) 	local fmt = options.fmt 	local want_sort = true 	local sortable = options.sortable 	if sortable == 'off' or (sortable == nil and novalue) then 		want_sort = false 	elseif sortable == 'debug' then 		-- Same as sortable = 'on' but the sort key is displayed. 	else 		sortable = 'on' 	end 	local sort_value = 1 	if want_sort then 		sort_value = number.value or 1 		if e_10.value and sort_value ~= 0 then 			-- The 'if' avoids  giving an invalid sort_value due to overflow. 			sort_value = sort_value * 10^e_10.value 		end 	end 	local unit_table = makeunit(sort_value, { 						u = unit_spec.u, 						want_link = unit_spec.want_link, 						per = unit_spec.per, 						want_per_link = unit_spec.want_per_link, 						nospace = novalue, 						want_longscale = unit_spec.want_longscale, 						sortable = sortable, 					}) 	local sortkey 	if unit_table then 		if want_sort then 			sortkey = unit_table.sortkey 		end 	else 		unit_table = { text =  } 		if want_sort then 			sortkey = convert_lookup('dummy', sort_value, nil, nil, nil, { sortable = sortable }).sortspan 		end 	end 	local final_unit = unit_table.isangle and  or unit_table.text 	local e_text, n_text, need_parens 	local uncertainty = values.uncertainty 	if uncertainty then 		if number.clean then 			n_text = delimit(number.sign, number.clean, fmt) .. (number.nend or ) 			local text 			text, need_parens = uncertainty_text(uncertainty, unit_table, options) 			if text then 				n_text = n_text .. text 			end 		else 			n_text =  		end 	else 		if values.numbers.isrepeat then 			final_unit = 		end 		n_text = range_text(values.numbers, unit_table, options) 		need_parens = true 	end 	if e_10.clean then 		if need_parens then 			n_text = '(' .. n_text .. ')' 		end 		e_text = '10' .. delimit(e_10.sign, e_10.clean, fmt) .. '' 		if number.clean then 			e_text = '×' .. e_text 		end 	else 		e_text =  	end 	local result = 		(sortkey or ) .. 		(options.prefix or ) .. 		n_text .. 		e_text .. 		final_unit .. 		(options.suffix or ) 	if result ~=  then 		result = '' .. result .. '' 	end 	return result .. (options.warning or '') end  local function check_parameters(args, has_ranges, nocat) 	-- Return warning text for the first problem parameter found, or nothing if ok. 	local whitelist = { 		a = true, 		action = true, 		debug = true, 		e = true, 		['end'] = true, 		errend = true, 		['+errend'] = true, 		['-errend'] = true, 		fmt = true, 		['long scale'] = true, 		long_scale = true, 		longscale = true, 		nocategory = true, 		p = true, 		s = true, 		sortable = true, 		u = true, 		ul = true, 		up = true, 		upl = true, 	} 	for k, v in pairs(args) do 		if type(k) == 'string' and not whitelist[k] then 			local warning = 'Val parameter "' .. k .. '=' .. v .. '" is not supported' 			return valerror(warning, nocat, true) 		end 	end 	if not has_ranges and args[4] then 		return valerror('Val parameter 4 ignored', nocat, true) 	end end  local function main(frame) 	local getArgs = require('Module:Arguments').getArgs 	local args = getArgs(frame, {wrappers = { 'Template:Val' }}) 	local nocat = args.nocategory 	local numbers = {}  -- table of number tables, perhaps with range text 	local msg = get_args(numbers, args) 	if msg then 		return valerror(msg, nocat) 	end 	if args.u and args.ul then 		return valerror('unit (u) and unit with link (ul) are both specified, only one is allowed.', nocat) 	end 	if args.up and args.upl then 		return valerror('unit per (up) and unit per with link (upl) are both specified, only one is allowed.', nocat) 	end 	local values 	if numbers.has_ranges then 		-- Multiple values with range separators but no uncertainty. 		numbers.nend = args['end'] 		values = { 			numbers = numbers, 		} 	else 		-- A single value with optional uncertainty. 		local function setfield(i, dst, src) 			local v = args[src] 			if v then 				if numbers[i] then 					numbers[i][dst] = v 				else 					numbers[i] = { [dst] = v } 				end 			end 		end 		setfield(1, 'nend', 'end') 		setfield(2, 'errend', '+errend') 		setfield(3, 'errend', '-errend') 		values = { 			number = numbers[1], 			uncertainty = { 				upper = numbers[2], 				lower = numbers[3], 				errend = args.errend, 			} 		} 	end 	local unit_spec = { 			u = args.ul or args.u, 			want_link = args.ul ~= nil, 			per = args.upl or args.up, 			want_per_link = args.upl ~= nil, 			want_longscale = (args.longscale or args.long_scale or args['long scale']) == 'on', 		} 	local options = { 			action = args.action, 			align = args.a, 			e = numbers.e, 			fmt = args.fmt, 			nocat = nocat, 			prefix = args.p, 			sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil, 			sortable = args.sortable or (args.debug == 'yes' and 'debug' or nil), 			suffix = args.s, 			warning = check_parameters(args, numbers.has_ranges, nocat), 		} 	return _main(values, unit_spec, options) end  return { main = main, _main = _main }