lua-users home
lua-l archive

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


On Sun, 17 Aug 2014 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