Mô đun:Arguments

Bách khoa toàn thư mở Wikipedia
Buớc tưới chuyển hướng Bước tới tìm kiếm
Biểu tượng tài liệu Tài liệu mô đun [xem] [sửa] [lịch sử] [làm mới]

Mô-đun này cung cấp xử lý dễ dàng các đối số được truyền từ #invoke. Đây là một mô-đun meta, được sử dụng cho các mô-đun khác và không được gọi trực tiếp từ #invoke. Các tính năng của nó bao gồm:

  • Dễ dàng cắt xén các đối số và loại bỏ các đối số trống.
  • Đối số có thể được truyền bởi cả khung hiện tại và khung cha cùng một lúc. (Thêm chi tiết bên dưới.)
  • Các đối số có thể được truyền trực tiếp từ một mô-đun Lua khác hoặc từ bảng điều khiển gỡ lỗi.
  • Đối số được tìm nạp khi cần, điều này có thể giúp tránh (một số) sự cố với thẻ <ref>...</ref>.
  • Hầu hết các tính năng có thể được tùy chỉnh.

Sử dụng cơ bản

Đầu tiên, bạn cần tải mô-đun. Nó chứa một hàm, được đặt tên getArgs.

local getArgs = require('Module:Arguments').getArgs

Trong kịch bản cơ bản nhất, bạn có thể sử dụng getArgs bên trong chức năng chính của mình. Biến args là bảng chứa các đối số từ #invoke. (Xem bên dưới để biết chi tiết.)

local getArgs = require('Module:Arguments').getArgs
local p = {}

function p.main(frame)
	local args = getArgs(frame)
	-- Main module code goes here.
end

return p

Tuy nhiên, thực tế nên đề xuất là sử dụng một hàm chỉ để xử lý các đối số từ #invoke. Điều này có nghĩa là nếu ai đó gọi mô-đun của bạn từ một mô-đun Lua khác, bạn không phải có sẵn một đối tượng khung, điều này giúp cải thiện hiệu suất.

local getArgs = require('Module:Arguments').getArgs
local p = {}

function p.main(frame)
	local args = getArgs(frame)
	return p._main(args)
end

function p._main(args)
	-- Main module code goes here.
end

return p

Nếu bạn muốn nhiều hàm sử dụng các đối số và bạn cũng muốn chúng có thể truy cập được từ #invoke, bạn có thể sử dụng hàm bao bọc.

local getArgs = require('Module:Arguments').getArgs

local p = {}

local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame)
		return p[funcName](args)
	end
end

p.func1 = makeInvokeFunc('_func1')

function p._func1(args)
	-- Code for the first function goes here.
end

p.func2 = makeInvokeFunc('_func2')

function p._func2(args)
	-- Code for the second function goes here.
end

return p

Tùy chỉnh

Một só Lựa chọn tùy chỉnh có sẵn khi sử dụng. Chúng được giải thích trong các phần dưới đây.

local args = getArgs(frame, {
	trim = false,
	removeBlanks = false,
	valueFunc = function (key, value)
		-- Code for processing one argument
	end,
	frameOnly = true,
	parentOnly = true,
	parentFirst = true,
	wrappers = {
		'Template:A wrapper template',
		'Template:Another wrapper template'
	},
	readOnly = true,
	noOverwrite = true
})

Cắt và loại bỏ khoảng trống

Các đối số trống thường gây khó đối với các lập trình viên mới muốn chuyển đổi các bản mẫu MediaWiki sang Lua. Trong cú pháp bản mẫu, chuỗi trống và chuỗi chỉ bao gồm khoảng trắng được coi là sai. Tuy nhiên, trong Lua, chuỗi trống và chuỗi bao gồm khoảng trắng được coi là đúng. Điều này có nghĩa là nếu bạn không chú ý đến những lập luận như vậy khi bạn viết các mô-đun Lua của mình, bạn có thể coi điều gì đó là đúng mà thực sự nên được coi là sai. Để tránh điều này, theo mặc định, mô-đun này sẽ xóa tất cả các đối số trống.

Tương tự, khoảng trắng có thể gây ra vấn đề khi xử lý các đối số vị trí. Mặc dù khoảng trắng được cắt bớt cho các đối số được đặt tên đến từ #invoke, nhưng nó được giữ nguyên cho các đối số vị trí. Hầu hết thời gian khoảng trắng bổ sung này là không mong muốn, vì vậy mô-đun này cắt nó theo mặc định.

