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