-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
-- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes
print('testing iteration')

-- basic for loop tests
do
  local a
  for a,b in pairs{} do error("not here") end
  for i=1,0 do error("not here") end
  for i=0,1,-1 do error("not here") end
  a = nil; for i=1,1 do assert(not a); a=1 end; assert(a)
  a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a)
  a = 0; for i=0, 1, 0.1 do a=a+1 end; assert(a==11)
end

-- precision tests for for loops
do
  local a
  --a = 0; for i=1, 0, -0.01 do a=a+1 end; assert(a==101)
  a = 0; for i=0, 0.999999999, 0.1 do a=a+1 end; assert(a==10)
  a = 0; for i=1, 1, 1 do a=a+1 end; assert(a==1)
  a = 0; for i=1e10, 1e10, -1 do a=a+1 end; assert(a==1)
  a = 0; for i=1, 0.99999, 1 do a=a+1 end; assert(a==0)
  a = 0; for i=99999, 1e5, -1 do a=a+1 end; assert(a==0)
  a = 0; for i=1, 0.99999, -1 do a=a+1 end; assert(a==1)
end

-- for loops do string->number coercion
do
  local a = 0; for i="10","1","-2" do a=a+1 end; assert(a==5)
end

-- generic for with function iterators
do
  local function f (n, p)
    local t = {}; for i=1,p do t[i] = i*10 end
    return function (_,n)
             if n > 0 then
               n = n-1
               return n, unpack(t)
             end
           end, nil, n
  end

  local x = 0
  for n,a,b,c,d in f(5,3) do
    x = x+1
    assert(a == 10 and b == 20 and c == 30 and d == nil)
  end
  assert(x == 5)
end

-- generic for with __call (tables)
do
  local f = {}
  setmetatable(f, { __call = function(_, _, n) if n > 0 then return n - 1 end end })

  local x = 0
  for n in f, nil, 5 do
    x += n
  end
  assert(x == 10)
end

-- generic for with __call (userdata)
do
  local f = newproxy(true)
  getmetatable(f).__call = function(_, _, n) if n > 0 then return n - 1 end end

  local x = 0
  for n in f, nil, 5 do
    x += n
  end
  assert(x == 10)
end

-- generic for with pairs
do
  local x = 0
  for k, v in pairs({a = 1, b = 2, c = 3}) do
    x += v
  end
  assert(x == 6)
end

-- generic for with pairs with holes
do
  local x = 0
  for k, v in pairs({1, 2, 3, nil, 5}) do
    x += v
  end
  assert(x == 11)
end

-- generic for with ipairs
do
  local x = 0
  for k, v in ipairs({1, 2, 3, nil, 5}) do
    x += v
  end
  assert(x == 6)
end

-- generic for with __iter (tables)
do
  local f = {}
  setmetatable(f, { __iter = function(x)
    assert(f == x)
    return next, {1, 2, 3, 4}
  end })

  local x = 0
  for n in f do
    x += n
  end
  assert(x == 10)
end

-- generic for with __iter (userdata)
do
  local f = newproxy(true)
  getmetatable(f).__iter = function(x)
    assert(f == x)
    return next, {1, 2, 3, 4}
  end

  local x = 0
  for n in f do
    x += n
  end
  assert(x == 10)
end

-- generic for with tables (dictionary)
do
  local x = 0
  for k, v in {a = 1, b = 2, c = 3} do
    print(k, v)
    x += v
  end
  assert(x == 6)
end

-- generic for with tables (arrays)
do
  local x = ''
  for k, v in {1, 2, 3, nil, 5} do
    x ..= tostring(v)
  end
  assert(x == "1235")
end

-- generic for with tables (mixed)
do
  local x = 0
  for k, v in {1, 2, 3, nil, 5, a = 1, b = 2, c = 3} do
    x += v
  end
  assert(x == 17)
end

-- generic for over a non-iterable object
do
  local ok, err = pcall(function() for x in 42 do end end)
  assert(not ok and err:match("attempt to iterate"))
end

-- generic for over an iterable object that doesn't return a function
do
  local obj = {}
  setmetatable(obj, { __iter = function() end })

  local ok, err = pcall(function() for x in obj do end end)
  assert(not ok and err:match("attempt to call a nil value"))
end

-- it's okay to iterate through a table with a single variable
do
  local x = 0
  for k in {1, 2, 3, 4, 5} do
    x += k
  end
  assert(x == 15)
end

-- all extra variables should be set to nil during builtin traversal
do
  local x = 0
  for k,v,a,b,c,d,e in {1, 2, 3, 4, 5} do
    x += k
    assert(a == nil and b == nil and c == nil and d == nil and e == nil)
  end
  assert(x == 15)
end

-- pairs/ipairs/next may be substituted through getfenv
-- however, they *must* be substituted with functions - we don't support them falling back to generalized iteration
function testgetfenv()
  local env = getfenv(1)
  env.pairs = function() return "nope" end
  env.ipairs = function() return "nope" end
  env.next = {1, 2, 3}

  local ok, err = pcall(function() for k, v in pairs({}) do end end)
  assert(not ok and err:match("attempt to iterate over a string value"))

  local ok, err = pcall(function() for k, v in ipairs({}) do end end)
  assert(not ok and err:match("attempt to iterate over a string value"))

  local ok, err = pcall(function() for k, v in next, {} do end end)
  assert(not ok and err:match("attempt to iterate over a table value"))
end

testgetfenv() -- DONT MOVE THIS LINE

return"OK"
