local B={}function B.typeValid(B)return type(B)~="\117\115\101\114\100\097\116\097",typeof(B)end function B.scanValidity(r,z,n)if type(r)~="\116\097\098\108\101"then return B.scanValidity({input=r},{},{})end z,n=z or{},n or{"\105\110\112\117\116"}z[r]=true local t do local B=next(r)if type(B)=="\110\117\109\098\101\114"then t="\065\114\114\097\121"else t="\068\105\099\116\105\111\110\097\114\121"end end local J=0 for r,m in next,r do n[#n+1]=tostring(r)if type(r)=="\110\117\109\098\101\114"then if t=="\068\105\099\116\105\111\110\097\114\121"then return false,n,"\077\105\120\101\100 \065\114\114\097\121\047\068\105\099\116\105\111\110\097\114\121"elseif r%1~=0 then return false,n,"\078\111\110-\105\110\116\101\103\101\114 \105\110\100\101\120"elseif r==math.huge or r==-math.huge then return false,n,"\040-\041\073\110\102\105\110\105\116\121 \105\110\100\101\120"end elseif type(r)~="\115\116\114\105\110\103"then return false,n,"\078\111\110-\115\116\114\105\110\103 \107\101\121",typeof(r)elseif t=="\065\114\114\097\121"then return false,n,"\077\105\120\101\100 \065\114\114\097\121\047\068\105\099\116\105\111\110\097\114\121"end if t=="\065\114\114\097\121"then if J~=r-1 then return false,n,"\065\114\114\097\121 \119\105\116\104 \110\111\110-\115\101\113\117\101\110\116\105\097\108 \105\110\100\101\120\101\115"end J=r end local w,x=B.typeValid(m)if not w then return false,n,"\073\110\118\097\108\105\100 \116\121\112\101",x end if type(m)=="\116\097\098\108\101"then if z[m]then return false,n,"\067\121\099\108\105\099"end local r,t,J,w=B.scanValidity(m,z,n)if not r then return r,t,J,w end end n[#n]=nil end z[r]=nil return true end function B.getStringPath(B)return table.concat(B,"\046")end function B.testValidity(r)local z,n,t,J=B.scanValidity(r)if not z then if J then return"\073\110\118\097\108\105\100 \097\116 "..(B.getStringPath(n)..(" \098\101\099\097\117\115\101\058 "..(t..(" \040"..(tostring(J).."\041")))))else return"\073\110\118\097\108\105\100 \097\116 "..(B.getStringPath(n)..(" \098\101\099\097\117\115\101\058 "..t))end end end return B


local Verifier = {}

function Verifier.typeValid(data)
	return type(data) ~= "userdata", typeof(data)
end

function Verifier.scanValidity(tbl, passed, path)
	if type(tbl) ~= "table" then
		return Verifier.scanValidity({input = tbl}, {}, {})
	end
	passed, path = passed or {}, path or {"input"}
	passed[tbl] = true
	local tblType
	do
		local key = next(tbl)
		if type(key) == "number" then
			tblType = "Array"
		else
			tblType = "Dictionary"
		end
	end
	local last = 0
	for key, value in next, tbl do
		path[#path + 1] = tostring(key)
		if type(key) == "number" then
			if tblType == "Dictionary" then
				return false, path, "Mixed Array/Dictionary"
			elseif key%1 ~= 0 then  -- if not an integer
				return false, path, "Non-integer index"
			elseif key == math.huge or key == -math.huge then
				return false, path, "(-)Infinity index"
			end
		elseif type(key) ~= "string" then
			return false, path, "Non-string key", typeof(key)
		elseif tblType == "Array" then
			return false, path, "Mixed Array/Dictionary"
		end
		if tblType == "Array" then
			if last ~= key - 1 then
				return false, path, "Array with non-sequential indexes"
			end
			last = key
		end
		local isTypeValid, valueType = Verifier.typeValid(value)
		if not isTypeValid then
			return false, path, "Invalid type", valueType
		end
		if type(value) == "table" then
			if passed[value] then
				return false, path, "Cyclic"
			end
			local isValid, keyPath, reason, extra = Verifier.scanValidity(value, passed, path)
			if not isValid then
				return isValid, keyPath, reason, extra
			end
		end
		path[#path] = nil
	end
	passed[tbl] = nil
	return true
end

function Verifier.getStringPath(path)
	return table.concat(path, ".")
end

function Verifier.testValidity(input)
	local isValid, keyPath, reason, extra = Verifier.scanValidity(input)
	if not isValid then
		if extra then
			return "Invalid at "..Verifier.getStringPath(keyPath).." because: "..reason.." ("..tostring(extra)..")"
		else
			return "Invalid at "..Verifier.getStringPath(keyPath).." because: "..reason
		end
	end
end

return Verifier
