jbe@0: seqlua: Extended sequences and iterators in Lua jbe@0: =============================================== jbe@0: jbe@0: This is an experimental package to extend Lua in the following manner: jbe@0: jbe@0: * allow ipairs(...) to accept tables as well as functions or iterator triplets, jbe@0: * provide a function iterator(...) that returns single functions unmodified, jbe@0: but converts jbe@0: * iterator triplets into closures, and jbe@0: * tables into a function closure that iterates over the elements, jbe@0: * provide the auxiliary C functions and macros to simplify iterating over both jbe@0: tables and iterator functions with the same statement. jbe@0: jbe@0: This library completely ignores the ``__ipairs`` metamethod (as it is jbe@0: deprecated since Lua 5.3.0-alpha). It respects, however, any ``__call`` jbe@0: metamethods (this may cause unexpected behavior when passing callable tables jbe@0: to ``ipairs``). jbe@0: jbe@0: jbe@0: jbe@0: Lua part of the library jbe@0: ----------------------- jbe@0: jbe@0: The new ``ipairs(...)`` function works as follows: jbe@0: jbe@0: require "seqlua" jbe@0: jbe@0: t = {"a", "b", "c"} jbe@0: jbe@0: for i, v in ipairs(t) do jbe@0: print(i, v) jbe@0: end jbe@0: -- prints: jbe@0: -- 1 a jbe@0: -- 2 b jbe@0: -- 3 c jbe@0: jbe@8: function alphabet(from, to) jbe@0: local letter = nil jbe@0: return function() jbe@0: if letter == nil then jbe@8: letter = from jbe@8: elseif letter == to then jbe@0: return nil jbe@0: else jbe@0: letter = string.char(string.byte(letter) + 1) jbe@0: end jbe@0: return letter jbe@0: end jbe@0: end jbe@0: jbe@8: f = alphabet("a", "z") jbe@0: jbe@0: for i, v in ipairs(f) do jbe@0: print(i, v) jbe@0: end jbe@0: -- prints: jbe@0: -- 1 a jbe@0: -- 2 b jbe@0: -- 3 c jbe@0: -- ... jbe@0: -- 25 y jbe@0: -- 26 z jbe@0: jbe@8: c = setmetatable( jbe@8: { iter = alphabet("a", "f") }, jbe@8: { __call = function(t) return t.iter() end } jbe@8: ) jbe@8: jbe@8: for i, v in ipairs(c) do jbe@8: print(i, v) jbe@8: end jbe@8: -- prints: jbe@8: -- 1 a jbe@8: -- 2 b jbe@8: -- 3 c jbe@10: -- 4 d jbe@10: -- 5 e jbe@10: -- 6 f jbe@8: jbe@13: g = coroutine.wrap(function() jbe@12: coroutine.yield("Alice") jbe@12: coroutine.yield("Bob") jbe@12: for i = 1, 3 do jbe@12: coroutine.yield("Person #" .. tostring(i)) jbe@12: end jbe@13: end) jbe@12: jbe@13: for i, v in ipairs(g) do jbe@12: print(i, v) jbe@12: end jbe@12: -- prints: jbe@12: -- 1 Alice jbe@12: -- 2 Bob jbe@12: -- 3 Person #1 jbe@12: -- 4 Person #2 jbe@12: -- 5 Person #3 jbe@12: jbe@0: set = {apple = true, banana = true} jbe@0: for i, k, v in ipairs(pairs(set)) do jbe@0: print(i, k, v) jbe@0: end jbe@0: -- prints: jbe@0: -- 1 banana true jbe@0: -- 2 apple true jbe@0: -- (order of "apple" and "banana" may vary) jbe@0: jbe@0: The function ``iterator(...)`` may be used to convert any table, any function, jbe@0: or any iterator triplet into a single function (possibly creating a closure): jbe@0: jbe@2: require "seqlua" jbe@2: jbe@0: function filter_strings(...) jbe@0: nextvalue = iterator(...) jbe@0: return function() jbe@0: local value jbe@0: repeat jbe@0: value = nextvalue() jbe@0: until value == nil or type(value) == "string" jbe@0: return value jbe@0: end jbe@0: end jbe@0: jbe@0: for i, v in ipairs(filter_strings{"Hello", true, "World"}) do jbe@0: print(i, v) jbe@0: end jbe@0: -- prints: jbe@0: -- 1 Hello jbe@0: -- 2 World jbe@0: jbe@0: tbl = {apple = true, banana = true, [1] = "array entry"} jbe@0: for v in filter_strings(pairs(tbl)) do jbe@0: print(v) jbe@0: end jbe@0: -- prints: jbe@0: -- banana jbe@0: -- apple jbe@0: -- (order may vary) jbe@0: jbe@0: jbe@0: jbe@0: C part of the library jbe@0: --------------------- jbe@0: jbe@0: In ``seqlualib.h``, the following macro is defined: jbe@0: jbe@0: #define seqlua_iterloop(L, iter, idx) \ jbe@0: for ( \ jbe@0: seqlua_iterinit((L), (iter), (idx)); \ jbe@0: seqlua_iternext(iter); \ jbe@0: lua_pop((L), 1) \ jbe@0: ) jbe@0: jbe@0: This macro allows iteration over either tables or iterator functions (but not jbe@0: iterator triplets) as the following example function demonstrates: jbe@0: jbe@0: int printcsv(lua_State *L) { jbe@0: seqlua_Iterator iter; jbe@0: seqlua_iterloop(L, &iter, 1) { jbe@0: if (seqlua_itercount(&iter) > 1) fputs(",", stdout); jbe@0: fputs(luaL_tolstring(L, -1, NULL), stdout); jbe@1: lua_pop(L, 1); // pops value that luaL_tolstring pushed onto stack jbe@0: } jbe@0: fputs("\n", stdout); jbe@0: return 0; jbe@0: } jbe@0: jbe@11: printcsv{"a", "b", "c"} jbe@11: -- prints: a,b,c jbe@11: jbe@11: printcsv(assert(io.open("testfile")):lines()) jbe@11: -- prints: line1,line2,... of "testfile" jbe@0: jbe@0: Additionally, ``seqlualib`` includes a function ``seqlua_iterclosure(L, idx)``, jbe@0: which converts a table at a given stack index into a function closure (stored jbe@0: on the same stack index) that iterates over the elements of the table. If the jbe@15: value at the given stack index is already a function (or if it is callable jbe@15: through a ``__call`` metamethod), then ``seqlua_iterclosure(L, idx)`` leaves jbe@15: the value at ``idx`` unchanged. jbe@0: jbe@0: