lua-users home
lua-l archive

Re: ipairs in Lua 5.3.0-alpha

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On 2014年8月17日 19:26:53 +0200
Jan Behrens <jbe-lua-l@public-software-group.org> wrote:
> do
> 
> local function ipairsaux_raw(t, i)
> i = i + 1
> local v = rawget(t, i)
> if v then
> return i, v
> else
> return nil
> end
> end
> 
> local function ipairsaux_meta(t, i)
> i = i + 1
> local v = t[i]
> if v then
> return i, v
> else
> return nil
> end
> end
> 
> local function ipairsaux_metalen(t, i)
> i = i + 1
> local v = t[i]
> if v ~= nil then
> return i, v
> else
> -- Roberto's idea: evaluate len only if v == nil
> if i <= #t then
> return i, nil
> else
> return nil
> end
> end
> end
> 
> local function ipairsaux_func(f, i)
> local v, v2, v3, v4, vn = f()
> -- variable arg number requires C implementation
> if v then
> return i + 1, v, v2, v3, v4, vn
> else
> return nil
> end
> end
> 
> local empty = {}
> 
> function supercool_ipairs(x, s, i)
> local mt = getmetatable(x) or empty
> local mt_ipairs = rawget(mt, "__ipairs")
> if mt_ipairs ~= nil then
> return mt_ipairs(x)
> elseif type(x) == "function" then
> if s == nil and i == nil then
> return ipairsaux_func, x, 0
> else
> local n = 0
> return function() -- closure not avoidable here
> n = n + 1
> local v, v2, v3, v4, vn = x(s, i)
> -- variable arg number requires C implementation
> if v == nil then
> return nil
> else
> i = v
> return n, v, v2, v3, v4, vn
> end
> end
> end
> else
> local mt_call = rawget(mt, "__call")
> if mt_call then
> return supercool_ipairs(mt_call, s, i)
> elseif rawget(mt, "__len") ~= nil then
> return ipairsaux_metalen, x, 0
> elseif rawget(mt, "__index") ~= nil then
> return ipairsaux_meta, x, 0
> else
> return ipairsaux_raw, x, 0
> end
> end
> end
> 
> end
I translated this to C, to give a short proof-of-concept
(in case somebody is interested in experimenting with it):
============================================================
#include <lua.h>
#include <lauxlib.h>
#define POWERITERATOR_GLOBAL
int poweriterator_ipairsaux_raw(lua_State *L) {
 lua_Integer i;
 luaL_checktype(L, 1, LUA_TTABLE);
 i = luaL_checkinteger(L, 2) + 1;
 lua_pushinteger(L, i);
 lua_rawgeti(L, 1, i); // TODO: Lua 5.3 returns type
 return lua_isnil(L, -1) ? 1 : 2;
}
int poweriterator_ipairsaux_meta(lua_State *L) {
 lua_Integer i;
 i = luaL_checkinteger(L, 2) + 1;
 lua_pushinteger(L, i);
 lua_pushinteger(L, i);
 lua_gettable(L, 1); // TODO: Lua 5.3 returns type
 return lua_isnil(L, -1) ? 1 : 2;
}
int poweriterator_ipairsaux_metalen(lua_State *L) {
 lua_Integer i;
 i = luaL_checkinteger(L, 2) + 1;
 lua_pushinteger(L, i);
 lua_pushinteger(L, i);
 lua_gettable(L, 1); // TODO: Lua 5.3 returns type
 return (lua_isnil(L, -1) && i > luaL_len(L, 1)) ? 1 : 2;
}
int poweriterator_ipairsaux_func(lua_State *L) {
 luaL_checktype(L, 1, LUA_TFUNCTION);
 lua_pushinteger(L, luaL_checkinteger(L, 2) + 1);
 lua_insert(L, 1);
 lua_settop(L, 2);
 lua_call(L, 0, LUA_MULTRET);
 if (lua_isnoneornil(L, 2)) {
 lua_settop(L, 0);
 lua_pushnil(L);
 return 1;
 } else {
 return lua_gettop(L);
 }
}
int poweriterator_ipairsaux_funcclosure(lua_State *L) {
 lua_Integer i = lua_tointeger(L, lua_upvalueindex(4)) + 1;
 lua_settop(L, 0);
 lua_pushinteger(L, i);
 lua_replace(L, lua_upvalueindex(4));
 lua_pushinteger(L, i);
 lua_pushvalue(L, lua_upvalueindex(1));
 lua_pushvalue(L, lua_upvalueindex(2));
 lua_pushvalue(L, lua_upvalueindex(3));
 lua_call(L, 2, LUA_MULTRET);
 if (lua_isnoneornil(L, 2)) {
 lua_settop(L, 0);
 lua_pushnil(L);
 return 1;
 } else {
 lua_pushvalue(L, 2);
 lua_replace(L, lua_upvalueindex(3));
 return lua_gettop(L);
 }
}
int poweriterator_ipairs(lua_State *L) {
 lua_settop(L, 3);
 if (luaL_getmetafield(L, 1, "__ipairs")) {
 lua_pushvalue(L, 1);
 lua_call(L, 1, LUA_MULTRET);
 return lua_gettop(L) - 3;
 }
 poweriterator_ipairs_repeat:
 if (lua_type(L, 1) == LUA_TFUNCTION) {
 if (lua_isnil(L, 2) && lua_isnil(L, 3)) {
 lua_pushcfunction(L, poweriterator_ipairsaux_func);
 } else {
 lua_pushinteger(L, 0);
 lua_pushcclosure(L, poweriterator_ipairsaux_funcclosure, 4);
 return 1;
 }
 } else if (luaL_getmetafield(L, 1, "__call")) {
 lua_replace(L, 1);
 goto poweriterator_ipairs_repeat;
 } else if (luaL_getmetafield(L, 1, "__len")) {
 lua_pushcfunction(L, poweriterator_ipairsaux_metalen);
 } else if (luaL_getmetafield(L, 1, "__index")) {
 lua_pushcfunction(L, poweriterator_ipairsaux_meta);
 } else {
 lua_pushcfunction(L, poweriterator_ipairsaux_raw);
 }
 lua_pushvalue(L, 1);
 lua_pushinteger(L, 0);
 return 3;
}
int poweriterator_crimp_closure(lua_State *L) {
 lua_settop(L, 0);
 lua_pushvalue(L, lua_upvalueindex(1));
 lua_pushvalue(L, lua_upvalueindex(2));
 lua_pushvalue(L, lua_upvalueindex(3));
 lua_call(L, 2, LUA_MULTRET);
 if (lua_isnoneornil(L, 1)) {
 lua_settop(L, 1);
 return 1;
 } else {
 lua_pushvalue(L, 1);
 lua_replace(L, lua_upvalueindex(3));
 return lua_gettop(L);
 }
}
int poweriterator_crimp(lua_State *L) {
 if (lua_isnoneornil(L, 2) && lua_isnoneornil(L, 3)) {
 lua_settop(L, 1);
 return 1;
 }
 lua_settop(L, 3);
 lua_pushcclosure(L, poweriterator_crimp_closure, 3);
 return 1;
}
static const struct luaL_Reg poweriterator_module_functions[] = {
 {"ipairs", poweriterator_ipairs},
 {"crimp", poweriterator_crimp},
 {NULL, NULL}
};
int luaopen_poweriterator(lua_State *L) {
#ifdef POWERITERATOR_GLOBAL
 lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
 luaL_setfuncs(L, poweriterator_module_functions, 0);
#endif
 luaL_newlib(L, poweriterator_module_functions);
 return 1;
}
============================================================
This module registers two global functions "ipairs" and "crimp".
* "ipairs" works just like "supercool_ipairs" that I posted earlier.
* "crimp" is a helper function that can be used to compress an
 iterator triplet to a single closure. If it's called with one
 argument, then it simply returns its argument. Otherwise it
 creates a closure for iteration.
