Skip to content

Commit fd43bdc

Browse files
committedApr 10, 2018
Added iterables.
1 parent 1045e47 commit fd43bdc

File tree

3 files changed

+136
-31
lines changed

3 files changed

+136
-31
lines changed
 

‎README.md

+34-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This is a Lua library I made for more convenient functional programming. It prov
1414
- [Inserting values into lists](#inserting-values-into-lists)
1515
- [String iterators](#string-iterators)
1616
- [Utility functions for wrapped tables](#utility-functions-for-wrapped-tables)
17+
- [Iterables](#iterables)
1718
- [Lambdas](#lambdas)
1819
- [Conditional lambda functions](#conditional-lambda-functions)
1920
- [Utility functions for wrapped and normal functions](#utility-functions-for-wrapped-and-normal-functions)
@@ -152,6 +153,37 @@ end
152153
- `lpairs(t:wrapped table)` This functions works just like `ipairs` when called with a list or wrapped string and just like `pairs` when called with anything else.
153154
- `isList(t:wrapped table or table):boolean` This function returns true if the table is either a list (as a wrapped table) or a normal table that can be turned into a list (i.e. if every key in the table is a number valid for `ipairs`)
154155

156+
### Iterables
157+
Iterables are objects that wrap a function to iterate over: The only parameter they take is a function that takes the current iteration index (or no parameter) and returns a value each time it is called, and nil when there is no new value left to return. Most of the [methods that can be called on wrapped tables](#wrapped-tables-1) can also be called on iterables.
158+
159+
```lua
160+
local function supply(index)
161+
if index <= 10 then
162+
return index * index
163+
end
164+
end
165+
166+
local itr = $(supply) -- Initializing an iterable for an existing function.
167+
168+
for i, j in pairs(itr) do -- This calls the function until it returns nil, starting with an iteration index of 1, and provides the iteration index and value.
169+
print(i, j) -- In this case, the first ten squares of natural numbers are printed.
170+
end
171+
172+
local itr2 = $(i -> i <= 10 and i*i or nil) -- Iterables can also be initialized using lambda functions.
173+
174+
for val in itr2 do -- This iterates through the iterable, only providing the value.
175+
print(val)
176+
end
177+
178+
local itr3 = $( -> switch(math.random(0, 9),
179+
(val! val >= 3 -> val)
180+
)) -- Of course, the supplier function does not have to use the index parameter.
181+
182+
for i, j <- itr3 do -- Selene's for loop syntax works as well.
183+
print(val)
184+
end
185+
```
186+
155187
### Lambdas
156188
Lambdas are wrapped in `()` brackets and always look like `(<var1> [, var2, ...] -> <operation>)`. Alternatively to the `->` you can also use `=>`.
157189
```lua
@@ -265,7 +297,7 @@ These functions will not work directly called on a string, i.e. `string.drop("He
265297
- `string.iter(s:string)` This functions returns an iterator over the string `s`, so you can iterate through the characters of the string using `for index, char in string.iter(s) do ... end`.
266298

267299
### Wrapped tables
268-
These are the functions you can call on wrapped tables. `$()` represents a wrapped list or map, `$l()` represents a list.
300+
These are the functions you can call on wrapped tables. `$()` represents a wrapped list or map, `$l()` represents a list, `$i()` represents an iterable.
269301
- `$():concat(sep:string, i:number, j:number):string` This works exactly like `table.concat`.
270302
- `$():foreach(f:function)` This works exactly like `string.foreach`, just that it will iterate through each key/value pair in the table.
271303
- `$():map(f:function):list or map` This works exactly like `string.map`, just that it will iterate through each key/value pair in the table.
@@ -302,6 +334,7 @@ These are the functions you can call on wrapped tables. `$()` represents a wrapp
302334
- `$l():reverse():list` This function will invert the list so that the last entry will be the first one etc.
303335
- `$l():flatten():list` This works exactly like `table.flatten`.
304336
- `$l():zip(other:list or table or function):list` This will merge the other table (which has to be an ipairs-valid list) or list into itself if both lists have the same length, in the pattern `{{t1[1], t2[1]}, {t1[2], t2[2]}, ...}`. If `other` is a function or wrapped function, it will call it once per iteration and merge the returned value in the described pattern.
337+
- `$i():collect():list` This function will iterate through the iterable and add every returned element to a list which it will return.
305338

306339
### Wrapped strings
307340
Wrapped strings or stringslists can mostly be seen as lists and have most of the functions wrapped tables have (including `drop`, `dropwhile` and `reverse`).

‎selene/lib/selene/init.lua

+99-30
Original file line numberDiff line numberDiff line change
@@ -132,25 +132,26 @@ local function lpairs(obj)
132132
end
133133

134134
local allMaps = { "map", "list", "stringlist" }
135+
local allIterables = { "map", "list", "stringlist", "iterable" }
136+
local allIndexables = {"list", "stringlist", "iterable"}
135137

136138
-- Errors if the value is not a valid type (list or map)
137139
local function checkType(n, have, ...)
138140
have = tblType(have)
139141
local things = { ... }
140-
if #things == 0 then things = allMaps end
141-
local function check(want, ...)
142-
if not want then
143-
return false
144-
else
145-
return have == want or check(...)
142+
if #things == 0 then
143+
things = allIterables
144+
elseif #things == 1 and type(things[1]) == "table" then
145+
things = things[1]
146+
end
147+
for _, want in ipairs(things) do
148+
if have == want then
149+
return
146150
end
147151
end
148-
149-
if not check(table.unpack(things)) then
150-
local msg = string.format("[Selene] bad argument #%d (%s expected, got %s)",
151-
n, table.concat({ ... }, " or "), have)
152-
error(msg, 3)
153-
end
152+
local msg = string.format("[Selene] bad argument #%d (%s expected, got %s)",
153+
n, table.concat(things, " or "), have)
154+
error(msg, 3)
154155
end
155156

156157
-- Errors if the value is not a function or does not have the required parameter count
@@ -271,6 +272,7 @@ local fmt = {
271272

272273
local _Table = {}
273274
local _String = {}
275+
local _Iterable = {}
274276

275277
local smt = shallowcopy(mt)
276278
smt.ltype = "stringlist"
@@ -279,6 +281,42 @@ smt.__call = function(str)
279281
end
280282
smt.__tostring = smt.__call
281283

284+
local function inext(spl, i)
285+
local v = spl(i)
286+
if v == nil then
287+
return v
288+
else
289+
return i+1, v
290+
end
291+
end
292+
293+
local imt = {
294+
__call = function(itr)
295+
local v = itr._spl(itr._i)
296+
itr._i = itr._i + 1
297+
return v
298+
end,
299+
__len = function(itr)
300+
error("[Selene] attempt to get length of " .. tblType(itr), 2)
301+
end,
302+
__pairs = function(itr)
303+
return inext, itr._spl, 1
304+
end,
305+
__ipairs = function(itr)
306+
return inext, itr._spl, 1
307+
end,
308+
__tostring = function(itr)
309+
return tostring(itr._spl)
310+
end,
311+
__index = function(itr, key)
312+
error("[Selene] attempt to index " .. tblType(itr), 2)
313+
end,
314+
__newindex = function(itr, key, val)
315+
error("[Selene] attempt to insert value into " .. tblType(itr), 2)
316+
end,
317+
ltype = "iterable"
318+
}
319+
282320
--------
283321
-- Initialization functions
284322
--------
@@ -365,11 +403,25 @@ local function newFunc(f, parCnt, applies)
365403
return newF
366404
end
367405

368-
local function newWrappedTable(...)
406+
local function newIterable(spl)
407+
checkType(1, spl, "function")
408+
local newI = {}
409+
newI._spl = spl
410+
newI._i = 1
411+
for i, j in pairs(_Iterable) do
412+
newI[i] = j
413+
end
414+
setmetatable(newI, imt)
415+
return newI
416+
end
417+
418+
local function newGeneric(...)
369419
local t = ...
370420
if #{ ... } > 1 then t = { ... } end
371421
if type(t) == "string" then
372422
return newString(t)
423+
elseif tblType(t) == "function" then
424+
return newIterable(t)
373425
else
374426
return newListOrMap(t)
375427
end
@@ -685,14 +737,17 @@ local function tbl_foldright(self, start, f)
685737
end
686738

687739
local function tbl_reduceleft(self, f)
688-
checkType(1, self, "list", "stringlist")
740+
checkType(1, self, "list", "stringlist", "iterable")
689741
checkFunc(2, f)
690-
if #self <= 0 then
691-
error("[Selene] bad argument #1 (got empty list)", 2)
692-
end
693-
local m = self[1]
694-
for i = 2, #self do
695-
m = f(m, self._tbl[i])
742+
local d = false
743+
local m
744+
for i, j in mpairs(self) do
745+
if d then
746+
m = f(m, j)
747+
else
748+
d = true
749+
m = j
750+
end
696751
end
697752
return m
698753
end
@@ -714,7 +769,7 @@ local function tbl_find(self, f)
714769
end
715770

716771
local function tbl_index(self, f)
717-
checkType(1, self, "list", "stringlist")
772+
checkType(1, self, allIndexables)
718773
checkFunc(2, f)
719774
local parCnt = checkParCnt(parCount(f, 2))
720775
for i, j in mpairs(self) do
@@ -742,7 +797,7 @@ local function rawflatten(self)
742797
end
743798

744799
local function tbl_flatten(self)
745-
checkType(1, self, "list")
800+
checkType(1, self, "list", "iterable")
746801
return newListOrMap(rawflatten(self._tbl))
747802
end
748803

@@ -801,7 +856,7 @@ local function tbl_contains(self, val)
801856
end
802857

803858
local function tbl_containskey(self, key)
804-
checkType(1, self)
859+
checkType(1, self, allMaps)
805860
return self._tbl[key] ~= nil
806861
end
807862

@@ -859,7 +914,7 @@ local function rawvalues(self)
859914
end
860915

861916
local function tbl_clear(self)
862-
checkType(1, self)
917+
checkType(1, self, allMaps)
863918
for _, j in ipairs(rawkeys(self)) do
864919
self._tbl[j] = nil
865920
end
@@ -876,6 +931,16 @@ local function tbl_values(self)
876931
return newList(rawvalues(self))
877932
end
878933

934+
-- for iterable objects
935+
936+
local function itr_collect(self)
937+
local collected = {}
938+
for i, j in mpairs(self) do
939+
insert(collected, false, i, j)
940+
end
941+
return newListOrMap(collected)
942+
end
943+
879944
-- for the actual table library
880945

881946
local function tbl_range(start, stop, step)
@@ -904,12 +969,12 @@ local function tbl_zipped(one, two)
904969
end
905970

906971
local function tbl_call(self, f, ...)
907-
checkType(1, self)
972+
checkType(1, self, allMaps)
908973
checkFunc(2, f)
909974
local res = f(self._tbl, ...)
910975
local tRes = tblType(res)
911976
if tRes == "table" or tRes == "string" then
912-
return newWrappedTable(res)
977+
return newGeneric(res)
913978
else
914979
return res
915980
end
@@ -991,7 +1056,7 @@ local function str_foreach(self, f)
9911056
checkFunc(2, f)
9921057
local parCnt = checkParCnt(parCount(f))
9931058
for i = 1, #self do
994-
f(parCnt(i, j))
1059+
f(parCnt(i, self:sub(i,i)))
9951060
end
9961061
end
9971062

@@ -1340,15 +1405,18 @@ local function loadSeleneConstructs()
13401405
_Table.values = tbl_values
13411406

13421407
_Table.shallowcopy = function(self)
1343-
checkType(1, self)
1408+
checkType(1, self, allMaps)
13441409
return newListOrMap(shallowcopy(self._tbl))
13451410
end
13461411

13471412
_Table.switch = function(self, ...)
1348-
checkType(1, self)
1413+
checkType(1, self, allMaps)
13491414
return switch(self, ...)
13501415
end
13511416

1417+
_Iterable = shallowcopy(_Table)
1418+
_Iterable.collect = itr_collect
1419+
13521420
_String.foreach = tbl_foreach
13531421
_String.map = tbl_map
13541422
_String.flatmap = tbl_flatmap
@@ -1396,14 +1464,15 @@ local function loadSelene(env, lvMode)
13961464
env._selene.liveMode = true
13971465
end
13981466

1399-
env._selene._new = newWrappedTable
1467+
env._selene._new = newGeneric
14001468
if not env.checkArg then
14011469
env.checkArg = checkArg
14021470
env._selene._checkArg = true
14031471
end
14041472
env._selene._newString = newString
14051473
env._selene._newList = newList
14061474
env._selene._newFunc = newFunc
1475+
env._selene._newIterable = newIterable
14071476
env._selene._newOptional = newOptional
14081477
env._selene._VERSION = VERSION
14091478
env._selene._parse = parse

‎selene/lib/selene/parser.lua

+3
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,9 @@ local function findDollars(tokens, i, part, line)
322322
elseif curr:find("^o") then
323323
tokens[i][1] = "_selene._newOptional"
324324
table.remove(tokens, i + 1)
325+
elseif curr:find("^i") then
326+
tokens[i][1] = "_selene._newIterable"
327+
table.remove(tokens, i + 1)
325328
else
326329
perror("invalid $ at index " .. i .. " (line " .. line .. ")")
327330
end

0 commit comments

Comments
 (0)
Please sign in to comment.