Documentation[créer] [purger]
--[[
---------------------------------------------------------------------------------
-- Iterating primitives 
---------------------------------------------------------------------------------


Base functions to work on stateful lua iterators.

Function that creates iterators, like "pair" and "ipair", but stateful unlike them

-- May be building a duplicate of https://www.wikidata.org/wiki/Module:Luafun 

--]]

local p = {}
----------------------------------------

-- Copied From Luafun


local methods = {} -- collects the methods to append to an iterator object

local register_method = (function(module, methods)
	return function(name, func)
		module[name] = func
		methods[name] = func
	end
end)(p, methods)


-- the metatable for an iterator object
local iterator_mt = {
    -- usually called by for-in loop
    __call = function(self, param, state)
        return self.gen(param, state)
    end;
    __tostring = function(self)
        return '<generator>'
    end;
    -- add all exported methods
    __index = methods;
}

-- used to change an iterator function to an iterator objects to allow to attach methods to an iterator
local wrap = function(gen, param, state)
    return setmetatable({
        gen = gen,
        param = param,
        state = state
    }, iterator_mt), param, state
end
p.wrap = wrap

local method0 = function(fun)
    return function(self)
        return fun(self.gen, self.param, self.state)
    end
end


local methodn = function(fun)
    return function(self, ...) 
       	return fun(self.gen, ...)
    end
end

--------------------------------------------------------



-- iterator constructor. Transforms an iterator over a sequence of values in 
-- an iterator on the result of the "value_constructor" function applied to the initial values
-- (a kind of an equivalent of the functional "map" function that works on iterator instead of list)

-- this iterator works on values and ignore the keys
local function map(it, transformation)
	assert(it, "map : no iterator provided")
	return wrap(
		function()
			local val = it()
			if val then return transformation(val) end
		end
	)
end

register_method("map", map)

-- like "map" except it works on pairs of values (usually key/val pairs)

-- this iterator works on pairs
local function pair_map(it, transformation)
	assert(it, "pair_map : no iterator provided")
	
	return wrap(
		function()
			local i, val = it()
			if i then return transformation(i, val) end
		end
	)
end

register_method("pair_map",pair_map)

-- iterates on the values of another iterators and yield only the values that pass the criteria
-- (a kind of an equivalent of the functional "filter" function that works on iterator instead of list)

-- this iterator works on values
local function filter(it, criteria)
	assert(it, "filter : no iterator provided")
	assert(type(criteria)=="function", "no criteria provided")
	
	return wrap(
		function()
			local val = it()
			while val and not(criteria(val)) do
				val = it()
			end
			return val
		end
	)
end

register_method("filter", filter)

-- pair version of the previous function

--this iterators works on pairs
local function pair_filter(it, criteria)
	assert(it, "pair_filter : no iterator provided")
	return wrap(
		function()
			local i, val = it()
			while val and not(criteria(i, val)) do
				i, val = it()
			end
			return i, val
		end
	)
end

register_method("pair_filter", pair_filter)

--creates a value only iterator from a "pair" one, yielding only the "keys" (first item of the pair)

--this iterators works on pairs
local function select_keys(it)
	assert(it, "select_keys : no iterator provided")
	return wrap(
		function()
			local i, val = it()
			return i
		end
	)
end

register_method("select_keys", select_keys)

--creates a value only iterator from a "pair" one, yielding only the "values" (second item of the pair)

--this iterators works on pairs
local function select_vals(it)
	assert(it, "pair_vals : no iterator provided")
	return wrap(
		function()
			local i, val = it()
			return val
		end
	)
end

p.select_vals = select_vals

-- create a stateful iterators that iterates on the values of a table
-- (from the stateless standard "pairs" iterator on tables)
local function on_vals(tabl)
    local _f, _s, _v  = pairs(tabl)
	return wrap(
		function()
			if _s then
				local i, res = _f(_s, _v)
				_v = i
				if not res then _s = nil end
				return res
			end
		end
	)
end
p.on_vals = on_vals

-- create a stateful iterators that iterates over the keys of a table
-- (from the stateless standard "pairs" iterator on tables)
local function on_pairs(tabl)
    local _f, _s, _v  = pairs(tabl)
    
    
	return --wrap(
		function()
			if _s then
				local i, res = _f(_s, _v)
				_v = i
				if not res then _s = nil end
				return i, res
			end
		end
	--)
end
p.on_pairs = on_pairs


-- equivalent of the "join" operation, with join({{"a"},{},{"b","c"}}) = {"a","b","c"} 
-- for iterators.
-- if the parameter "it" is an iterator that yields {"a"} ; then {} ; then {"b","c"} 
-- and "creator" is a function that creates an iterator that yields "b" then "c" from the table {"b","c"}
-- the "flatten"-ing of this parameter will yield "a" then "b" then "c"

local function flatten(it, creator)
	assert(it, "flatten : no iterator provided")
	assert(creator, "flatten : no iterator creator provided")
	
	local main_val = it()
	
	if main_val then
		local sub_it = creator(main_val)
		return wrap(
			function()
				if main_val then
					local val = nil
					while not val and main_val do
						if sub_it then
							val = sub_it()
						end
						if not val then
							main_val = it()
							if not main_val then return end
							sub_it = creator(main_val)
						end
					end
					return val
				end
			end
		)
	else
		return wrap(function () return nil end)
	end
end

register_method("flatten", flatten)

-- equivalent of list concatenation for iterators

local chain = function (it1, it2)
	return wrap(
		function()
			local res = it1() or it2()
			return res
		end
	)
end

register_method("chain", chain)


-- creates an iterator on a single value

p.singleton = function (val)
	local iterated
	return wrap(function()
		if not iterated then
			iterated = true
			return val
		end
	end)
end

local function fold(it, acc, init)
	local accum = init
	for res in it do
		accum = acc(res, accum)
	end
	return accum
end

register_method("fold", fold)

local function totable(it)
	return fold(
		it, 
		function (val, tabl)
			table.insert(tabl, val)
			return tabl
		end,
		{}
	)
end

register_method("totable", totable)

function p.range(start_i, end_i, step)
	local i = nil
	step = step or 1
	
	assert(step ~= 0)
	
	local direction = step/math.abs(step)
	
	return wrap(function()
		if not i then 
			i = start_i
		else
			i = i + step
		end
		if i * direction < end_i * direction then 
			return i
		else
			return
		end
	end)
end

--------------------------------------------------------------------------------
-- TESTING FUNCTIONS
--------------------------------------------------------------------------------

function p.execute(iterator)
	for x in iterator do
		mw.log(x)
	end
end

function p.execute_pair(iterator)
	for x, y in iterator do
		mw.log(x, y)
	end
end

return p