I am trying to implement a custom iterator which works like table.foreach
in Lua, but in more "syntax friendly" manner. I came across following construction (not sure if it's done purely in Lua or some kind of C "hack" in the Lua source code.
-- creature is a collection of Creature objects
-- "ps" is used as a filter variable
foreach creature cre "ps" do
if cre.name ~= $name then
-- code...
end
end
I tried to play around with table.foreach
, ipairs
and so on - but can nowhere get the similar result to the mentioned code sample. Could you give me a hint how this could be implemented?
A new keyword requires either a preproccesor or modifying the language's grammar directly.
An approximation of this syntax can already be easily achieved without leaving the confines of the language by writing an iterator - a function that when provided to the generic for
continuously supplies values for the loop variables, until the first value it returns is nil.
(More specifically, you usually write a function that creates (returns) an iterator. pairs
and ipairs
are examples of this.)
next
is a function that when provided a table and a key, returns the next key and its associated value.
You can extend this with metatables and the __call
metamethod to shorten the syntax required to construct the iterator (properly constructed classes / objects / prototypes (i.e., OOP) in Lua already rely heavily on metatables).
All this means that the syntax
for value in collection 'filter' do
print(value)
end
is very achievable.
The full example, with a very basic iterator:
-- This is a live, proxied view into an existing table
local function View(t)
local methods = {}
local function match(value, filter)
return type(value) == 'string' and value:match(filter)
end
function methods:filter(filter)
local key, value
return function (_)
repeat
key, value = next(t, key)
until value == nil or match(value, filter)
return value, key
end
end
return setmetatable({}, {
__index = methods,
__metatable = false,
__call = methods.filter
})
end
local foo = View { 'c', 'a', 'b', 'a', 'c', 'd' }
-- :filter via __call
for value in foo '[ab]' do
print(value)
end
print('-------')
-- explicitly
for value in foo:filter '[cd]' do
print(value)
end
print('-------')
-- additional index variable
-- this is the inverse of the more typical `pairs` construct
for value, key in foo '[ad]' do
print(key)
end
a
b
a
-------
c
c
d
-------
2
4
6