| 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@37
 | 
    91 Thus, the first of the three functions above will accept both (table) sequences
 | 
| 
jbe@37
 | 
    92 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@37
 | 
   114 behavior. Consider:
 | 
| 
jbe@37
 | 
   115 
 | 
| 
jbe@37
 | 
   116     function process_row(database_row)
 | 
| 
jbe@37
 | 
   117       -- some function
 | 
| 
jbe@37
 | 
   118     end
 | 
| 
jbe@37
 | 
   119     local result = sql_query("SELECT * FROM actor")
 | 
| 
jbe@37
 | 
   120     process_row(result)  -- without writing :rows() or similar
 | 
| 
jbe@37
 | 
   121 
 | 
| 
jbe@37
 | 
   122 This extension, however, doesn't respect the ``__len`` metamethod due to the
 | 
| 
jbe@37
 | 
   123 following reasons:
 | 
| 
jbe@37
 | 
   124 
 | 
| 
jbe@39
 | 
   125 * An efficient implementation where ``for i, v in ipairs(tbl) do ... end`` does
 | 
| 
jbe@39
 | 
   126   neither create a closure nor repeatedly evaluate ``#tbl`` seems to be
 | 
| 
jbe@39
 | 
   127   impossible.
 | 
| 
jbe@37
 | 
   128 * Respecting ``__len`` could be used to implement sparse arrays, but this would
 | 
| 
jbe@37
 | 
   129   require iterating functions to expect ``nil`` as a potential value. This may
 | 
| 
jbe@37
 | 
   130   lead to problems because ``nil`` is usually also used to indicate the absence
 | 
| 
jbe@37
 | 
   131   of a value.
 | 
| 
jbe@37
 | 
   132 
 | 
| 
jbe@40
 | 
   133 Though, if such behavior is desired, it can still be implemented through the
 | 
| 
jbe@37
 | 
   134 ``__ipairs`` metamethod.
 | 
| 
jbe@37
 | 
   135 
 | 
| 
jbe@37
 | 
   136 Unless manually done by the user in the ``__ipairs`` metamethod, this extension
 | 
| 
jbe@37
 | 
   137 never creates any closures or other values that need to be garbage collected.
 | 
| 
jbe@37
 | 
   138 
 | 
| 
jbe@0
 | 
   139 
 | 
| 
jbe@0
 | 
   140 
 | 
| 
jbe@0
 | 
   141 Lua part of the library
 | 
| 
jbe@0
 | 
   142 -----------------------
 | 
| 
jbe@0
 | 
   143 
 | 
| 
jbe@30
 | 
   144 The modified ``ipairs(seq)`` and the new ``string.concat(sep, seq)`` functions
 | 
| 
jbe@30
 | 
   145 accept either a table or a function as ``seq``. This is demonstrated in the
 | 
| 
jbe@30
 | 
   146 following examples:
 | 
| 
jbe@0
 | 
   147 
 | 
| 
jbe@0
 | 
   148     require "seqlua"
 | 
| 
jbe@0
 | 
   149 
 | 
| 
jbe@0
 | 
   150     t = {"a", "b", "c"}
 | 
| 
jbe@0
 | 
   151 
 | 
| 
jbe@0
 | 
   152     for i, v in ipairs(t) do
 | 
| 
jbe@0
 | 
   153       print(i, v)
 | 
| 
jbe@0
 | 
   154     end
 | 
| 
jbe@0
 | 
   155     -- prints:
 | 
| 
jbe@0
 | 
   156     --  1   a
 | 
| 
jbe@0
 | 
   157     --  2   b
 | 
| 
jbe@0
 | 
   158     --  3   c
 | 
| 
jbe@0
 | 
   159 
 | 
| 
jbe@25
 | 
   160     print(string.concat(",", t))
 | 
| 
jbe@25
 | 
   161     -- prints: a,b,c
 | 
| 
jbe@25
 | 
   162 
 | 
| 
jbe@19
 | 
   163     function alphabet()
 | 
| 
jbe@0
 | 
   164       local letter = nil
 | 
| 
jbe@0
 | 
   165       return function()
 | 
| 
jbe@0
 | 
   166         if letter == nil then
 | 
| 
jbe@19
 | 
   167           letter = "a"
 | 
| 
jbe@19
 | 
   168         elseif letter == "z" then
 | 
| 
jbe@0
 | 
   169           return nil
 | 
| 
jbe@0
 | 
   170         else
 | 
| 
jbe@0
 | 
   171           letter = string.char(string.byte(letter) + 1)
 | 
| 
jbe@0
 | 
   172         end
 | 
| 
jbe@0
 | 
   173         return letter
 | 
| 
jbe@0
 | 
   174       end
 | 
| 
jbe@0
 | 
   175     end
 | 
| 
jbe@0
 | 
   176 
 | 
| 
jbe@23
 | 
   177     for i, v in ipairs(alphabet()) do
 | 
| 
jbe@0
 | 
   178       print(i, v)
 | 
| 
jbe@0
 | 
   179     end
 | 
| 
jbe@0
 | 
   180     -- prints:
 | 
| 
jbe@0
 | 
   181     --  1   a
 | 
| 
jbe@0
 | 
   182     --  2   b
 | 
| 
jbe@0
 | 
   183     --  3   c
 | 
| 
jbe@0
 | 
   184     --  ...
 | 
| 
jbe@0
 | 
   185     --  25  y
 | 
| 
jbe@0
 | 
   186     --  26  z
 | 
| 
jbe@0
 | 
   187 
 | 
| 
jbe@25
 | 
   188     print(string.concat(",", alphabet()))
 | 
| 
jbe@25
 | 
   189     -- 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
 | 
   190 
 | 
| 
jbe@26
 | 
   191     function filter(f)
 | 
| 
jbe@26
 | 
   192       return function(seq)
 | 
| 
jbe@26
 | 
   193         return coroutine.wrap(function()
 | 
| 
jbe@26
 | 
   194           for i, v in ipairs(seq) do f(v) end
 | 
| 
jbe@26
 | 
   195         end)
 | 
| 
jbe@26
 | 
   196       end
 | 
| 
jbe@0
 | 
   197     end
 | 
| 
jbe@19
 | 
   198 
 | 
| 
jbe@29
 | 
   199     alpha_beta_x = filter(function(v)
 | 
| 
jbe@28
 | 
   200       if v == "a" then
 | 
| 
jbe@28
 | 
   201         coroutine.yield("alpha")
 | 
| 
jbe@28
 | 
   202       elseif v == "b" then
 | 
| 
jbe@28
 | 
   203         coroutine.yield("beta")
 | 
| 
jbe@28
 | 
   204       elseif type(v) == "number" then
 | 
| 
jbe@23
 | 
   205         for i = 1, v do
 | 
| 
jbe@28
 | 
   206           coroutine.yield("X")
 | 
| 
jbe@23
 | 
   207         end
 | 
| 
jbe@0
 | 
   208       end
 | 
| 
jbe@26
 | 
   209     end)
 | 
| 
jbe@0
 | 
   210 
 | 
| 
jbe@29
 | 
   211     print((","):concat(alpha_beta_x{"a", 3, "b", "c", "d"}))
 | 
| 
jbe@28
 | 
   212     -- prints: alpha,X,X,X,beta
 | 
| 
jbe@25
 | 
   213 
 | 
| 
jbe@29
 | 
   214     print((","):concat(alpha_beta_x(alphabet())))
 | 
| 
jbe@28
 | 
   215     -- prints: alpha,beta
 | 
| 
jbe@27
 | 
   216 
 | 
| 
jbe@0
 | 
   217 
 | 
| 
jbe@37
 | 
   218 
 | 
| 
jbe@0
 | 
   219 C part of the library
 | 
| 
jbe@0
 | 
   220 ---------------------
 | 
| 
jbe@0
 | 
   221 
 | 
| 
jbe@0
 | 
   222 In ``seqlualib.h``, the following macro is defined:
 | 
| 
jbe@0
 | 
   223 
 | 
| 
jbe@0
 | 
   224     #define seqlua_iterloop(L, iter, idx) \
 | 
| 
jbe@0
 | 
   225       for ( \
 | 
| 
jbe@0
 | 
   226         seqlua_iterinit((L), (iter), (idx)); \
 | 
| 
jbe@0
 | 
   227         seqlua_iternext(iter); \
 | 
| 
jbe@25
 | 
   228       )
 | 