Tuy nhiên, đôi khi bạn muốn sử dụng các đối số trống làm đầu vào và đôi khi bạn muốn giữ khoảng trắng bổ sung. Điều này có thể cần thiết để chuyển đổi một số mẫu chính xác như chúng đã được viết. Nếu bạn muốn làm điều này, bạn có thể đặt trimremoveBlanks với tham số là false.

local args = getArgs(frame, {
	trim = false,
	removeBlanks = false
})

Định dạng tùy chỉnh của các đối số

Đôi khi bạn muốn xóa một số đối số trống nhưng không phải là đối số khác hoặc có lẽ bạn muốn đặt tất cả các đối số vị trí ở dạng chữ thường. Để làm những việc như thế này bạn có thể sử dụng tùy chọn valueFunc. Đầu vào của tùy chọn này phải là một hàm có hai tham số, keyvalue, và trả về một giá trị duy nhất. Giá trị này là những gì bạn sẽ nhận được khi truy cập vào trường key trong bảng args.

Ví dụ 1: hàm này bảo toàn khoảng trắng cho đối số vị trí đầu tiên, nhưng cắt bỏ tất cả các đối số khác và loại bỏ tất cả các đối số trống khác.

local args = getArgs(frame, {
	valueFunc = function (key, value)
		if key == 1 then
			return value
		elseif value then
			value = mw.text.trim(value)
			if value ~= '' then
				return value
			end
		end
		return nil
	end
})

Ví dụ 2: hàm này loại bỏ các đối số trống và chuyển đổi tất cả các đối số thành chữ thường, nhưng không cắt khoảng trắng khỏi các tham số vị trí.

local args = getArgs(frame, {
	valueFunc = function (key, value)
		if not value then
			return nil
		end
		value = mw.ustring.lower(value)
		if mw.ustring.find(value, '%S') then
			return value
		end
		return nil
	end
})

Lưu ý: các chức năng trên sẽ thất bại nếu đầu vào không thuộc loại string hoặc nil. Đây có thể là trường hợp nếu bạn sử dụng chức năng getArgs trong chức năng chính của mô-đun của bạn và chức năng đó được gọi bởi một mô-đun Lua khác. Trong trường hợp này, bạn sẽ cần kiểm tra loại đầu vào của bạn. Đây không phải là vấn đề nếu bạn đang sử dụng một hàm đặc biệt cho các đối số từ #invoke (tức là bạn có chức năng p.mainp._main, hoặc một cái gì đó tương tự).

Ngoài ra, xin lưu ý rằng hàm valueFunc được gọi nhiều hơn hoặc ít hơn mỗi khi một đối số được yêu cầu từ bảng args, vì vậy nếu bạn quan tâm đến hiệu suất mà bạn nên chắc chắn rằng bạn không làm bất cứ điều gì không hiệu quả với mã của bạn.

Khung và khung cha

Đối số trong bảng args có thể được truyền từ khung hiện tại hoặc từ khung chính của nó cùng một lúc. Để hiểu điều này có nghĩa là gì, dễ nhất là đưa ra một ví dụ. Hãy nói rằng chúng ta có một mô-đun gọi là Module:ExampleArgs. Mô-đun này in hai đối số vị trí đầu tiên mà nó được thông qua.

