| rev | 
   line source | 
| 
jbe@37
 | 
     1 seqlua: Extension for handling sequential data in Lua
 | 
| 
jbe@37
 | 
     2 =====================================================
 | 
| 
jbe@0
 | 
     3 
 | 
| 
jbe@0
 | 
     4 This is an experimental package to extend Lua in the following manner:
 | 
| 
jbe@0
 | 
     5 
 | 
| 
jbe@38
 | 
     6 * allow ``ipairs(seq)`` to accept either tables or functions (i.e function
 | 
| 
jbe@38
 | 
     7   iterators) as an argument,
 | 
| 
jbe@38
 | 
     8 * add a new function ``string.concat(separator, seq)`` that concats either
 | 
| 
jbe@32
 | 
     9   table entries or function return values,
 | 
| 
jbe@23
 | 
    10 * provide auxiliary C functions and macros to simplify iterating over both
 | 
| 
jbe@38
 | 
    11   tables and iterator functions with a generic statement.
 | 
| 
jbe@0
 | 
    12 
 | 
| 
jbe@33
 | 
    13 Existing ``__ipairs`` or ``__index`` (but not ``__len``) metamethods are
 | 
| 
jbe@33
 | 
    14 respected by both the Lua functions and the C functions and macros. The
 | 
| 
jbe@32
 | 
    15 ``__ipairs`` metamethod takes precedence over ``__index``, while the
 | 
| 
jbe@32
 | 
    16 ``__len`` metamethod is never used.
 | 
| 
jbe@32
 | 
    17 
 | 
| 
jbe@37
 | 
    18 Metamethod handling in detail is explained in the last section
 | 
| 
jbe@37
 | 
    19 ("Respected metamethods") at the bottom of this README.
 | 
| 
jbe@37
 | 
    20 
 | 
| 
jbe@37
 | 
    21 
 | 
| 
jbe@37
 | 
    22 
 | 
| 
jbe@37
 | 
    23 Motivation
 | 
| 
jbe@37
 | 
    24 ----------
 | 
| 
jbe@37
 | 
    25 
 | 
| 
jbe@37
 | 
    26 Sequential data (such as arrays or streams) is often represented in two
 | 
| 
jbe@37
 | 
    27 different ways:
 | 
| 
jbe@37
 | 
    28 
 | 
| 
jbe@37
 | 
    29 * as an ordered set of values (usually implemented as an array in other
 | 
| 
jbe@37
 | 
    30   programming languages, or as a sequence in Lua: a table with numeric keys
 | 
| 
jbe@37
 | 
    31   {1..n} associated with a value each),
 | 
| 
jbe@37
 | 
    32 * as some sort of data stream (sometimes implemented as a class of objects
 | 
| 
jbe@37
 | 
    33   providing certain methods, or as an iterator function in Lua: a function that
 | 
| 
jbe@37
 | 
    34   returns the next value with every call, where nil indicates the end of the
 | 
| 
jbe@37
 | 
    35   stream).
 | 
| 
jbe@37
 | 
    36 
 | 
| 
jbe@37
 | 
    37 Quite often, when functions work on sequential data, it shouldn't matter in
 | 
| 
jbe@37
 | 
    38 which form the sequential data is being provided to the function. As an
 | 
| 
jbe@37
 | 
    39 example, consider a function that is writing a sequence of strings to a file.
 | 
| 
jbe@37
 | 
    40 Such function could either be fed with an array of strings (a table with
 | 
| 
jbe@37
 | 
    41 numeric keys in Lua) or with a (possibly infinite) stream of data (an iterator
 | 
| 
jbe@37
 | 
    42 function in Lua).
 | 
| 
jbe@37
 | 
    43 
 | 
| 
jbe@37
 | 
    44 A function in Lua that accepts a table, might look like as follows:
 | 
| 
jbe@37
 | 
    45 
 | 
| 
jbe@37
 | 
    46     function write_lines(lines)
 | 
| 
jbe@37
 | 
    47       for i, line in ipairs(lines) do
 | 
| 
jbe@37
 | 
    48         io.stdout:write(line)
 | 
| 
jbe@37
 | 
    49         io.stdout:write("\n")
 | 
| 
jbe@37
 | 
    50       end
 | 
| 
jbe@37
 | 
    51     end
 | 
| 
jbe@37
 | 
    52 
 | 
| 
jbe@37
 | 
    53 In contrast, a function in Lua that accepts an iterator function would have to
 | 
| 
jbe@37
 | 
    54 be implemented differently:
 | 
| 
jbe@37
 | 
    55 
 | 
| 
jbe@37
 | 
    56     function write_lines(get_next_line)
 | 
| 
jbe@37
 | 
    57       for line in get_next_line do
 | 
| 
jbe@37
 | 
    58         io.stdout:write(line)
 | 
| 
jbe@37
 | 
    59         io.stdout:write("\n")
 | 
| 
jbe@37
 | 
    60       end
 | 
| 
jbe@37
 | 
    61     end
 | 
| 
jbe@37
 | 
    62 
 | 
| 
jbe@37
 | 
    63 If one wanted to create a function that accepts either a sequence in form of a
 | 
| 
jbe@37
 | 
    64 table or an iterator function, then one might need to write:
 | 
| 
jbe@37
 | 
    65 
 | 
| 
jbe@41
 | 
    66     do
 | 
| 
jbe@41
 | 
    67       local function write_line(line)
 | 
| 
jbe@37
 | 
    68         io.stdout:write(line)
 | 
| 
jbe@37
 | 
    69         io.stdout:write("\n")
 | 
| 
jbe@37
 | 
    70       end
 | 
| 
jbe@41
 | 
    71       function write_lines(lines)
 | 
| 
jbe@41
 | 
    72         if type(lines) == "function" then
 | 
| 
jbe@41
 | 
    73           for line in lines do
 | 
| 
jbe@41
 | 
    74             write_line(line)
 | 
| 
jbe@41
 | 
    75           end
 | 
| 
jbe@41
 | 
    76         else
 | 
| 
jbe@41
 | 
    77           for i, line in ipairs(lines) do
 | 
| 
jbe@41
 | 
    78             write_line(line)
 | 
| 
jbe@41
 | 
    79           end
 | 
| 
jbe@41
 | 
    80         end
 | 
| 
jbe@41
 | 
    81       end
 | 
| 
jbe@37
 | 
    82     end
 | 
| 
jbe@37
 | 
    83 
 | 
| 
jbe@41
 | 
    84 Obviously, this isn't something we want to do in every function that accepts
 | 
| 
jbe@37
 | 
    85 sequential data. Therefore, we usually decide for one of the two first forms
 | 
| 
jbe@37
 | 
    86 and therefore disallow the other possible representation of sequential data to
 | 
| 
jbe@37
 | 
    87 be passed to the function.
 | 
| 
jbe@37
 | 
    88 
 | 
| 
jbe@37
 | 
    89 This extension, however, modifies Lua's ``ipairs`` statement in such way that
 | 
| 
jbe@37
 | 
    90 it automatically accepts either a table or an iterator function as argument.
 | 
| 
jbe@42
 | 
    91 Thus, the first of the three ``write_lines`` functions above will accept both
 | 
| 
jbe@42
 | 
    92 (table) sequences and (function) iterators.
 | 
| 
jbe@37
 | 
    93 
 | 
| 
jbe@37
 | 
    94 In addition to the modification of ``ipairs``, it also provides C functions and
 | 
| 
jbe@37
 | 
    95 macros to iterate over values in the same manner as a generic loop statement
 | 
| 
jbe@37
 | 
    96 with ``ipairs`` would do.
 | 
| 
jbe@37
 | 
    97 
 | 