| 
jbe@25
 | 
   229 
 | 
| 
jbe@25
 | 
   230 and
 | 
| 
jbe@25
 | 
   231 
 | 
| 
jbe@25
 | 
   232     #define seqlua_iterloopauto(L, iter, idx) \
 | 
| 
jbe@25
 | 
   233       for ( \
 | 
| 
jbe@25
 | 
   234         seqlua_iterinit((L), (iter), (idx)); \
 | 
| 
jbe@25
 | 
   235         seqlua_iternext(iter); \
 | 
| 
jbe@0
 | 
   236         lua_pop((L), 1) \
 | 
| 
jbe@0
 | 
   237       )
 | 
| 
jbe@0
 | 
   238 
 | 
| 
jbe@23
 | 
   239 This macro allows iteration over either tables or iterator functions as the
 | 
| 
jbe@23
 | 
   240 following example function demonstrates:
 | 
| 
jbe@0
 | 
   241 
 | 
| 
jbe@0
 | 
   242     int printcsv(lua_State *L) {
 | 
| 
jbe@0
 | 
   243       seqlua_Iterator iter;
 | 
| 
jbe@0
 | 
   244       seqlua_iterloop(L, &iter, 1) {
 | 
| 
jbe@0
 | 
   245         if (seqlua_itercount(&iter) > 1) fputs(",", stdout);
 | 
| 
jbe@0
 | 
   246         fputs(luaL_tolstring(L, -1, NULL), stdout);
 | 
| 
jbe@25
 | 
   247         // two values need to be popped (the value pushed by
 | 
| 
jbe@25
 | 
   248         // seqlua_iternext and the value pushed by luaL_tolstring)
 | 
| 
jbe@25
 | 
   249         lua_pop(L, 2);
 | 
| 
jbe@0
 | 
   250       }
 | 
| 
jbe@0
 | 
   251       fputs("\n", stdout);
 | 
| 
jbe@0
 | 
   252       return 0;
 | 
| 
jbe@0
 | 
   253     }
 | 
| 
jbe@0
 | 
   254 
 | 
| 
jbe@11
 | 
   255     printcsv{"a", "b", "c"}
 | 
| 
jbe@11
 | 
   256     -- prints: a,b,c
 | 
| 
jbe@11
 | 
   257 
 | 
| 
jbe@11
 | 
   258     printcsv(assert(io.open("testfile")):lines())
 | 
| 
jbe@11
 | 
   259     -- prints: line1,line2,... of "testfile"
 | 
| 
jbe@0
 | 
   260 
 | 
| 
jbe@31
 | 
   261 NOTE: During iteration using ``seqlua_iterloop``, ``seqlua_iterloopauto``, or
 | 
| 
jbe@31
 | 
   262 ``seqlua_iterinit``, three extra elements are stored on the stack (additionally
 | 
| 
jbe@31
 | 
   263 to the value). These extra elements are removed automatically when the loop ends
 | 
| 
jbe@31
 | 
   264 (i.e. when ``seqlua_iternext`` returns zero). The value pushed onto the stack
 | 
| 
jbe@31
 | 
   265 for every iteration step has to be removed manually from the stack, unless
 | 
| 
jbe@31
 | 
   266 ``seqlua_iterloopauto`` is used.
 | 
| 
jbe@0
 | 
   267 
 | 
| 
jbe@31
 | 
   268 
 | 
| 
jbe@37
 | 
   269 
 | 
| 
jbe@35
 | 
   270 Respected metamethods
 | 
| 
jbe@35
 | 
   271 ---------------------
 | 
| 
jbe@35
 | 
   272 
 | 
| 
jbe@35
 | 
   273 Regarding the behavior of the Lua functions and the C functions and macros
 | 
| 
jbe@35
 | 
   274 provided by this extension, an existing ``__index`` metamethod will be
 | 
| 
jbe@35
 | 
   275 respected automatically. An existing ``__ipairs`` metamethod, however, takes
 | 
| 
jbe@35
 | 
   276 precedence.
 | 
| 
jbe@35
 | 
   277 
 | 
