├── .gitignore ├── README.md ├── lazy.js ├── package.json └── test ├── bucket.js ├── complex.js ├── custom.js ├── em.js ├── filter.js ├── foldr.js ├── forEach.js ├── head.js ├── join.js ├── lines.js ├── map.js ├── pipe.js ├── product.js ├── range.js ├── skip.js ├── sum.js ├── tail.js ├── take.js └── takeWhile.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lazy lists for node 2 | =================== 3 | 4 | 5 | # Table of contents: 6 | 7 | [Introduction](#Introduction) 8 | 9 | [Documentation](#Documentation) 10 | 11 | 12 | # Introduction 13 | Lazy comes really handy when you need to treat a stream of events like a list. 14 | The best use case currently is returning a lazy list from an asynchronous 15 | function, and having data pumped into it via events. In asynchronous 16 | programming you can't just return a regular list because you don't yet have 17 | data for it. The usual solution so far has been to provide a callback that gets 18 | called when the data is available. But doing it this way you lose the power of 19 | chaining functions and creating pipes, which leads to not that nice interfaces. 20 | (See the 2nd example below to see how it improved the interface in one of my 21 | modules.) 22 | 23 | Check out this toy example, first you create a Lazy object: 24 | ```javascript 25 | var Lazy = require('lazy'); 26 | 27 | var lazy = new Lazy; 28 | lazy 29 | .filter(function (item) { 30 | return item % 2 == 0 31 | }) 32 | .take(5) 33 | .map(function (item) { 34 | return item*2; 35 | }) 36 | .join(function (xs) { 37 | console.log(xs); 38 | }); 39 | ``` 40 | 41 | This code says that 'lazy' is going to be a lazy list that filters even 42 | numbers, takes first five of them, then multiplies all of them by 2, and then 43 | calls the join function (think of join as in threads) on the final list. 44 | 45 | And now you can emit 'data' events with data in them at some point later, 46 | ```javascript 47 | [0,1,2,3,4,5,6,7,8,9,10].forEach(function (x) { 48 | lazy.emit('data', x); 49 | }); 50 | ``` 51 | 52 | The output will be produced by the 'join' function, which will output the 53 | expected [0, 4, 8, 12, 16]. 54 | 55 | And here is a real-world example. Some time ago I wrote a hash database for 56 | node.js called node-supermarket (think of key-value store except greater). Now 57 | it had a similar interface as a list, you could .forEach on the stored 58 | elements, .filter them, etc. But being asynchronous in nature it lead to the 59 | following code, littered with callbacks and temporary lists: 60 | ```javascript 61 | var Store = require('supermarket'); 62 | 63 | var db = new Store({ filename : 'users.db', json : true }); 64 | 65 | var users_over_20 = []; 66 | db.filter( 67 | function (user, meta) { 68 | // predicate function 69 | return meta.age > 20; 70 | }, 71 | function (err, user, meta) { 72 | // function that gets executed when predicate is true 73 | if (users_over_20.length < 5) 74 | users_over_20.push(meta); 75 | }, 76 | function () { 77 | // done function, called when all records have been filtered 78 | 79 | // now do something with users_over_20 80 | } 81 | ) 82 | ``` 83 | This code selects first five users who are over 20 years old and stores them 84 | in users_over_20. 85 | 86 | But now we changed the node-supermarket interface to return lazy lists, and 87 | the code became: 88 | ```javascript 89 | var Store = require('supermarket'); 90 | 91 | var db = new Store({ filename : 'users.db', json : true }); 92 | 93 | db.filter(function (user, meta) { 94 | return meta.age > 20; 95 | }) 96 | .take(5) 97 | .join(function (xs) { 98 | // xs contains the first 5 users who are over 20! 99 | }); 100 | ``` 101 | This is so much nicer! 102 | 103 | Here is the latest feature: .lines. Given a stream of data that has \n's in it, 104 | .lines converts that into a list of lines. 105 | 106 | Here is an example from node-iptables that I wrote the other week, 107 | ```javascript 108 | var Lazy = require('lazy'); 109 | var spawn = require('child_process').spawn; 110 | var iptables = spawn('iptables', ['-L', '-n', '-v']); 111 | 112 | Lazy(iptables.stdout) 113 | .lines 114 | .map(String) 115 | .skip(2) // skips the two lines that are iptables header 116 | .map(function (line) { 117 | // packets, bytes, target, pro, opt, in, out, src, dst, opts 118 | var fields = line.trim().split(/\s+/, 9); 119 | return { 120 | parsed : { 121 | packets : fields[0], 122 | bytes : fields[1], 123 | target : fields[2], 124 | protocol : fields[3], 125 | opt : fields[4], 126 | in : fields[5], 127 | out : fields[6], 128 | src : fields[7], 129 | dst : fields[8] 130 | }, 131 | raw : line.trim() 132 | }; 133 | }); 134 | ``` 135 | This example takes the `iptables -L -n -v` command and uses .lines on its output. 136 | Then it .skip's two lines from input and maps a function on all other lines that 137 | creates a data structure from the output. 138 | 139 | 140 | # Documentation 141 | 142 | Supports the following operations: 143 | 144 | * lazy.filter(f) 145 | * lazy.forEach(f) 146 | * lazy.map(f) 147 | * lazy.take(n) 148 | * lazy.takeWhile(f) 149 | * lazy.bucket(init, f) 150 | * lazy.lines 151 | * lazy.sum(f) 152 | * lazy.product(f) 153 | * lazy.foldr(op, i, f) 154 | * lazy.skip(n) 155 | * lazy.head(f) 156 | * lazy.tail(f) 157 | * lazy.join(f) 158 | 159 | The Lazy object itself has a .range property for generating all the possible ranges. 160 | 161 | Here are several examples: 162 | 163 | * Lazy.range('10..') - infinite range starting from 10 164 | * Lazy.range('(10..') - infinite range starting from 11 165 | * Lazy.range(10) - range from 0 to 9 166 | * Lazy.range(-10, 10) - range from -10 to 9 (-10, -9, ... 0, 1, ... 9) 167 | * Lazy.range(-10, 10, 2) - range from -10 to 8, skipping every 2nd element (-10, -8, ... 0, 2, 4, 6, 8) 168 | * Lazy.range(10, 0, 2) - reverse range from 10 to 1, skipping every 2nd element (10, 8, 6, 4, 2) 169 | * Lazy.range(10, 0) - reverse range from 10 to 1 170 | * Lazy.range('5..50') - range from 5 to 49 171 | * Lazy.range('50..44') - range from 50 to 45 172 | * Lazy.range('1,1.1..4') - range from 1 to 4 with increment of 0.1 (1, 1.1, 1.2, ... 3.9) 173 | * Lazy.range('4,3.9..1') - reverse range from 4 to 1 with decerement of 0.1 174 | * Lazy.range('[1..10]') - range from 1 to 10 (all inclusive) 175 | * Lazy.range('[10..1]') - range from 10 to 1 (all inclusive) 176 | * Lazy.range('[1..10)') - range grom 1 to 9 177 | * Lazy.range('[10..1)') - range from 10 to 2 178 | * Lazy.range('(1..10]') - range from 2 to 10 179 | * Lazy.range('(10..1]') - range from 9 to 1 180 | * Lazy.range('(1..10)') - range from 2 to 9 181 | * Lazy.range('[5,10..50]') - range from 5 to 50 with a step of 5 (all inclusive) 182 | 183 | Then you can use other lazy functions on these ranges. 184 | 185 | 186 | -------------------------------------------------------------------------------- /lazy.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var util = require('util'); 3 | var stream = require('stream'); 4 | 5 | function Lazy(em, opts) { 6 | if (!(this instanceof Lazy)) return new Lazy(em, opts); 7 | EventEmitter.call(this); 8 | var self = this; 9 | 10 | 11 | self.once = function (name, f) { 12 | self.on(name, function g () { 13 | self.removeListener(name, g); 14 | f.apply(this, arguments); 15 | }); 16 | } 17 | 18 | if (!opts) opts = {}; 19 | var dataName = opts.data || 'data'; 20 | var pipeName = opts.pipe || 'pipe'; 21 | var endName = opts.pipe || 'end'; 22 | 23 | if (pipeName != endName) { 24 | var piped = false; 25 | self.once(pipeName, function () { piped = true }); 26 | self.once(endName, function () { 27 | if (!piped) self.emit(pipeName); 28 | }); 29 | } 30 | 31 | self.push = function (x) { 32 | self.emit(dataName, x); 33 | } 34 | 35 | self.end = function () { 36 | self.emit(endName); 37 | } 38 | 39 | if (em && em.on) { 40 | em.on(endName, function () { 41 | self.emit(endName); 42 | }); 43 | self.on(pipeName, function () { 44 | em.emit(pipeName); 45 | }); 46 | // Check for v0.10 or Greater (Stream2 has Duplex type) 47 | if (stream.Duplex && em instanceof(stream)) { 48 | em.on('readable', function () { 49 | var x = em.read(); 50 | self.emit(dataName, x); 51 | }); 52 | } else { 53 | // Old Stream1 or Event support 54 | em.on(dataName, function (x) { 55 | self.emit(dataName, x); 56 | }); 57 | } 58 | } 59 | 60 | function newLazy (g, h, l) { 61 | if (!g) { 62 | g = function () { 63 | return true; 64 | }; 65 | } 66 | if (!h) { 67 | h = function (x) { 68 | return x; 69 | }; 70 | } 71 | var lazy = new Lazy(null, opts, l); 72 | self.on(dataName, function (x, y) { 73 | if (g.call(lazy, x)) { 74 | lazy.emit(dataName, h(x), y); 75 | } 76 | }); 77 | self.once(pipeName, function () { 78 | lazy.emit(pipeName); 79 | }); 80 | return lazy; 81 | } 82 | 83 | self.filter = function (f) { 84 | return newLazy(function (x) { 85 | return f(x); 86 | }); 87 | } 88 | 89 | self.forEach = function (f) { 90 | return newLazy(function (x) { 91 | f(x); 92 | return true; 93 | }); 94 | } 95 | 96 | self.map = function (f) { 97 | return newLazy( 98 | function () { return true }, 99 | function (x) { return f(x) } 100 | ); 101 | } 102 | 103 | self.head = function (f) { 104 | var lazy = newLazy(); 105 | lazy.on(dataName, function g (x) { 106 | f(x) 107 | lazy.removeListener(dataName, g) 108 | }) 109 | } 110 | 111 | self.tail = function () { 112 | var skip = true; 113 | return newLazy(function () { 114 | if (skip) { 115 | skip = false; 116 | return false; 117 | } 118 | return true; 119 | }); 120 | } 121 | 122 | self.skip = function (n) { 123 | return newLazy(function () { 124 | if (n > 0) { 125 | n--; 126 | return false; 127 | } 128 | return true; 129 | }); 130 | } 131 | 132 | self.take = function (n) { 133 | return newLazy(function () { 134 | if (n == 0) self.emit(pipeName); 135 | return n-- > 0; 136 | }); 137 | } 138 | 139 | self.takeWhile = function (f) { 140 | var cond = true; 141 | return newLazy(function (x) { 142 | if (cond && f(x)) return true; 143 | cond = false; 144 | self.emit(pipeName); 145 | return false; 146 | }); 147 | } 148 | 149 | self.foldr = function (op, i, f) { 150 | var acc = i; 151 | var lazy = newLazy(); 152 | lazy.on(dataName, function g (x) { 153 | acc = op(x, acc); 154 | }); 155 | lazy.once(pipeName, function () { 156 | f(acc); 157 | }); 158 | } 159 | 160 | self.sum = function (f) { 161 | return self.foldr(function (x, acc) { return x + acc }, 0, f); 162 | } 163 | 164 | self.product = function (f) { 165 | return self.foldr(function (x, acc) { return x*acc }, 1, f); 166 | } 167 | 168 | self.join = function (f) { 169 | var data = [] 170 | var lazy = newLazy(function (x) { 171 | data.push(x); 172 | return true; 173 | }); 174 | lazy.once(pipeName, function () { f(data) }); 175 | return self; 176 | } 177 | 178 | self.bucket = function (init, f) { 179 | var lazy = new Lazy(null, opts); 180 | var yieldTo = function (x) { 181 | lazy.emit(dataName, x); 182 | }; 183 | 184 | var acc = init; 185 | 186 | self.on(dataName, function (x) { 187 | acc = f.call(yieldTo, acc, x); 188 | }); 189 | 190 | self.once(pipeName, function () { 191 | lazy.emit(pipeName); 192 | }); 193 | 194 | // flush on end event 195 | self.once(endName, function () { 196 | var finalBuffer = mergeBuffers(acc); 197 | if (finalBuffer) { 198 | yieldTo(finalBuffer); 199 | } 200 | }); 201 | 202 | return lazy; 203 | } 204 | 205 | // Streams that use this should emit strings or buffers only 206 | self.__defineGetter__('lines', function () { 207 | return self.bucket([], function (chunkArray, chunk) { 208 | var newline = '\n'.charCodeAt(0), lastNewLineIndex = 0; 209 | if (typeof chunk === 'string') chunk = new Buffer(chunk); 210 | if (chunk){ 211 | for (var i = 0; i < chunk.length; i++) { 212 | if (chunk[i] === newline) { 213 | // If we have content from the current chunk to append to our buffers, do it. 214 | if (i > 0) { 215 | chunkArray.push(chunk.slice(lastNewLineIndex, i)); 216 | } 217 | 218 | // Wrap all our buffers and emit it. 219 | this(mergeBuffers(chunkArray)); 220 | lastNewLineIndex = i + 1; 221 | } 222 | } 223 | } 224 | 225 | if (lastNewLineIndex > 0) { 226 | // New line found in the chunk, push the remaining part of the buffer. 227 | if (lastNewLineIndex < chunk.length) { 228 | chunkArray.push(chunk.slice(lastNewLineIndex)); 229 | } 230 | } else { 231 | // No new line found, push the whole buffer. 232 | if (chunk && chunk.length) { 233 | chunkArray.push(chunk); 234 | } 235 | } 236 | return chunkArray; 237 | }); 238 | }); 239 | } 240 | 241 | Lazy.range = function () { 242 | var args = arguments; 243 | var step = 1; 244 | var infinite = false; 245 | 246 | if (args.length == 1 && typeof args[0] == 'number') { 247 | var i = 0, j = args[0]; 248 | } 249 | else if (args.length == 1 && typeof args[0] == 'string') { // 'start[,next]..[end]' 250 | var arg = args[0]; 251 | var startOpen = false, endClosed = false; 252 | if (arg[0] == '(' || arg[0] == '[') { 253 | if (arg[0] == '(') startOpen = true; 254 | arg = arg.slice(1); 255 | } 256 | if (arg.slice(-1) == ']') endClosed = true; 257 | 258 | var parts = arg.split('..'); 259 | if (parts.length != 2) 260 | throw new Error("single argument range takes 'start..' or 'start..end' or 'start,next..end'"); 261 | 262 | if (parts[1] == '') { // 'start..' 263 | var i = parts[0]; 264 | infinite = true; 265 | } 266 | else { // 'start[,next]..end' 267 | var progression = parts[0].split(','); 268 | if (progression.length == 1) { // start..end 269 | var i = parts[0], j = parts[1]; 270 | } 271 | else { // 'start,next..end' 272 | var i = progression[0], j = parts[1]; 273 | step = Math.abs(progression[1]-i); 274 | } 275 | } 276 | 277 | i = parseInt(i, 10); 278 | j = parseInt(j, 10); 279 | 280 | if (startOpen) { 281 | if (infinite || i < j) i++; 282 | else i--; 283 | } 284 | 285 | if (endClosed) { 286 | if (i < j) j++; 287 | else j--; 288 | } 289 | } 290 | else if (args.length == 2 || args.length == 3) { // start, end[, step] 291 | var i = args[0], j = args[1]; 292 | if (args.length == 3) { 293 | var step = args[2]; 294 | } 295 | } 296 | else { 297 | throw new Error("range takes 1, 2 or 3 arguments"); 298 | } 299 | var lazy = new Lazy; 300 | var stopInfinite = false; 301 | lazy.on('pipe', function () { 302 | stopInfinite = true; 303 | }); 304 | if (infinite) { 305 | process.nextTick(function g () { 306 | if (stopInfinite) return; 307 | lazy.emit('data', i++); 308 | process.nextTick(g); 309 | }); 310 | } 311 | else { 312 | process.nextTick(function () { 313 | if (i < j) { 314 | for (; ij; i-=step) { 320 | lazy.emit('data', i) 321 | } 322 | } 323 | lazy.emit('end'); 324 | }); 325 | } 326 | return lazy; 327 | } 328 | 329 | var mergeBuffers = function mergeBuffers(buffers) { 330 | // We expect buffers to be a non-empty Array 331 | if (!buffers || !Array.isArray(buffers) || !buffers.length) return; 332 | 333 | var finalBufferLength, finalBuffer, currentBuffer, currentSize = 0; 334 | 335 | // Sum all the buffers lengths 336 | finalBufferLength = buffers.reduce(function(left, right) { return (left.length||left) + (right.length||right); }, 0); 337 | finalBuffer = new Buffer(finalBufferLength); 338 | while(buffers.length) { 339 | currentBuffer = buffers.shift(); 340 | currentBuffer.copy(finalBuffer, currentSize); 341 | currentSize += currentBuffer.length; 342 | } 343 | 344 | return finalBuffer; 345 | } 346 | 347 | 348 | util.inherits(Lazy, EventEmitter); 349 | module.exports = Lazy; 350 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy", 3 | "version": "1.0.11", 4 | "description": "Lazy lists for node", 5 | "main": "./lazy.js", 6 | "keywords": [ 7 | "lazy lists", "functional" 8 | ], 9 | "author": { 10 | "name": "Peteris Krumins", 11 | "email": "peteris.krumins@gmail.com", 12 | "web": "http://www.catonmat.net", 13 | "twitter": "pkrumins" 14 | }, 15 | "license": { 16 | "type": "MIT" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "http://github.com/pkrumins/node-lazy.git" 21 | }, 22 | "devDependencies" : { 23 | "expresso" : ">=0.7.5" 24 | }, 25 | "engines": { 26 | "node": ">=0.2.0" 27 | }, 28 | "scripts": { 29 | "test": "expresso" 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /test/bucket.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Lazy = require('..'); 3 | 4 | exports.bucket = function () { 5 | var joined = false; 6 | var lazy = new Lazy; 7 | lazy.bucket('', function splitter (acc, x) { 8 | var accx = acc + x; 9 | var i = accx.indexOf('\n'); 10 | if (i >= 0) { 11 | this(accx.slice(0, i)); 12 | return splitter.call(this, accx.slice(i + 1), ''); 13 | } 14 | return accx; 15 | }).join(function (lines) { 16 | assert.deepEqual(lines, 'foo bar baz quux moo'.split(' ')); 17 | joined = true; 18 | }); 19 | 20 | setTimeout(function () { 21 | lazy.emit('data', 'foo\nba'); 22 | }, 50); 23 | setTimeout(function () { 24 | lazy.emit('data', 'r'); 25 | }, 100); 26 | setTimeout(function () { 27 | lazy.emit('data', '\nbaz\nquux\nm'); 28 | }, 150); 29 | setTimeout(function () { 30 | lazy.emit('data', 'oo'); 31 | }, 200); 32 | setTimeout(function () { 33 | lazy.emit('data', '\nzoom'); 34 | lazy.emit('end'); 35 | assert.ok(joined); 36 | }, 250); 37 | }; 38 | -------------------------------------------------------------------------------- /test/complex.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Lazy = require('..'); 3 | var expresso = expresso; 4 | 5 | function range(i, j) { 6 | var r = []; 7 | for (;i 5 }).join(function (xs) { 16 | assert.deepEqual(xs, [6,7,8,9]); 17 | executed = true; 18 | }); 19 | 20 | range(0,10).forEach(function (x) { 21 | lazy.emit('data', x); 22 | }); 23 | lazy.emit('pipe'); 24 | 25 | assert.ok(executed, 'join didn\'t execute'); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /test/foldr.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Lazy = require('..'); 3 | var expresso = expresso; 4 | 5 | function range(i, j) { 6 | var r = []; 7 | for (;i 0); 13 | }) 14 | .join(function (lines) { 15 | assert.deepEqual( 16 | lines.map(function (x) { return x.toString() }), 17 | 'foo bar baz quux moo'.split(' ') 18 | ); 19 | joined = true; 20 | }); 21 | ; 22 | 23 | setTimeout(function () { 24 | lazy.push(new Buffer('foo\nbar')); 25 | lazy.push(new Buffer('\nbaz\nquux\nmoo')); 26 | lazy.push(new Buffer('')); 27 | lazy.push(new Buffer('\ndoom')); 28 | lazy.end(); 29 | assert.ok(joined); 30 | }, 50); 31 | }; 32 | 33 | exports['string lines'] = function () { 34 | var lazy = Lazy(); 35 | var joined = false; 36 | lazy.lines 37 | .forEach(function (line) { 38 | assert.ok(line instanceof Buffer); 39 | assert.ok(!line.toString().match(/\n/)); 40 | assert.ok(line.length > 0); 41 | }) 42 | .join(function (lines) { 43 | assert.deepEqual( 44 | lines.map(function (x) { return x.toString() }), 45 | 'foo bar baz quux moo'.split(' ') 46 | ); 47 | joined = true; 48 | }); 49 | ; 50 | 51 | setTimeout(function () { 52 | lazy.push('foo\nbar'); 53 | lazy.push('\nbaz\nquux\nmoo'); 54 | lazy.push(''); 55 | lazy.push('\ndoom'); 56 | lazy.end(); 57 | assert.ok(joined); 58 | }, 50); 59 | }; 60 | 61 | exports.endStream = function () { 62 | var to = setTimeout(function () { 63 | assert.fail('never finished'); 64 | }, 2500); 65 | 66 | var em = new EventEmitter; 67 | var i = 0; 68 | var lines = []; 69 | Lazy(em).lines.forEach(function (line) { 70 | i ++; 71 | lines.push(line); 72 | if (i == 2) { 73 | clearTimeout(to); 74 | assert.eql(lines.map(String), [ 'foo', 'bar' ]); 75 | } 76 | }); 77 | 78 | setTimeout(function () { 79 | em.emit('data', 'fo'); 80 | }, 100); 81 | 82 | setTimeout(function () { 83 | em.emit('data', 'o\nbar'); 84 | }, 150); 85 | 86 | setTimeout(function () { 87 | em.emit('end'); 88 | }, 200); 89 | }; 90 | -------------------------------------------------------------------------------- /test/map.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Lazy = require('..'); 3 | var expresso = expresso; 4 | 5 | function range(i, j) { 6 | var r = []; 7 | for (;i i) for (;ij;i-=s) r.push(i); 10 | return r; 11 | } 12 | 13 | exports['infinite range'] = function () { 14 | var joinExecuted = false; 15 | Lazy.range('10..').take(10).join(function (xs) { 16 | joinExecuted = true; 17 | assert.deepEqual(xs, range(10, 20)); 18 | assert.equal(xs.length, 10); 19 | }); 20 | 21 | setTimeout(function () { 22 | assert.ok(joinExecuted, 'join didn\'t execute'); 23 | }, 2000); 24 | } 25 | 26 | exports['infinite range half-open'] = function () { 27 | var joinExecuted = false; 28 | Lazy.range('(10..').take(10).join(function (xs) { 29 | joinExecuted = true; 30 | assert.deepEqual(xs, range(11, 21)); 31 | assert.equal(xs.length, 10); 32 | }); 33 | 34 | setTimeout(function () { 35 | assert.ok(joinExecuted, 'join didn\'t execute'); 36 | }, 2000); 37 | } 38 | 39 | exports['range i'] = function () { 40 | var joinExecuted = false; 41 | Lazy.range(10).join(function (xs) { 42 | joinExecuted = true; 43 | assert.deepEqual(xs, range(0, 10)); 44 | assert.equal(xs.length, 10); 45 | }); 46 | 47 | setTimeout(function () { 48 | assert.ok(joinExecuted, 'join didn\'t execute'); 49 | }, 2000); 50 | } 51 | 52 | exports['range i,j (ij)'] = function () { 79 | var joinExecuted = false; 80 | Lazy.range(10, 0, 2).join(function (xs) { 81 | joinExecuted = true; 82 | assert.deepEqual(xs, range(10, 0, 2)); 83 | assert.equal(xs.length, 5); 84 | }); 85 | 86 | setTimeout(function () { 87 | assert.ok(joinExecuted, 'join didn\'t execute'); 88 | }, 2000); 89 | } 90 | 91 | exports['range i,j (i>j)'] = function () { 92 | var joinExecuted = false; 93 | Lazy.range(10, -8).join(function (xs) { 94 | joinExecuted = true; 95 | assert.deepEqual(xs, range(10, -8)); 96 | assert.equal(xs.length, 18); 97 | }); 98 | 99 | setTimeout(function () { 100 | assert.ok(joinExecuted, 'join didn\'t execute'); 101 | }, 2000); 102 | } 103 | 104 | exports['range i..j (ij)'] = function () { 118 | var joinExecuted = false; 119 | Lazy.range('50..44').join(function (xs) { 120 | joinExecuted = true; 121 | assert.deepEqual(xs, range(50, 44)); 122 | assert.equal(xs.length, 6); 123 | }); 124 | 125 | setTimeout(function () { 126 | assert.ok(joinExecuted, 'join didn\'t execute'); 127 | }, 2000); 128 | } 129 | 130 | exports['range i,next..j (ij)'] = function () { 144 | var joinExecuted = false; 145 | Lazy.range('4,3.9..1').join(function (xs) { 146 | joinExecuted = true; 147 | assert.deepEqual(xs, range(4,1,0.1)); 148 | assert.equal(xs.length, 30); 149 | }); 150 | 151 | setTimeout(function () { 152 | assert.ok(joinExecuted, 'join didn\'t execute'); 153 | }, 2000); 154 | } 155 | 156 | exports['range [i..j] (ij)'] = function () { 170 | var joinExecuted = false; 171 | Lazy.range('[10..1]').join(function (xs) { 172 | joinExecuted = true; 173 | assert.deepEqual(xs, range(10,0)); 174 | assert.equal(xs.length, 10); 175 | }); 176 | 177 | setTimeout(function () { 178 | assert.ok(joinExecuted, 'join didn\'t execute'); 179 | }, 2000); 180 | } 181 | 182 | exports['range [i..j) (ij)'] = function () { 196 | var joinExecuted = false; 197 | Lazy.range('[10..1)').join(function (xs) { 198 | joinExecuted = true; 199 | assert.deepEqual(xs, range(10,1)); 200 | assert.equal(xs.length, 9); 201 | }); 202 | 203 | setTimeout(function () { 204 | assert.ok(joinExecuted, 'join didn\'t execute'); 205 | }, 2000); 206 | } 207 | 208 | exports['range (i..j] (ij)'] = function () { 222 | var joinExecuted = false; 223 | Lazy.range('(10..1]').join(function (xs) { 224 | joinExecuted = true; 225 | assert.deepEqual(xs, range(9,0)); 226 | assert.equal(xs.length, 9); 227 | }); 228 | 229 | setTimeout(function () { 230 | assert.ok(joinExecuted, 'join didn\'t execute'); 231 | }, 2000); 232 | } 233 | 234 | exports['range (i..j) (ij)'] = function () { 248 | var joinExecuted = false; 249 | Lazy.range('(10..1)').join(function (xs) { 250 | joinExecuted = true; 251 | assert.deepEqual(xs, range(9,1)); 252 | assert.equal(xs.length, 8); 253 | }); 254 | 255 | setTimeout(function () { 256 | assert.ok(joinExecuted, 'join didn\'t execute'); 257 | }, 2000); 258 | } 259 | 260 | exports['range [i,step..j]'] = function () { 261 | var joinExecuted = false; 262 | Lazy.range('[5,10..50]').join(function (xs) { 263 | joinExecuted = true; 264 | assert.deepEqual(xs, range(5,51,5)); 265 | assert.equal(xs.length, 10); 266 | }); 267 | 268 | setTimeout(function () { 269 | assert.ok(joinExecuted, 'join didn\'t execute'); 270 | }, 2000); 271 | } 272 | 273 | -------------------------------------------------------------------------------- /test/skip.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Lazy = require('..'); 3 | var expresso = expresso; 4 | 5 | function range(i, j) { 6 | var r = []; 7 | for (;i