Module:FunList/Iterators: Difference between revisions
From Melvor Idle
No edit summary |
No edit summary |
||
Line 24: | Line 24: | ||
self.current = nil | self.current = nil | ||
self.index = nil | self.index = nil | ||
-- Assume by default we are not dealing with a simple array | |||
self.isArray = false | |||
return self | return self | ||
end | end | ||
Line 31: | Line 33: | ||
end | end | ||
function Enumerator:getEnumerator() | function Enumerator:getEnumerator(startIndex) | ||
error('Not implemented in base class.') | error('Not implemented in base class.') | ||
end | end | ||
Line 38: | Line 40: | ||
function Enumerator:overridePairs(startIndex) | function Enumerator:overridePairs(startIndex) | ||
-- Get or create clean enumerator. This ensures the state is 0. | -- Get or create clean enumerator. This ensures the state is 0. | ||
local enum = self:getEnumerator() | local enum = self:getEnumerator(startIndex) | ||
enum.current = nil | enum.current = nil | ||
enum.index = startIndex | enum.index = startIndex | ||
Line 56: | Line 58: | ||
local TableEnumerator = setmetatable({}, { __index = Enumerator }) | local TableEnumerator = setmetatable({}, { __index = Enumerator }) | ||
TableEnumerator.__index = TableEnumerator | TableEnumerator.__index = TableEnumerator | ||
TableEnumerator.__pairs = Enumerator_mt.__pairs | TableEnumerator.__pairs = Enumerator_mt.__pairs | ||
TableEnumerator.__ipairs = Enumerator_mt.__ipairs | |||
function TableEnumerator.new(tbl) | function TableEnumerator.new(tbl) | ||
Line 63: | Line 66: | ||
self.tbl = tbl | self.tbl = tbl | ||
self.state = 0 | self.state = 0 | ||
return self | return self | ||
Line 71: | Line 73: | ||
if self.state == 0 then | if self.state == 0 then | ||
self.state = 1 | self.state = 1 | ||
end | end | ||
if self. | if self.isArray == true then | ||
-- Iterate using ipairs, starting from index 1 | -- Iterate using ipairs, starting from index 1 | ||
self.index = self.index + 1 | self.index = self.index + 1 | ||
Line 93: | Line 94: | ||
end | end | ||
function TableEnumerator:getEnumerator() | -- startIndex is used to determine if the underlying table should be treated | ||
-- as an array or as a mixed table. It is ignored in the other enumerators as | |||
-- they just call moveNext on the enumerator instead. | |||
function TableEnumerator:getEnumerator(startIndex) | |||
local instance = nil | |||
if self.state == 0 then | if self.state == 0 then | ||
instance = self | |||
else | else | ||
instance = TableEnumerator.new(self.tbl) | |||
end | end | ||
mw.log(startIndex == 0) | |||
instance.isArray = startIndex == 0 | |||
return instance | |||
end | end | ||
Revision as of 23:12, 21 July 2024
Documentation for this module may be created at Module:FunList/Iterators/doc
-- Helper Functions --
local function isType(obj, class)
local mt = getmetatable(obj)
while mt do
if mt == class then
return true
end
mt = getmetatable(mt)
end
return false
end
-- BASE ENUMERATOR CLASS --
--enumerable = {}
local Enumerator = {}
local Enumerator_mt = {
__index = Enumerator,
__pairs = function(t) return t:overridePairs() end,
__ipairs = function(t) return t:overridePairs(0) end
}
function Enumerator.new()
local self = setmetatable({}, Enumerator_mt)
self.current = nil
self.index = nil
-- Assume by default we are not dealing with a simple array
self.isArray = false
return self
end
function Enumerator:moveNext()
error('Not implemented in base class.')
end
function Enumerator:getEnumerator(startIndex)
error('Not implemented in base class.')
end
-- Hooks the moveNext function into the Lua 'pairs' function
function Enumerator:overridePairs(startIndex)
-- Get or create clean enumerator. This ensures the state is 0.
local enum = self:getEnumerator(startIndex)
enum.current = nil
enum.index = startIndex
local function iterator(t, k)
if enum:moveNext() == true then
return enum.index, enum.current
end
return nil, nil
end
return iterator, enum, enum.index
end
-- TABLE ENUMERATOR CLASS --
-- This is essentially a wrapper for the table object,
-- since it provides no state machine esque iteration out of the box
local TableEnumerator = setmetatable({}, { __index = Enumerator })
TableEnumerator.__index = TableEnumerator
TableEnumerator.__pairs = Enumerator_mt.__pairs
TableEnumerator.__ipairs = Enumerator_mt.__ipairs
function TableEnumerator.new(tbl)
assert(tbl, 'Table value cannot be nil')
local self = setmetatable(Enumerator.new(), TableEnumerator)
self.tbl = tbl
self.state = 0
return self
end
function TableEnumerator:moveNext()
if self.state == 0 then
self.state = 1
end
if self.isArray == true then
-- Iterate using ipairs, starting from index 1
self.index = self.index + 1
self.current = self.tbl[self.index]
return self.current ~= nil
else
-- Iterate using pairs
local key = self.index
key = next(self.tbl, key)
self.index = key
if key ~= nil then
self.current = self.tbl[key]
return true
end
end
return false
end
-- startIndex is used to determine if the underlying table should be treated
-- as an array or as a mixed table. It is ignored in the other enumerators as
-- they just call moveNext on the enumerator instead.
function TableEnumerator:getEnumerator(startIndex)
local instance = nil
if self.state == 0 then
instance = self
else
instance = TableEnumerator.new(self.tbl)
end
mw.log(startIndex == 0)
instance.isArray = startIndex == 0
return instance
end
-- SELECT ENUMERATOR --
local SelectEnumerator = setmetatable({}, { __index = Enumerator })
SelectEnumerator.__index = SelectEnumerator
SelectEnumerator.__pairs = Enumerator_mt.__pairs
function SelectEnumerator.new(source, selector)
local self = setmetatable(Enumerator.new(), SelectEnumerator)
self.state = 0
self.source = source
self.selector = selector
self.enumerator = nil
self.position = 0
return self
end
function SelectEnumerator:moveNext()
if self.state == 0 then
self.state = 1
self.position = 0
self.enumerator = self.source:getEnumerator()
end
if self.state == 1 then
local enumerator = self.enumerator
if enumerator:moveNext() == true then
local sourceElement = enumerator.current
self.index = enumerator.index -- Preserve index
self.position = self.position + 1
self.current = self.selector(sourceElement, self.position)
assert(self.current, 'Selected value must be non-nil')
return true
end
end
return false
end
function SelectEnumerator:getEnumerator()
if self.state == 0 then
return self
else
return SelectEnumerator.new(self.source, self.selector)
end
end
-- WHERE ENUMERATOR --
local WhereEnumerator = setmetatable({}, { __index = Enumerator })
WhereEnumerator.__index = WhereEnumerator
WhereEnumerator.__pairs = Enumerator_mt.__pairs
function WhereEnumerator.new(source, predicate)
local self = setmetatable(Enumerator.new(), WhereEnumerator)
self.state = 0
self.source = source
self.predicate = predicate
self.enumerator = nil
return self
end
function WhereEnumerator:moveNext()
if self.state == 0 then
self.state = 1
self.position = 0
self.enumerator = self.source:getEnumerator()
end
if self.state == 1 then
local enumerator = self.enumerator
while enumerator:moveNext() == true do
local sourceElement = enumerator.current
if self.predicate(sourceElement) == true then
self.index = enumerator.index
self.current = sourceElement
return true
end
end
end
return false
end
function WhereEnumerator:getEnumerator()
if self.state == 0 then
return self
else
return WhereEnumerator.new(self.source, self.predicate)
end
end
-- WHERE ENUMERATOR --
local SelectManyEnumerator = setmetatable({}, { __index = Enumerator })
SelectManyEnumerator.__index = SelectManyEnumerator
SelectManyEnumerator.__pairs = Enumerator_mt.__pairs
function SelectManyEnumerator.new(source, selector)
local self = setmetatable(Enumerator.new(), SelectManyEnumerator)
self.state = 0
self.source = source
self.selector = selector
self.position = 0
self.enumerator = nil -- Enumerator of the source Enumerable
self.sourceEnumerator = nil -- Enumerator of the nested Enumerable
return self
end
function SelectManyEnumerator:moveNext()
while true do
-- Setup state
if self.state == 0 then
self.position = 0
self.enumerator = self.source:getEnumerator()
self.state = -3 -- signal to get (first) nested enumerator
end
-- Grab next value from nested enumerator
if self.state == -4 then
if self.sourceEnumerator:moveNext() then
self.current = self.sourceEnumerator.current
self.index = self.sourceEnumerator.index
self.state = -4 -- signal to get next item
return true
else
self.state = -3 -- signal to get next enumerator
end
end
-- Grab nest nested enumerator
if self.state == -3 then
if self.enumerator:moveNext() then
local current = self.enumerator.current
self.position = self.position + 1
local sourceTable = self.selector(current, self.position)
if not isType(sourceTable, Enumerator) then
-- We need to turn the nested table into an enumerator
self.sourceEnumerator = TableEnumerator.new(sourceTable):getEnumerator()
else
self.sourceEnumerator = sourceTable
end
self.state = -4 -- signal to get next item
else
-- enumerator doesn't have any more nested enumerators.
return false
end
end
end
end
function SelectManyEnumerator:getEnumerator()
if self.state == 0 then
return self
else
return SelectManyEnumerator.new(self.source, self.predicate)
end
end
return {
Enumerator = Enumerator,
TableEnumerator = TableEnumerator,
SelectEnumerator = SelectEnumerator,
WhereEnumerator = WhereEnumerator,
SelectManyEnumerator = SelectManyEnumerator
}