Module:ExampleArgs sau đó được gọi bởi Bản mẫu:ExampleArgs, có chứa đoạn mã {{#invoke:ExampleArgs|main|firstInvokeArg}}. Điều này tạo ra kết quả "firstInvokeArg".

Bây giờ nếu chúng ta gọi Bản mẫu:ExampleArgs, điều sau đây sẽ xảy ra:

Kết quả
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstInvokeArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstInvokeArg secondTemplateArg

Có ba tùy chọn bạn có thể đặt để thay đổi hành vi này: frameOnly, parentOnlyparentFirst. Nếu bạn đặt frameOnly sau đó chỉ các đối số được truyền từ khung hiện tại sẽ được chấp nhận; nếu bạn đặt parentOnly sau đó chỉ các đối số được truyền từ khung cha sẽ được chấp nhận; và nếu bạn đặt parentFirst sau đó các đối số sẽ được truyền từ cả khung hiện tại và khung chính, nhưng khung chính sẽ có mức độ ưu tiên so với khung hiện tại. Dưới đây là kết quả với Bản mẫu:ExampleArgs:

frameOnly
Kết quả
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstInvokeArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstInvokeArg
parentOnly
Kết quả
{{ExampleArgs}}
{{ExampleArgs|firstTemplateArg}} firstTemplateArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstTemplateArg secondTemplateArg
parentFirst
Kết quả
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstTemplateArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstTemplateArg secondTemplateArg

Chú ý:

  1. Nếu bạn đặt cả hai tùy chọn frameOnlyparentOnly, mô-đun sẽ không tìm nạp bất kỳ đối số nào từ #invoke. Đây có lẽ không phải là những gì bạn muốn.
  2. Trong một số trường hợp, khung cha có thể không khả dụng, ví dụ: nếu getArgs thông qua khung cha chứ không phải khung hiện tại. Trong trường hợp này, chỉ các đối số khung sẽ được sử dụng (trừ khi parentOnly được đặt, trong trường hợp đó sẽ không có đối số nào được sử dụng) và tùy chọn parentFirstframeOnly sẽ không có tác dụng.

Wrappers

Tùy chon wrappers được sử dụng để chỉ định một số lượng bản mẫu giới hạn như Bản mẫu wrapper, đó là, các bản mẫu có mục đích duy nhất là gọi một mô-đun. Nếu mô-đun phát hiện ra rằng nó đang được gọi từ một bản mẫu wrappers, nó sẽ chỉ kiểm tra các đối số trong khung cha; nếu không, nó sẽ chỉ kiểm tra các đối số trong khung được truyền cho getArgs. Điều này cho phép các mô-đun được gọi bằng #invoke hoặc thông qua mẫu wrappers mà không làm giảm hiệu suất liên quan đến việc phải kiểm tra cả khung và khung chính cho mỗi lần tra cứu đối số.

Ví dụ: nội dung duy nhất của Template:Side box (trừ nội dung trong cặp <noinclude>...</noinclude>) là {{#invoke:Side box|main}}. Không có điểm nào trong việc kiểm tra các đối số được truyền trực tiếp vào câu lệnh #invoke cho bản mẫu này, vì sẽ không có đối số nào được chỉ định ở đó. Chúng ta có thể tránh việc kiểm tra các đối số được truyền cho #invoke bằng cách sử dụng tùy chọn parentOnly, nhưng nếu chúng ta làm điều này thì #invoke cũng sẽ không hoạt động từ các trang khác. Nếu đây là trường hợp, |text=Some text trong mã {{#invoke:Side box|main|text=Some text}} sẽ bị bỏ qua hoàn toàn, bất kể nó được sử dụng từ trang nào. Bằng cách sử dụng tùy chọn wrappers để cụ thể hóa 'Bản mẫu:Side box' là một wrapper, ta có thể tạo {{#invoke:Side box|main|text=Some text}} hoạt động từ hầu hết các trang, trong khi vẫn không yêu cầu mô-đun kiểm tra các đối số trên chính trang Bản mẫu:Side box.

Wrappers có thể được chỉ định là một chuỗi hoặc là một chuỗi các chuỗi.

local args = getArgs(frame, {
	wrappers = 'Template:Wrapper template'
})


local args = getArgs(frame, {
	wrappers = {
		'Template:Wrapper 1',
		'Template:Wrapper 2',
		-- Bất kì số nào của bản mẫu wrapper cũng có thể thêm vào đây.
	}
})

Chú ý:

  1. Mô-đun sẽ tự động phát hiện nếu nó được gọi từ trang con /sandbox của bản mẫu wrapper, do đó không cần chỉ định rõ ràng các trang sandbox.
  2. Tùy chọn wrappers thay đổi hiệu quả mặc định của tùy chọn frameOnlyparentOnly. Nếu, ví dụ, parentOnly rõ ràng được đặt thành false với set wrappers, các cuộc gọi thông qua các bàn mẫu Wrapper sẽ dẫn đến cả hai đối số khung và đối số được tải, mặc dù các cuộc gọi không qua các mẫu trình bao bọc sẽ chỉ dẫn đến các đối số khung được tải.
  3. Nếu tùy chọn wrappers được đặt và không có khung cha nào khả dụng, mô-đun sẽ luôn nhận được các đối số từ khung được truyền cho getArgs.

Viết vào bảng args

Đôi khi nó có thể hữu ích để viết các giá trị mới vào bảng args. Điều này là có thể với các thiết lập mặc định của mô-đun này. (Tuy nhiên, hãy nhớ rằng thường là phong cách mã hóa tốt hơn để tạo một bảng mới với các giá trị mới của bạn và sao chép các đối số từ bảng args khi cần.)

args.foo = 'some value'

Có thể thay đổi hành vi này với tùy chọn readOnlynoOverwrite. Nếu readOnly được đặt thì không thể ghi bất kỳ giá trị nào vào bảng args. Nếu noOverwrite được đặt, sau đó có thể thêm các giá trị mới vào bảng, nhưng không thể thêm giá trị nếu nó ghi đè lên bất kỳ đối số nào được truyền từ #invoke.

Cập tags Ref

Mô đun này sử dụng metatables để tìm nạp các đối số từ #invoke. Điều này cho phép truy cập vào cả đối số khung và đối số khung cha mà không cần sử dụng hàm chức năng pairs(). Điều này có thể giúp có ích nếu mô-đun của bạn có thể được thông qua tags <ref>...</ref> làm đầu vào.

Ngay khi Tags <ref>...</ref> được truy cập từ Lua, chúng được xử lý bởi phần mềm MediaWiki và tài liệu tham khảo sẽ xuất hiện trong danh sách tham khảo ở cuối bài viết. Nếu mô-đun của bạn tiến hành bỏ qua thẻ tham chiếu từ đầu ra, bạn sẽ kết thúc bằng một tham chiếu ảo - một tham chiếu xuất hiện trong danh sách tham chiếu, nhưng không có số nào liên kết với nó. Đây là một vấn đề với các mô-đun sử dụng pairs() để phát hiện xem nên sử dụng các đối số từ khung hoặc khung cha, vì các mô-đun đó tự động xử lý mọi đối số có sẵn.

Mô-đun này giải quyết vấn đề này bằng cách cho phép truy cập vào cả đối số khung và khung cha, trong khi vẫn chỉ tìm nạp các đối số đó khi cần thiết. Vấn đề vẫn sẽ xảy ra nếu bạn sử dụng pairs(args) ở nơi khác trong mô-đun của bạn, tuy nhiên.

Những hạn chế đã biết

Việc sử dụng các metatables cũng có nhược điểm của nó. Hầu hết các công cụ bảng Lua bình thường sẽ không hoạt động chính xác trên bảng args, bao gồm cả mã hoạt động #, hàm chức năng next(), và các chức năng trong thư viện bảng. Nếu việc sử dụng này là quan trọng cho mô-đun của bạn, bạn nên sử dụng chức năng xử lý đối số của riêng bạn thay vì mô-đun này.

-- This module provides easy processing of arguments passed to Scribunto from
-- #gọi. It is intended for use by other Lua modules, and should not be called
-- from #gọi directly.

local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType

local arguments = {}

-- Generate four different tidyVal functions, so that we don't have to check the
-- options every time we call it.

local function tidyValDefault(key, val)
	if type(val) == 'string' then
		val = mw.text.trim(val)
		if val == '' then
			return nil
		else
			return val
		end
	else
		return val
	end
end

local function tidyValTrimOnly(key, val)
	if type(val) == 'string' then
		return mw.text.trim(val)
	else
		return val
	end
end

local function tidyValRemoveBlanksOnly(key, val)
	if type(val) == 'string' then
		if val:find('%S') then
			return val
		else
			return nil
		end
	else
		return val
	end
end

local function tidyValNoChange(key, val)
	return val
end

local function matchesTitle(given, title)
	local tp = type( given )
	return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title
end

local translate_mt = { __index = function(t, k) return k end }

function arguments.getArgs(frame, options)
	checkType('getArgs', 1, frame, 'table', true)
	checkType('getArgs', 2, options, 'table', true)
	frame = frame or {}
	options = options or {}

	--[[
	-- Set up argument translation.
	--]]
	options.translate = options.translate or {}
	if getmetatable(options.translate) == nil then
		setmetatable(options.translate, translate_mt)
	end
	if options.backtranslate == nil then
		options.backtranslate = {}
		for k,v in pairs(options.translate) do
			options.backtranslate[v] = k
		end
	end
	if options.backtranslate and getmetatable(options.backtranslate) == nil then
		setmetatable(options.backtranslate, {
			__index = function(t, k)
				if options.translate[k] ~= k then
					return nil
				else
					return k
				end
			end
		})
	end

	--[[
	-- Get the argument tables. If we were passed a valid frame object, get the
	-- frame arguments (fargs) and the parent frame arguments (pargs), depending
	-- on the options set and on the parent frame's availability. If we weren't
	-- passed a valid frame object, we are being called from another Lua module
	-- or from the debug console, so assume that we were passed a table of args
	-- directly, and assign it to a new variable (luaArgs).
	--]]
	local fargs, pargs, luaArgs
	if type(frame.args) == 'table' and type(frame.getParent) == 'function' then
		if options.wrappers then
			--[[
			-- The wrappers option makes Module:Arguments look up arguments in
			-- either the frame argument table or the parent argument table, but
			-- not both. This means that users can use either the #invoke syntax
			-- or a wrapper template without the loss of performance associated
			-- with looking arguments up in both the frame and the parent frame.
			-- Module:Arguments will look up arguments in the parent frame
			-- if it finds the parent frame's title in options.wrapper;
			-- otherwise it will look up arguments in the frame object passed
			-- to getArgs.
			--]]
			local parent = frame:getParent()
			if not parent then
				fargs = frame.args
			else
				local title = parent:getTitle():gsub('/sandbox$', '')
				local found = false
				if matchesTitle(options.wrappers, title) then
					found = true
				elseif type(options.wrappers) == 'table' then
					for _,v in pairs(options.wrappers) do
						if matchesTitle(v, title) then
							found = true
							break
						end
					end
				end

				-- We test for false specifically here so that nil (the default) acts like true.
				if found or options.frameOnly == false then
					pargs = parent.args
				end
				if not found or options.parentOnly == false then
					fargs = frame.args
				end
			end
		else
			-- options.wrapper isn't set, so check the other options.
			if not options.parentOnly then
				fargs = frame.args
			end
			if not options.frameOnly then
				local parent = frame:getParent()
				pargs = parent and parent.args or nil
			end
		end
		if options.parentFirst then
			fargs, pargs = pargs, fargs
		end
	else
		luaArgs = frame
	end

	-- Set the order of precedence of the argument tables. If the variables are
	-- nil, nothing will be added to the table, which is how we avoid clashes
	-- between the frame/parent args and the Lua args.
	local argTables = {fargs}
	argTables[#argTables + 1] = pargs
	argTables[#argTables + 1] = luaArgs

	--[[
	-- Generate the tidyVal function. If it has been specified by the user, we
	-- use that; if not, we choose one of four functions depending on the
	-- options chosen. This is so that we don't have to call the options table
	-- every time the function is called.
	--]]
	local tidyVal = options.valueFunc
	if tidyVal then
		if type(tidyVal) ~= 'function' then
			error(
				"giá trị không hợp lệ được đặt cho tùy chọn “valueFunc” "
					.. "(mong đợi hàm nhưng gặp "
					.. type(tidyVal)
					.. ')',
				2
			)
		end
	elseif options.trim ~= false then
		if options.removeBlanks ~= false then
			tidyVal = tidyValDefault
		else
			tidyVal = tidyValTrimOnly
		end
	else
		if options.removeBlanks ~= false then
			tidyVal = tidyValRemoveBlanksOnly
		else
			tidyVal = tidyValNoChange
		end
	end

	--[[
	-- Set up the args, metaArgs and nilArgs tables. args will be the one
	-- accessed from functions, and metaArgs will hold the actual arguments. Nil
	-- arguments are memoized in nilArgs, and the metatable connects all of them
	-- together.
	--]]
	local args, metaArgs, nilArgs, metatable = {}, {}, {}, {}
	setmetatable(args, metatable)

	local function mergeArgs(tables)
		--[[
		-- Accepts multiple tables as input and merges their keys and values
		-- into one table. If a value is already present it is not overwritten;
		-- tables listed earlier have precedence. We are also memoizing nil
		-- values, which can be overwritten if they are 's' (soft).
		--]]
		for _, t in ipairs(tables) do
			for key, val in pairs(t) do
				if metaArgs[key] == nil and nilArgs[key] ~= 'h' then
					local tidiedVal = tidyVal(key, val)
					if tidiedVal == nil then
						nilArgs[key] = 's'
					else
						metaArgs[key] = tidiedVal
					end
				end
			end
		end
	end

	--[[
	-- Define metatable behaviour. Arguments are memoized in the metaArgs table,
	-- and are only fetched from the argument tables once. Fetching arguments
	-- from the argument tables is the most resource-intensive step in this
	-- module, so we try and avoid it where possible. For this reason, nil
	-- arguments are also memoized, in the nilArgs table. Also, we keep a record
	-- in the metatable of when pairs and ipairs have been called, so we do not
	-- run pairs and ipairs on the argument tables more than once. We also do
	-- not run ipairs on fargs and pargs if pairs has already been run, as all
	-- the arguments will already have been copied over.
	--]]

	metatable.__index = function (t, key)
		--[[
		-- Fetches an argument when the args table is indexed. First we check
		-- to see if the value is memoized, and if not we try and fetch it from
		-- the argument tables. When we check memoization, we need to check
		-- metaArgs before nilArgs, as both can be non-nil at the same time.
		-- If the argument is not present in metaArgs, we also check whether
		-- pairs has been run yet. If pairs has already been run, we return nil.
		-- This is because all the arguments will have already been copied into
		-- metaArgs by the mergeArgs function, meaning that any other arguments
		-- must be nil.
		--]]
		if type(key) == 'string' then
			key = options.translate[key]
		end
		local val = metaArgs[key]
		if val ~= nil then
			return val
		elseif metatable.donePairs or nilArgs[key] then
			return nil
		end
		for _, argTable in ipairs(argTables) do
			local argTableVal = tidyVal(key, argTable[key])
			if argTableVal ~= nil then
				metaArgs[key] = argTableVal
				return argTableVal
			end
		end
		nilArgs[key] = 'h'
		return nil
	end

	metatable.__newindex = function (t, key, val)
		-- This function is called when a module tries to add a new value to the
		-- args table, or tries to change an existing value.
		if type(key) == 'string' then
			key = options.translate[key]
		end
		if options.readOnly then
			error(
				'không thể ghi vào bảng đối số cho chìa khóa “'
					.. tostring(key)
					.. '”; bảng là chỉ-đọc',
				2
			)
		elseif options.noOverwrite and args[key] ~= nil then
			error(
				'không thể ghi vào bảng đối số cho chìa khóa “'
				.. tostring(key)
				.. '”; không thể ghi đè các đối số đã tồn tại',
				2
			)
		elseif val == nil then
			--[[
			-- If the argument is to be overwritten with nil, we need to erase
			-- the value in metaArgs, so that __index, __pairs and __ipairs do
			-- not use a previous existing value, if present; and we also need
			-- to memoize the nil in nilArgs, so that the value isn't looked
			-- up in the argument tables if it is accessed again.
			--]]
			metaArgs[key] = nil
			nilArgs[key] = 'h'
		else
			metaArgs[key] = val
		end
	end

	local function translatenext(invariant)
		local k, v = next(invariant.t, invariant.k)
		invariant.k = k
		if k == nil then
			return nil
		elseif type(k) ~= 'string' or not options.backtranslate then
			return k, v
		else
			local backtranslate = options.backtranslate[k]
			if backtranslate == nil then
				-- Skip this one. This is a tail call, so this won't cause stack overflow
				return translatenext(invariant)
			else
				return backtranslate, v
			end
		end
	end

	metatable.__pairs = function ()
		-- Called when pairs is run on the args table.
		if not metatable.donePairs then
			mergeArgs(argTables)
			metatable.donePairs = true
		end
		return translatenext, { t = metaArgs }
	end

	local function inext(t, i)
		-- This uses our __index metamethod
		local v = t[i + 1]
		if v ~= nil then
			return i + 1, v
		end
	end

	metatable.__ipairs = function (t)
		-- Called when ipairs is run on the args table.
		return inext, t, 0
	end

	return args
end

return arguments