| 
jbe@35
 | 
   278 If the ``__ipairs`` field of a value's metatable is set, then it must always
 | 
| 
jbe@35
 | 
   279 refer to a function. When starting iteration over a value with such a
 | 
| 
jbe@35
 | 
   280 metamethod being set, then this function is called with ``self`` (i.e. the
 | 
| 
jbe@35
 | 
   281 value itself) passed as first argument. The return values of the ``__ipairs``
 | 
| 
jbe@35
 | 
   282 metamethod may take one of the following 4 forms:
 | 
| 
jbe@35
 | 
   283 
 | 
| 
jbe@35
 | 
   284 * ``return function_or_callable, static_argument, startindex`` causes the three
 | 
| 
jbe@35
 | 
   285   arguments to be returned by ``ipairs`` without further modification. Using
 | 
| 
jbe@35
 | 
   286   the C macros and functions for iteration, the behavior is according to the
 | 
| 
jbe@35
 | 
   287   generic loop statement in Lua:
 | 
| 
jbe@35
 | 
   288   ``for i, v in function_or_callable, static_argument, startindex do ... end``
 | 
| 
jbe@35
 | 
   289 * ``return "raw", table`` will result in iteration over the table ``table``
 | 
| 
jbe@35
 | 
   290   using ``lua_rawgeti``
 | 
| 
jbe@35
 | 
   291 * ``return "index", table_or_userdata`` will result in iteration over the table
 | 
| 
jbe@35
 | 
   292   or userdata while respecting any ``__index`` metamethod of the table or
 | 
| 
jbe@35
 | 
   293   userdata value
 | 
| 
jbe@35
 | 
   294 * ``return "call", function_or_callable`` will use the callable value as
 | 
| 
jbe@35
 | 
   295   (function) iterator where the function is expected to return a single value
 | 
| 
jbe@35
 | 
   296   without any index (the index is inserted automatically when using the
 | 
| 
jbe@35
 | 
   297   ``ipairs`` function for iteration)
 | 
| 
jbe@35
 | 
   298 
 | 
| 
jbe@35
 | 
   299 These possiblities are demonstrated by the following example code:
 | 
| 
jbe@35
 | 
   300 
 | 
| 
jbe@35
 | 
   301     require "seqlua"
 | 
| 
jbe@35
 | 
   302 
 | 
| 
jbe@35
 | 
   303     do
 | 
| 
jbe@35
 | 
   304       local function ipairsaux(t, i)
 | 
| 
jbe@35
 | 
   305         i = i + 1
 | 
| 
jbe@35
 | 
   306         if i <= 3 then
 | 
| 
jbe@35
 | 
   307           return i, t[i]
 | 
| 
jbe@35
 | 
   308         end
 | 
| 
jbe@35
 | 
   309       end
 | 
| 
jbe@35
 | 
   310       custom = setmetatable(
 | 
| 
jbe@35
 | 
   311         {"one", "two", "three", "four", "five"},
 | 
| 
jbe@35
 | 
   312         {
 | 
| 
jbe@35
 | 
   313           __ipairs = function(self)
 | 
| 
jbe@35
 | 
   314             return ipairsaux, self, 0
 | 
| 
jbe@35
 | 
   315           end
 | 
| 
jbe@35
 | 
   316         }
 | 
| 
jbe@35
 | 
   317       )
 | 
| 
jbe@35
 | 
   318     end
 | 
| 
jbe@35
 | 
   319     print(string.concat(",", custom))
 | 
| 
jbe@36
 | 
   320     -- prints: one,two,three
 | 
| 
jbe@35
 | 
   321     -- (note that "four" and "five" are not printed)
 | 
| 
jbe@35
 | 
   322 
 | 
| 
jbe@35
 | 
   323     tbl = {"alpha", "beta"}
 | 
| 
jbe@35
 | 
   324 
 | 
| 
jbe@35
 | 
   325     proxy1 = setmetatable({}, {__index = tbl})
 | 
| 
jbe@35
 | 
   326     for i, v in ipairs(proxy1) do print(i, v) end
 | 
| 
jbe@35
 | 
   327     -- prints:
 | 
| 
jbe@35
 | 
   328     --  1   alpha
 | 
