0

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?

2
  • 1
    This is a syntax error in Lua; it can't be implemented in plain Lua. You could add a foreach keyword to the tokenizer, a production to the grammer etc. just to have some syntactic sugar that will stump every tool that supports Lua. Is it really worth it? Why is a standard Lua for loop like for cre in creature:filter"ps" do ... end not good enough? I find it more readable, and since it is just regular Lua, it will play nice with whatever tools you choose to use. Commented Sep 21, 2023 at 9:05
  • @Oka I would say brief - hence this question, if it's doable to implement this foreach keyword with iterators/next. Commented Sep 21, 2023 at 9:29

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

2 Comments

Is __newindex = false a fast-and-dirty way to implement read-only?
Uh, sort of - the runtime will explode if you attempt to plainly register a new index in that table :). rawset bypasses that. That part of the example isn't important, but I probably meant __metatable = false so that methods cannot be changed/referenced via the proxy/view (getametatable). I'll update the wording as its not exactly read-only.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.