rev |
line source |
jbe@0
|
1 seqlua: Extended sequences and iterators in Lua
|
jbe@0
|
2 ===============================================
|
jbe@0
|
3
|
jbe@0
|
4 This is an experimental package to extend Lua in the following manner:
|
jbe@0
|
5
|
jbe@30
|
6 * allow ``ipairs(seq)`` to accept tables as well as functions (i.e function
|
jbe@30
|
7 iterators),
|
jbe@32
|
8 * add a new function ``string.concat(separator, seq)`` to concat 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@0
|
11 tables and iterator functions with the same 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@0
|
18
|
jbe@0
|
19
|
jbe@0
|
20 Lua part of the library
|
jbe@0
|
21 -----------------------
|
jbe@0
|
22
|
jbe@30
|
23 The modified ``ipairs(seq)`` and the new ``string.concat(sep, seq)`` functions
|
jbe@30
|
24 accept either a table or a function as ``seq``. This is demonstrated in the
|
jbe@30
|
25 following examples:
|
jbe@0
|
26
|
jbe@0
|
27 require "seqlua"
|
jbe@0
|
28
|
jbe@0
|
29 t = {"a", "b", "c"}
|
jbe@0
|
30
|
jbe@0
|
31 for i, v in ipairs(t) do
|
jbe@0
|
32 print(i, v)
|
jbe@0
|
33 end
|
jbe@0
|
34 -- prints:
|
jbe@0
|
35 -- 1 a
|
jbe@0
|
36 -- 2 b
|
jbe@0
|
37 -- 3 c
|
jbe@0
|
38
|
jbe@25
|
39 print(string.concat(",", t))
|
jbe@25
|
40 -- prints: a,b,c
|
jbe@25
|
41
|
jbe@19
|
42 function alphabet()
|
jbe@0
|
43 local letter = nil
|
jbe@0
|
44 return function()
|
jbe@0
|
45 if letter == nil then
|
jbe@19
|
46 letter = "a"
|
jbe@19
|
47 elseif letter == "z" then
|
jbe@0
|
48 return nil
|
jbe@0
|
49 else
|
jbe@0
|
50 letter = string.char(string.byte(letter) + 1)
|
jbe@0
|
51 end
|
jbe@0
|
52 return letter
|
jbe@0
|
53 end
|
jbe@0
|
54 end
|
jbe@0
|
55
|
jbe@23
|
56 for i, v in ipairs(alphabet()) do
|
jbe@0
|
57 print(i, v)
|
jbe@0
|
58 end
|
jbe@0
|
59 -- prints:
|
jbe@0
|
60 -- 1 a
|
jbe@0
|
61 -- 2 b
|
jbe@0
|
62 -- 3 c
|
jbe@0
|
63 -- ...
|
jbe@0
|
64 -- 25 y
|
jbe@0
|
65 -- 26 z
|
jbe@0
|
66
|
jbe@25
|
67 print(string.concat(",", alphabet()))
|
jbe@25
|
68 -- 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
|
69
|
jbe@26
|
70 function filter(f)
|
jbe@26
|
71 return function(seq)
|
jbe@26
|
72 return coroutine.wrap(function()
|
jbe@26
|
73 for i, v in ipairs(seq) do f(v) end
|
jbe@26
|
74 end)
|
jbe@26
|
75 end
|
jbe@0
|
76 end
|
jbe@19
|
77
|
jbe@29
|
78 alpha_beta_x = filter(function(v)
|
jbe@28
|
79 if v == "a" then
|
jbe@28
|
80 coroutine.yield("alpha")
|
jbe@28
|
81 elseif v == "b" then
|
jbe@28
|
82 coroutine.yield("beta")
|
jbe@28
|
83 elseif type(v) == "number" then
|
jbe@23
|
84 for i = 1, v do
|
jbe@28
|
85 coroutine.yield("X")
|
jbe@23
|
86 end
|
jbe@0
|
87 end
|
jbe@26
|
88 end)
|
jbe@0
|
89
|
jbe@29
|
90 print((","):concat(alpha_beta_x{"a", 3, "b", "c", "d"}))
|
jbe@28
|
91 -- prints: alpha,X,X,X,beta
|
jbe@25
|
92
|
jbe@29
|
93 print((","):concat(alpha_beta_x(alphabet())))
|
jbe@28
|
94 -- prints: alpha,beta
|
jbe@27
|
95
|
jbe@0
|
96
|
jbe@0
|
97 C part of the library
|
jbe@0
|
98 ---------------------
|
jbe@0
|
99
|
jbe@0
|
100 In ``seqlualib.h``, the following macro is defined:
|
jbe@0
|
101
|
jbe@0
|
102 #define seqlua_iterloop(L, iter, idx) \
|
jbe@0
|
103 for ( \
|
jbe@0
|
104 seqlua_iterinit((L), (iter), (idx)); \
|
jbe@0
|
105 seqlua_iternext(iter); \
|
jbe@25
|
106 )
|
jbe@25
|
107
|
jbe@25
|
108 and
|
jbe@25
|
109
|
jbe@25
|
110 #define seqlua_iterloopauto(L, iter, idx) \
|
jbe@25
|
111 for ( \
|
jbe@25
|
112 seqlua_iterinit((L), (iter), (idx)); \
|
jbe@25
|
113 seqlua_iternext(iter); \
|
jbe@0
|
114 lua_pop((L), 1) \
|
jbe@0
|
115 )
|
jbe@0
|
116
|
jbe@23
|
117 This macro allows iteration over either tables or iterator functions as the
|
jbe@23
|
118 following example function demonstrates:
|
jbe@0
|
119
|
jbe@0
|
120 int printcsv(lua_State *L) {
|
jbe@0
|
121 seqlua_Iterator iter;
|
jbe@0
|
122 seqlua_iterloop(L, &iter, 1) {
|
jbe@0
|
123 if (seqlua_itercount(&iter) > 1) fputs(",", stdout);
|
jbe@0
|
124 fputs(luaL_tolstring(L, -1, NULL), stdout);
|
jbe@25
|
125 // two values need to be popped (the value pushed by
|
jbe@25
|
126 // seqlua_iternext and the value pushed by luaL_tolstring)
|
jbe@25
|
127 lua_pop(L, 2);
|
jbe@0
|
128 }
|
jbe@0
|
129 fputs("\n", stdout);
|
jbe@0
|
130 return 0;
|
jbe@0
|
131 }
|
jbe@0
|
132
|
jbe@11
|
133 printcsv{"a", "b", "c"}
|
jbe@11
|
134 -- prints: a,b,c
|
jbe@11
|
135
|
jbe@11
|
136 printcsv(assert(io.open("testfile")):lines())
|
jbe@11
|
137 -- prints: line1,line2,... of "testfile"
|
jbe@0
|
138
|
jbe@31
|
139 NOTE: During iteration using ``seqlua_iterloop``, ``seqlua_iterloopauto``, or
|
jbe@31
|
140 ``seqlua_iterinit``, three extra elements are stored on the stack (additionally
|
jbe@31
|
141 to the value). These extra elements are removed automatically when the loop ends
|
jbe@31
|
142 (i.e. when ``seqlua_iternext`` returns zero). The value pushed onto the stack
|
jbe@31
|
143 for every iteration step has to be removed manually from the stack, unless
|
jbe@31
|
144 ``seqlua_iterloopauto`` is used.
|
jbe@0
|
145
|
jbe@31
|
146
|
jbe@35
|
147 Respected metamethods
|
jbe@35
|
148 ---------------------
|
jbe@35
|
149
|
jbe@35
|
150 Regarding the behavior of the Lua functions and the C functions and macros
|
jbe@35
|
151 provided by this extension, an existing ``__index`` metamethod will be
|
jbe@35
|
152 respected automatically. An existing ``__ipairs`` metamethod, however, takes
|
jbe@35
|
153 precedence.
|
jbe@35
|
154
|
jbe@35
|
155 If the ``__ipairs`` field of a value's metatable is set, then it must always
|
jbe@35
|
156 refer to a function. When starting iteration over a value with such a
|
jbe@35
|
157 metamethod being set, then this function is called with ``self`` (i.e. the
|
jbe@35
|
158 value itself) passed as first argument. The return values of the ``__ipairs``
|
jbe@35
|
159 metamethod may take one of the following 4 forms:
|
jbe@35
|
160
|
jbe@35
|
161 * ``return function_or_callable, static_argument, startindex`` causes the three
|
jbe@35
|
162 arguments to be returned by ``ipairs`` without further modification. Using
|
jbe@35
|
163 the C macros and functions for iteration, the behavior is according to the
|
jbe@35
|
164 generic loop statement in Lua:
|
jbe@35
|
165 ``for i, v in function_or_callable, static_argument, startindex do ... end``
|
jbe@35
|
166 * ``return "raw", table`` will result in iteration over the table ``table``
|
jbe@35
|
167 using ``lua_rawgeti``
|
jbe@35
|
168 * ``return "index", table_or_userdata`` will result in iteration over the table
|
jbe@35
|
169 or userdata while respecting any ``__index`` metamethod of the table or
|
jbe@35
|
170 userdata value
|
jbe@35
|
171 * ``return "call", function_or_callable`` will use the callable value as
|
jbe@35
|
172 (function) iterator where the function is expected to return a single value
|
jbe@35
|
173 without any index (the index is inserted automatically when using the
|
jbe@35
|
174 ``ipairs`` function for iteration)
|
jbe@35
|
175
|
jbe@35
|
176 These possiblities are demonstrated by the following example code:
|
jbe@35
|
177
|
jbe@35
|
178 require "seqlua"
|
jbe@35
|
179
|
jbe@35
|
180 do
|
jbe@35
|
181 local function ipairsaux(t, i)
|
jbe@35
|
182 i = i + 1
|
jbe@35
|
183 if i <= 3 then
|
jbe@35
|
184 return i, t[i]
|
jbe@35
|
185 end
|
jbe@35
|
186 end
|
jbe@35
|
187 custom = setmetatable(
|
jbe@35
|
188 {"one", "two", "three", "four", "five"},
|
jbe@35
|
189 {
|
jbe@35
|
190 __ipairs = function(self)
|
jbe@35
|
191 return ipairsaux, self, 0
|
jbe@35
|
192 end
|
jbe@35
|
193 }
|
jbe@35
|
194 )
|
jbe@35
|
195 end
|
jbe@35
|
196 print(string.concat(",", custom))
|
jbe@36
|
197 -- prints: one,two,three
|
jbe@35
|
198 -- (note that "four" and "five" are not printed)
|
jbe@35
|
199
|
jbe@35
|
200 tbl = {"alpha", "beta"}
|
jbe@35
|
201
|
jbe@35
|
202 proxy1 = setmetatable({}, {__index = tbl})
|
jbe@35
|
203 for i, v in ipairs(proxy1) do print(i, v) end
|
jbe@35
|
204 -- prints:
|
jbe@35
|
205 -- 1 alpha
|
jbe@35
|
206 -- 2 beta
|
jbe@35
|
207
|
jbe@35
|
208 proxy2 = setmetatable({}, {
|
jbe@35
|
209 __ipairs = function(self)
|
jbe@35
|
210 return "index", proxy1
|
jbe@35
|
211 end
|
jbe@35
|
212 })
|
jbe@35
|
213 for i, v in ipairs(proxy2) do print(i, v) end
|
jbe@35
|
214 -- prints:
|
jbe@35
|
215 -- 1 alpha
|
jbe@35
|
216 -- 2 beta
|
jbe@35
|
217 print(proxy2[1])
|
jbe@35
|
218 -- prints: nil
|
jbe@35
|
219
|
jbe@35
|
220 cursor = setmetatable({
|
jbe@35
|
221 "alice", "bob", "charlie", pos=1
|
jbe@35
|
222 }, {
|
jbe@35
|
223 __call = function(self)
|
jbe@35
|
224 local value = self[self.pos]
|
jbe@35
|
225 if value == nil then
|
jbe@35
|
226 self.pos = 1
|
jbe@35
|
227 else
|
jbe@35
|
228 self.pos = self.pos + 1
|
jbe@35
|
229 end
|
jbe@35
|
230 return value
|
jbe@35
|
231 end,
|
jbe@35
|
232 __ipairs = function(self)
|
jbe@35
|
233 return "call", self
|
jbe@35
|
234 end
|
jbe@35
|
235 })
|
jbe@35
|
236 for i, v in ipairs(cursor) do print(i, v) end
|
jbe@35
|
237 -- prints:
|
jbe@35
|
238 -- 1 alice
|
jbe@35
|
239 -- 2 bob
|
jbe@35
|
240 -- 3 charlie
|
jbe@35
|
241 print(cursor())
|
jbe@35
|
242 -- prints: alice
|
jbe@35
|
243 for i, v in ipairs(cursor) do print(i, v) end
|
jbe@35
|
244 -- prints:
|
jbe@35
|
245 -- 1 bob
|
jbe@35
|
246 -- 2 charlie
|
jbe@35
|
247 -- (note that "alice" has been returned earlier)
|
jbe@35
|
248
|
jbe@35
|
249 coefficients = setmetatable({1.25, 3.14, 17.5}, {
|
jbe@35
|
250 __index = function(self) return 1 end,
|
jbe@35
|
251 __ipairs = function(self) return "raw", self end
|
jbe@35
|
252 })
|
jbe@35
|
253 for i, v in ipairs(coefficients) do print(i, v) end
|
jbe@35
|
254 -- prints:
|
jbe@35
|
255 -- 1 1.25
|
jbe@35
|
256 -- 2 3.14
|
jbe@35
|
257 -- 3 17.5
|
jbe@35
|
258 -- (note that iteration terminates even if coefficients[4] == 1)
|
jbe@35
|
259 print(coefficients[4])
|
jbe@35
|
260 -- prints: 1
|
jbe@35
|
261
|
jbe@35
|
262
|