| 
jbe@35
 | 
   329     --  2   beta
 | 
| 
jbe@35
 | 
   330 
 | 
| 
jbe@35
 | 
   331     proxy2 = setmetatable({}, {
 | 
| 
jbe@35
 | 
   332       __ipairs = function(self)
 | 
| 
jbe@35
 | 
   333         return "index", proxy1
 | 
| 
jbe@35
 | 
   334       end
 | 
| 
jbe@35
 | 
   335     })
 | 
| 
jbe@35
 | 
   336     for i, v in ipairs(proxy2) do print(i, v) end
 | 
| 
jbe@35
 | 
   337     -- prints:
 | 
| 
jbe@35
 | 
   338     --  1   alpha
 | 
| 
jbe@35
 | 
   339     --  2   beta
 | 
| 
jbe@35
 | 
   340     print(proxy2[1])
 | 
| 
jbe@35
 | 
   341     -- prints: nil
 | 
| 
jbe@35
 | 
   342 
 | 
| 
jbe@35
 | 
   343     cursor = setmetatable({
 | 
| 
jbe@35
 | 
   344       "alice", "bob", "charlie", pos=1
 | 
| 
jbe@35
 | 
   345     }, {
 | 
| 
jbe@35
 | 
   346       __call = function(self)
 | 
| 
jbe@35
 | 
   347         local value = self[self.pos]
 | 
| 
jbe@35
 | 
   348         if value == nil then
 | 
| 
jbe@35
 | 
   349           self.pos = 1
 | 
| 
jbe@35
 | 
   350         else
 | 
| 
jbe@35
 | 
   351           self.pos = self.pos + 1
 | 
| 
jbe@35
 | 
   352         end
 | 
| 
jbe@35
 | 
   353         return value
 | 
| 
jbe@35
 | 
   354       end,
 | 
| 
jbe@35
 | 
   355       __ipairs = function(self)
 | 
| 
jbe@35
 | 
   356         return "call", self
 | 
| 
jbe@35
 | 
   357       end
 | 
| 
jbe@35
 | 
   358     })
 | 
| 
jbe@35
 | 
   359     for i, v in ipairs(cursor) do print(i, v) end
 | 
| 
jbe@35
 | 
   360     -- prints:
 | 
| 
jbe@35
 | 
   361     --  1   alice
 | 
| 
jbe@35
 | 
   362     --  2   bob
 | 
| 
jbe@35
 | 
   363     --  3   charlie
 | 
| 
jbe@35
 | 
   364     print(cursor())
 | 
| 
jbe@35
 | 
   365     -- prints: alice
 | 
| 
jbe@35
 | 
   366     for i, v in ipairs(cursor) do print(i, v) end
 | 
| 
jbe@35
 | 
   367     -- prints:
 | 
| 
jbe@35
 | 
   368     --  1   bob
 | 
| 
jbe@35
 | 
   369     --  2   charlie
 | 
| 
jbe@35
 | 
   370     -- (note that "alice" has been returned earlier)
 | 
| 
jbe@35
 | 
   371 
 | 
| 
jbe@35
 | 
   372     coefficients = setmetatable({1.25, 3.14, 17.5}, {
 | 
| 
jbe@35
 | 
   373       __index  = function(self) return 1 end,
 | 
| 
jbe@35
 | 
   374       __ipairs = function(self) return "raw", self end
 | 
| 
jbe@35
 | 
   375     })
 | 
| 
jbe@35
 | 
   376     for i, v in ipairs(coefficients) do print(i, v) end
 | 
| 
jbe@35
 | 
   377     -- prints:
 | 
| 
jbe@35
 | 
   378     --  1   1.25
 | 
| 
jbe@35
 | 
   379     --  2   3.14
 | 
| 
jbe@35
 | 
   380     --  3   17.5
 | 
| 
jbe@35
 | 
   381     -- (note that iteration terminates even if coefficients[4] == 1)
 | 
| 
jbe@35
 | 
   382     print(coefficients[4])
 | 
| 
jbe@35
 | 
   383     -- prints: 1
 | 
| 
jbe@35
 | 
   384 
 | 
| 
jbe@35
 | 
   385 
 |