seqlua

annotate README @ 39:40de0fd016c4

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

Impressum / About Us