| 
jbe@37
 | 
    98 Note that this extension doesn't aim to supersede Lua's concept of iterator
 | 
| 
jbe@37
 | 
    99 functions. While metamethods (see section "Respected metamethods" below) may be
 | 
| 
jbe@37
 | 
   100 used to customize iteration behavior on values, this extension isn't thought to
 | 
| 
jbe@37
 | 
   101 replace the common practice to use function closures as iterators. Consider the
 | 
| 
jbe@37
 | 
   102 following example:
 | 
| 
jbe@37
 | 
   103 
 | 
| 
jbe@37
 | 
   104     local result = sql_query("SELECT * FROM actor ORDER BY birthdate")
 | 
| 
jbe@37
 | 
   105     write_lines(result:get_column_entries("name"))
 | 
| 
jbe@37
 | 
   106 
 | 
| 
jbe@37
 | 
   107 The ``get_column_entries`` method can return a simple function closure that
 | 
| 
jbe@37
 | 
   108 returns the next entry in the "name" column (returning ``nil`` to indicate the
 | 
| 
jbe@37
 | 
   109 end). Such a closure can then be passed to another function that iterates
 | 
| 
jbe@37
 | 
   110 through a sequence of values by invoking ``ipairs`` with the general for-loop
 | 
| 
jbe@37
 | 
   111 (as previously shown).
 | 
| 
jbe@37
 | 
   112 
 | 
| 
jbe@37
 | 
   113 Where desired, it is also possible to use metamethods to customize iteration
 | 
| 
jbe@44
 | 
   114 behavior:
 | 
| 
jbe@44
 | 
   115 
 | 
| 
jbe@44
 | 
   116     function print_rows(rows)
 | 
| 
jbe@44
 | 
   117       for i, row in ipairs(rows) do
 | 
| 
jbe@44
 | 
   118         print_row(row)
 | 
| 
jbe@44
 | 
   119       end
 | 
| 
jbe@44
 | 
   120     end
 | 
| 
jbe@44
 | 
   121     local result = sql_query("SELECT * FROM actor ORDER BY birthday")
 | 
| 
jbe@46
 | 
   122     assert(type(result) == "userdata")
 | 
| 
jbe@44
 | 
   123 
 | 
| 
jbe@44
 | 
   124     -- we may rely on the ``__index`` or ``__ipairs`` metamethod to
 | 
| 
jbe@44
 | 
   125     -- iterate through all result rows here:
 | 
| 
jbe@44
 | 
   126     print_rows(result)  -- no need to use ":rows()" or a similar syntax
 | 
| 
jbe@44
 | 
   127 
 | 
| 
jbe@45
 | 
   128     -- but we can also still pass an individual set of result rows to the
 | 
| 
jbe@44
 | 
   129     -- print_rows function:
 | 