Here are four examples of how it could work in Lua 5.3:
do
 local function printentries(entries)
 -- NOTE: no more varargs here,
 -- entries shall be a single value iterable through ipairs
 for i, v, v2 in ipairs(entries) do
 if v2 == nil then
 print("Entry #" .. tostring(i) .. ": " .. tostring(v))
 else
 print(
 "Entry #" .. tostring(i) ..
 ": (" .. tostring(v) .. "," .. tostring(v2) .. ")"
 )
 end
 end
 end
 local letter = nil
 local function my_iterator() -- some example iterator
 if letter == nil then
 letter = "a"
 elseif letter == "z" then
 return nil
 else
 letter = string.char(string.byte(letter) + 1)
 end
 return letter
 end
 local lines = assert(io.open("testfile", "r")):lines()
 local kv_pairs = crimp(pairs{fruit = "apple", mood = "good"})
 -- crimp will convert the iterator triplet into a single value
 -- so that the printentries function can accept it
 printentries(my_iterator)
 -- prints:
 -- Entry #1: a
 -- Entry #2: b
 -- Entry #3: c
 -- Entry #4: d
 -- ...
 -- Entry #25: y
 -- Entry #26: z
 printentries{"a", "b", "c"}
 -- prints:
 -- Entry #1: a
 -- Entry #2: b
 -- Entry #3: c
 printentries(lines)
 -- prints:
 -- Entry #1: This is line #1 of my testfile.
 -- Entry #2: This is line #2 of my testfile.
 printentries(kv_pairs)
 -- prints:
 -- Entry #1: (mood,good)
 -- Entry #2: (fruit,apple)
end
As you can see, the printentries function accepts any single value
that's iterable (through ipairs). Raw tables, e.g. {"a", "b", "c"},
are iterable by default, so are functions, e.g.
function() return "loop" end.
As I previously said, this requires only minimal changes in lbaselib.c
It would create a common iterator interface that could be used by all
libraries. The ipairs function could work on tables, SQL cursors,
sparse arrays, basically anything!
(Still looking forward to feedback.)
Regards
Jan Behrens

AltStyle によって変換されたページ (->オリジナル) /