| 
jbe@44
 | 
   130     print_rows{result[1], result[#result]}
 | 
| 
jbe@44
 | 
   131 
 | 
| 
jbe@44
 | 
   132 This extension, however, doesn't respect the ``__len`` metamethod due to the
 | 
| 
jbe@47
 | 
   133 following considerations:
 | 
| 
jbe@37
 | 
   134 
 | 
| 
jbe@39
 | 
   135 * An efficient implementation where ``for i, v in ipairs(tbl) do ... end`` does
 | 
| 
jbe@39
 | 
   136   neither create a closure nor repeatedly evaluate ``#tbl`` seems to be
 | 
| 
jbe@39
 | 
   137   impossible.
 | 
| 
jbe@37
 | 
   138 * Respecting ``__len`` could be used to implement sparse arrays, but this would
 | 
| 
jbe@37
 | 
   139   require iterating functions to expect ``nil`` as a potential value. This may
 | 
| 
jbe@37
 | 
   140   lead to problems because ``nil`` is usually also used to indicate the absence
 | 
| 
jbe@37
 | 
   141   of a value.
 | 
| 
jbe@37
 | 
   142 
 | 
| 
jbe@40
 | 
   143 Though, if such behavior is desired, it can still be implemented through the
 | 
| 
jbe@37
 | 
   144 ``__ipairs`` metamethod.
 | 
| 
jbe@37
 | 
   145 
 | 
| 
jbe@37
 | 
   146 Unless manually done by the user in the ``__ipairs`` metamethod, this extension
 | 
| 
jbe@37
 | 
   147 never creates any closures or other values that need to be garbage collected.
 | 
| 
jbe@37
 | 
   148 
 | 
| 
jbe@0
 | 
   149 
 | 
| 
jbe@0
 | 
   150 
 | 
| 
jbe@0
 | 
   151 Lua part of the library
 | 
| 
jbe@0
 | 
   152 -----------------------
 | 
| 
jbe@0
 | 
   153 
 | 
| 
jbe@30
 | 
   154 The modified ``ipairs(seq)`` and the new ``string.concat(sep, seq)`` functions
 | 
| 
jbe@30
 | 
   155 accept either a table or a function as ``seq``. This is demonstrated in the
 | 
| 
jbe@30
 | 
   156 following examples:
 | 
| 
jbe@0
 | 
   157 
 | 
| 
jbe@0
 | 
   158     require "seqlua"
 | 
| 
jbe@0
 | 
   159 
 | 
| 
jbe@0
 | 
   160     t = {"a", "b", "c"}
 | 
| 
jbe@0
 | 
   161 
 | 
| 
jbe@0
 | 
   162     for i, v in ipairs(t) do
 | 
| 
jbe@0
 | 
   163       print(i, v)
 | 
| 
jbe@0
 | 
   164     end
 | 
| 
jbe@0
 | 
   165     -- prints:
 | 
| 
jbe@0
 | 
   166     --  1   a
 | 
| 
jbe@0
 | 
   167     --  2   b
 | 
| 
jbe@0
 | 
   168     --  3   c
 | 
| 
jbe@0
 | 
   169 
 | 
| 
jbe@25
 | 
   170     print(string.concat(",", t))
 | 
| 
jbe@25
 | 
   171     -- prints: a,b,c
 | 
| 
jbe@25
 | 
   172 
 | 
| 
jbe@19
 | 
   173     function alphabet()
 | 
| 
jbe@0
 | 
   174       local letter = nil
 | 
| 
jbe@0
 | 
   175       return function()
 | 
| 
jbe@0
 | 
   176         if letter == nil then
 | 
| 
jbe@19
 | 
   177           letter = "a"
 | 
| 
jbe@19
 | 
   178         elseif letter == "z" then
 | 
| 
jbe@0
 | 
   179           return nil
 | 
| 
jbe@0
 | 
   180         else
 | 
| 
jbe@0
 | 
   181           letter = string.char(string.byte(letter) + 1)
 | 
| 
jbe@0
 | 
   182         end
 | 
| 
jbe@0
 | 
   183         return letter
 | 
| 
jbe@0
 | 
   184       end
 | 
| 
jbe@0
 | 
   185     end
 | 
| 
jbe@0
 | 
   186 
 | 
| 
jbe@23
 | 
   187     for i, v in ipairs(alphabet()) do
 | 
| 
jbe@0
 | 
   188       print(i, v)
 | 
| 
jbe@0
 | 
   189     end
 | 
| 
jbe@0
 | 
   190     -- prints:
 | 
| 
jbe@0
 | 
   191     --  1   a
 | 
| 
jbe@0
 | 
   192     --  2   b
 | 
| 
jbe@0
 | 
   193     --  3   c
 | 
| 
jbe@0
 | 
   194     --  ...
 | 
| 
jbe@0
 | 
   195     --  25  y
 | 
| 
jbe@0
 | 
   196     --  26  z
 | 
| 
jbe@0
 | 
   197 
 | 
| 
jbe@25
 | 
   198     print(string.concat(",", alphabet()))
 | 
| 
jbe@25
 | 
   199     -- prints: a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
 | 
| 
jbe@25
 | 
   200 
 | 
| 
jbe@26
 | 
   201     function filter(f)
 | 
| 
jbe@26
 | 
   202       return function(seq)
 | 
| 
jbe@26
 | 
   203         return coroutine.wrap(function()
 | 
| 
jbe@26
 | 
   204           for i, v in ipairs(seq) do f(v) end
 | 
| 
jbe@26
 | 
   205         end)
 | 
| 
jbe@26
 | 
   206       end
 | 
| 
jbe@0
 | 
   207     end
 | 
| 
jbe@19
 | 
   208 
 | 
| 
jbe@29
 | 
   209     alpha_beta_x = filter(function(v)
 | 
| 
jbe@28
 | 
   210       if v == "a" then
 | 
| 
jbe@28
 | 
   211         coroutine.yield("alpha")
 | 
| 
jbe@28
 | 
   212       elseif v == "b" then
 | 
| 
jbe@28
 | 
   213         coroutine.yield("beta")
 | 
| 
jbe@28
 | 
   214       elseif type(v) == "number" then
 | 
| 
jbe@23
 | 
   215         for i = 1, v do
 | 
| 
jbe@28
 | 
   216           coroutine.yield("X")
 | 
| 
jbe@23
 | 
   217         end
 | 
| 
jbe@0
 | 
   218       end
 | 
| 
jbe@26
 | 
   219     end)
 | 
| 
jbe@0
 | 
   220 
 | 
| 
jbe@29
 | 
   221     print((","):concat(alpha_beta_x{"a", 3, "b", "c", "d"}))
 | 
| 
jbe@28
 | 
   222     -- prints: alpha,X,X,X,beta
 | 
| 
jbe@25
 | 
   223 
 | 
| 
jbe@29
 | 
   224     print((","):concat(alpha_beta_x(alphabet())))
 | 
| 
jbe@28
 | 
   225     -- prints: alpha,beta
 | 
| 
jbe@27
 | 
   226 
 | 
| 
jbe@0
 | 
   227 
 | 
| 
jbe@37
 | 
   228 
 | 
| 
jbe@0
 | 
   229 C part of the library
 | 
| 
jbe@0
 | 
   230 ---------------------
 | 
| 
jbe@0
 | 
   231 
 | 
| 
jbe@0
 | 
   232 In ``seqlualib.h``, the following macro is defined:
 | 
| 
jbe@0
 | 
   233 
 | 
| 
jbe@0
 | 
   234     #define seqlua_iterloop(L, iter, idx) \
 | 
| 
jbe@0
 | 
   235       for ( \
 | 
| 
jbe@0
 | 
   236         seqlua_iterinit((L), (iter), (idx)); \
 | 
| 
jbe@0
 | 
   237         seqlua_iternext(iter); \
 | 
| 
jbe@25
 | 
   238       )
 | 
| 
jbe@25
 | 
   239 
 | 
| 
jbe@25
 | 
   240 and
 | 
| 
jbe@25
 | 
   241 
 | 
| 
jbe@25
 | 
   242     #define seqlua_iterloopauto(L, iter, idx) \
 | 
| 
jbe@25
 | 
   243       for ( \
 | 
| 
jbe@25
 | 
   244         seqlua_iterinit((L), (iter), (idx)); \
 | 
| 
jbe@25
 | 
   245         seqlua_iternext(iter); \
 | 
| 
jbe@0
 | 
   246         lua_pop((L), 1) \
 | 
| 
jbe@0
 | 
   247       )
 | 
| 
jbe@0
 | 
   248 
 | 
| 
jbe@23
 | 
   249 This macro allows iteration over either tables or iterator functions as the
 | 
| 
jbe@23
 | 
   250 following example function demonstrates:
 | 
| 
jbe@0
 | 
   251 
 | 
| 
jbe@0
 | 
   252     int printcsv(lua_State *L) {
 | 
| 
jbe@0
 | 
   253       seqlua_Iterator iter;
 | 
| 
jbe@0
 | 
   254       seqlua_iterloop(L, &iter, 1) {
 | 
| 
jbe@0
 | 
   255         if (seqlua_itercount(&iter) > 1) fputs(",", stdout);
 | 
| 
jbe@0
 | 
   256         fputs(luaL_tolstring(L, -1, NULL), stdout);
 | 
| 
jbe@25
 | 
   257         // two values need to be popped (the value pushed by
 | 
| 
jbe@25
 | 
   258         // seqlua_iternext and the value pushed by luaL_tolstring)
 | 
| 
jbe@25
 | 
   259         lua_pop(L, 2);
 | 
| 
jbe@0
 | 
   260       }
 | 
| 
jbe@0
 | 
   261       fputs("\n", stdout);
 | 
| 
jbe@0
 | 
   262       return 0;
 | 
| 
jbe@0
 | 
   263     }
 | 
| 
jbe@0
 | 
   264 
 | 
| 
jbe@11
 | 
   265     printcsv{"a", "b", "c"}
 | 
| 
jbe@11
 | 
   266     -- prints: a,b,c
 | 
| 
jbe@11
 | 
   267 
 | 
| 
jbe@11
 | 
   268     printcsv(assert(io.open("testfile")):lines())
 | 
| 
jbe@11
 | 
   269     -- prints: line1,line2,... of "testfile"
 | 
| 
jbe@0
 | 
   270 
 | 
| 
jbe@31
 | 
   271 NOTE: During iteration using ``seqlua_iterloop``, ``seqlua_iterloopauto``, or
 | 
| 
jbe@31
 | 
   272 ``seqlua_iterinit``, three extra elements are stored on the stack (additionally
 | 
| 
jbe@31
 | 
   273 to the value). These extra elements are removed automatically when the loop ends
 | 
| 
jbe@31
 | 
   274 (i.e. when ``seqlua_iternext`` returns zero). The value pushed onto the stack
 | 
| 
jbe@31
 | 
   275 for every iteration step has to be removed manually from the stack, unless
 | 
| 
jbe@31
 | 
   276 ``seqlua_iterloopauto`` is used.
 | 
| 
jbe@0
 | 
   277 
 | 
| 
jbe@31
 | 
   278 
 | 
| 
jbe@37
 | 
   279 
 | 
| 
jbe@35
 | 
   280 Respected metamethods
 | 
| 
jbe@35
 | 
   281 ---------------------
 | 
| 
jbe@35
 | 
   282 
 | 
| 
jbe@35
 | 
   283 Regarding the behavior of the Lua functions and the C functions and macros
 | 
| 
jbe@35
 | 
   284 provided by this extension, an existing ``__index`` metamethod will be
 | 
| 
jbe@35
 | 
   285 respected automatically. An existing ``__ipairs`` metamethod, however, takes
 | 
| 
jbe@35
 | 
   286 precedence.
 | 
| 
jbe@35
 | 
   287 
 | 
| 
jbe@35
 | 
   288 If the ``__ipairs`` field of a value's metatable is set, then it must always
 | 
| 
jbe@35
 | 
   289 refer to a function. When starting iteration over a value with such a
 | 
| 
jbe@35
 | 
   290 metamethod being set, then this function is called with ``self`` (i.e. the
 | 
| 
jbe@35
 | 
   291 value itself) passed as first argument. The return values of the ``__ipairs``
 | 
| 
jbe@35
 | 
   292 metamethod may take one of the following 4 forms:
 | 
| 
jbe@35
 | 
   293 
 | 
| 
jbe@35
 | 
   294 * ``return function_or_callable, static_argument, startindex`` causes the three
 | 
| 
jbe@35
 | 
   295   arguments to be returned by ``ipairs`` without further modification. Using
 | 
| 
jbe@35
 | 
   296   the C macros and functions for iteration, the behavior is according to the
 | 
| 
jbe@35
 | 
   297   generic loop statement in Lua:
 | 
| 
jbe@35
 | 
   298   ``for i, v in function_or_callable, static_argument, startindex do ... end``
 | 
| 
jbe@35
 | 
   299 * ``return "raw", table`` will result in iteration over the table ``table``
 | 
| 
jbe@35
 | 
   300   using ``lua_rawgeti``
 | 
| 
jbe@35
 | 
   301 * ``return "index", table_or_userdata`` will result in iteration over the table
 | 
| 
jbe@35
 | 
   302   or userdata while respecting any ``__index`` metamethod of the table or
 | 
| 
jbe@35
 | 
   303   userdata value
 | 
| 
jbe@35
 | 
   304 * ``return "call", function_or_callable`` will use the callable value as
 | 
| 
jbe@35
 | 
   305   (function) iterator where the function is expected to return a single value
 | 
| 
jbe@35
 | 
   306   without any index (the index is inserted automatically when using the
 | 
| 
jbe@35
 | 
   307   ``ipairs`` function for iteration)
 | 
| 
jbe@35
 | 
   308 
 | 
| 
jbe@35
 | 
   309 These possiblities are demonstrated by the following example code:
 | 
| 
jbe@35
 | 
   310 
 | 
| 
jbe@35
 | 
   311     require "seqlua"
 | 
| 
jbe@35
 | 
   312 
 | 
| 
jbe@35
 | 
   313     do
 | 
| 
jbe@35
 | 
   314       local function ipairsaux(t, i)
 | 
| 
jbe@35
 | 
   315         i = i + 1
 | 
| 
jbe@35
 | 
   316         if i <= 3 then
 | 
| 
jbe@35
 | 
   317           return i, t[i]
 | 
| 
jbe@35
 | 
   318         end
 | 
| 
jbe@35
 | 
   319       end
 | 
| 
jbe@35
 | 
   320       custom = setmetatable(
 | 
| 
jbe@35
 | 
   321         {"one", "two", "three", "four", "five"},
 | 
| 
jbe@35
 | 
   322         {
 | 
| 
jbe@35
 | 
   323           __ipairs = function(self)
 | 
| 
jbe@35
 | 
   324             return ipairsaux, self, 0
 | 
| 
jbe@35
 | 
   325           end
 | 
| 
jbe@35
 | 
   326         }
 | 
| 
jbe@35
 | 
   327       )
 | 
| 
jbe@35
 | 
   328     end
 | 
| 
jbe@35
 | 
   329     print(string.concat(",", custom))
 | 
| 
jbe@36
 | 
   330     -- prints: one,two,three
 | 
| 
jbe@35
 | 
   331     -- (note that "four" and "five" are not printed)
 | 
| 
jbe@35
 | 
   332 
 | 
| 
jbe@35
 | 
   333     tbl = {"alpha", "beta"}
 | 
| 
jbe@35
 | 
   334 
 | 
| 
jbe@35
 | 
   335     proxy1 = setmetatable({}, {__index = tbl})
 | 
| 
jbe@35
 | 
   336     for i, v in ipairs(proxy1) do print(i, v) end
 | 
| 
jbe@35
 | 
   337     -- prints:
 | 
| 
jbe@35
 | 
   338     --  1   alpha
 | 
| 
jbe@35
 | 
   339     --  2   beta
 | 
| 
jbe@35
 | 
   340 
 | 
| 
jbe@35
 | 
   341     proxy2 = setmetatable({}, {
 | 
| 
jbe@35
 | 
   342       __ipairs = function(self)
 | 
| 
jbe@35
 | 
   343         return "index", proxy1
 | 
| 
jbe@35
 | 
   344       end
 | 
| 
jbe@35
 | 
   345     })
 | 
| 
jbe@35
 | 
   346     for i, v in ipairs(proxy2) do print(i, v) end
 | 
| 
jbe@35
 | 
   347     -- prints:
 | 
| 
jbe@35
 | 
   348     --  1   alpha
 | 
| 
jbe@35
 | 
   349     --  2   beta
 | 
| 
jbe@35
 | 
   350     print(proxy2[1])
 | 
| 
jbe@35
 | 
   351     -- prints: nil
 | 
| 
jbe@35
 | 
   352 
 | 
| 
jbe@35
 | 
   353     cursor = setmetatable({
 | 
| 
jbe@35
 | 
   354       "alice", "bob", "charlie", pos=1
 | 
| 
jbe@35
 | 
   355     }, {
 | 
| 
jbe@35
 | 
   356       __call = function(self)
 | 
| 
jbe@35
 | 
   357         local value = self[self.pos]
 | 
| 
jbe@35
 | 
   358         if value == nil then
 | 
| 
jbe@35
 | 
   359           self.pos = 1
 | 
| 
jbe@35
 | 
   360         else
 | 
| 
jbe@35
 | 
   361           self.pos = self.pos + 1
 | 
| 
jbe@35
 | 
   362         end
 | 
| 
jbe@35
 | 
   363         return value
 | 
| 
jbe@35
 | 
   364       end,
 | 
| 
jbe@35
 | 
   365       __ipairs = function(self)
 | 
| 
jbe@35
 | 
   366         return "call", self
 | 
| 
jbe@35
 | 
   367       end
 | 
| 
jbe@35
 | 
   368     })
 | 
| 
jbe@35
 | 
   369     for i, v in ipairs(cursor) do print(i, v) end
 | 
| 
jbe@35
 | 
   370     -- prints:
 | 
| 
jbe@35
 | 
   371     --  1   alice
 | 
| 
jbe@35
 | 
   372     --  2   bob
 | 
| 
jbe@35
 | 
   373     --  3   charlie
 | 
| 
jbe@35
 | 
   374     print(cursor())
 | 
| 
jbe@35
 | 
   375     -- prints: alice
 | 
| 
jbe@35
 | 
   376     for i, v in ipairs(cursor) do print(i, v) end
 | 
| 
jbe@35
 | 
   377     -- prints:
 | 
| 
jbe@35
 | 
   378     --  1   bob
 | 
| 
jbe@35
 | 
   379     --  2   charlie
 | 
| 
jbe@35
 | 
   380     -- (note that "alice" has been returned earlier)
 | 
| 
jbe@35
 | 
   381 
 | 
| 
jbe@35
 | 
   382     coefficients = setmetatable({1.25, 3.14, 17.5}, {
 | 
| 
jbe@35
 | 
   383       __index  = function(self) return 1 end,
 | 
| 
jbe@35
 | 
   384       __ipairs = function(self) return "raw", self end
 | 
| 
jbe@35
 | 
   385     })
 | 
| 
jbe@35
 | 
   386     for i, v in ipairs(coefficients) do print(i, v) end
 | 
| 
jbe@35
 | 
   387     -- prints:
 | 
| 
jbe@35
 | 
   388     --  1   1.25
 | 
| 
jbe@35
 | 
   389     --  2   3.14
 | 
| 
jbe@35
 | 
   390     --  3   17.5
 | 
| 
jbe@35
 | 
   391     -- (note that iteration terminates even if coefficients[4] == 1)
 | 
| 
jbe@35
 | 
   392     print(coefficients[4])
 | 
| 
jbe@35
 | 
   393     -- prints: 1
 | 
| 
jbe@35
 | 
   394 
 | 
| 
jbe@35
 | 
   395 
 |