PageRenderTime 30ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/test/comprehensions.coffee

http://github.com/jashkenas/coffee-script
CoffeeScript | 620 lines | 515 code | 92 blank | 13 comment | 55 complexity | 2272f85e3db37b48190e990de8168ec4 MD5 | raw file
  1. # Comprehensions
  2. # --------------
  3. # * Array Comprehensions
  4. # * Range Comprehensions
  5. # * Object Comprehensions
  6. # * Implicit Destructuring Assignment
  7. # * Comprehensions with Nonstandard Step
  8. # TODO: refactor comprehension tests
  9. test "Basic array comprehensions.", ->
  10. nums = (n * n for n in [1, 2, 3] when n & 1)
  11. results = (n * 2 for n in nums)
  12. ok results.join(',') is '2,18'
  13. test "Basic object comprehensions.", ->
  14. obj = {one: 1, two: 2, three: 3}
  15. names = (prop + '!' for prop of obj)
  16. odds = (prop + '!' for prop, value of obj when value & 1)
  17. ok names.join(' ') is "one! two! three!"
  18. ok odds.join(' ') is "one! three!"
  19. test "Basic range comprehensions.", ->
  20. nums = (i * 3 for i in [1..3])
  21. negs = (x for x in [-20..-5*2])
  22. negs = negs[0..2]
  23. result = nums.concat(negs).join(', ')
  24. ok result is '3, 6, 9, -20, -19, -18'
  25. test "With range comprehensions, you can loop in steps.", ->
  26. results = (x for x in [0...15] by 5)
  27. ok results.join(' ') is '0 5 10'
  28. results = (x for x in [0..100] by 10)
  29. ok results.join(' ') is '0 10 20 30 40 50 60 70 80 90 100'
  30. test "And can loop downwards, with a negative step.", ->
  31. results = (x for x in [5..1])
  32. ok results.join(' ') is '5 4 3 2 1'
  33. ok results.join(' ') is [(10-5)..(-2+3)].join(' ')
  34. results = (x for x in [10..1])
  35. ok results.join(' ') is [10..1].join(' ')
  36. results = (x for x in [10...0] by -2)
  37. ok results.join(' ') is [10, 8, 6, 4, 2].join(' ')
  38. test "Range comprehension gymnastics.", ->
  39. eq "#{i for i in [5..1]}", '5,4,3,2,1'
  40. eq "#{i for i in [5..-5] by -5}", '5,0,-5'
  41. a = 6
  42. b = 0
  43. c = -2
  44. eq "#{i for i in [a..b]}", '6,5,4,3,2,1,0'
  45. eq "#{i for i in [a..b] by c}", '6,4,2,0'
  46. test "Multiline array comprehension with filter.", ->
  47. evens = for num in [1, 2, 3, 4, 5, 6] when not (num & 1)
  48. num *= -1
  49. num -= 2
  50. num * -1
  51. eq evens + '', '4,6,8'
  52. test "The in operator still works, standalone.", ->
  53. ok 2 of evens
  54. test "all isn't reserved.", ->
  55. all = 1
  56. test "Ensure that the closure wrapper preserves local variables.", ->
  57. obj = {}
  58. for method in ['one', 'two', 'three'] then do (method) ->
  59. obj[method] = ->
  60. "I'm " + method
  61. ok obj.one() is "I'm one"
  62. ok obj.two() is "I'm two"
  63. ok obj.three() is "I'm three"
  64. test "Index values at the end of a loop.", ->
  65. i = 0
  66. for i in [1..3]
  67. -> 'func'
  68. break if false
  69. ok i is 4
  70. test "Ensure that local variables are closed over for range comprehensions.", ->
  71. funcs = for i in [1..3]
  72. do (i) ->
  73. -> -i
  74. eq (func() for func in funcs).join(' '), '-1 -2 -3'
  75. ok i is 4
  76. test "Even when referenced in the filter.", ->
  77. list = ['one', 'two', 'three']
  78. methods = for num, i in list when num isnt 'two' and i isnt 1
  79. do (num, i) ->
  80. -> num + ' ' + i
  81. ok methods.length is 2
  82. ok methods[0]() is 'one 0'
  83. ok methods[1]() is 'three 2'
  84. test "Even a convoluted one.", ->
  85. funcs = []
  86. for i in [1..3]
  87. do (i) ->
  88. x = i * 2
  89. ((z)->
  90. funcs.push -> z + ' ' + i
  91. )(x)
  92. ok (func() for func in funcs).join(', ') is '2 1, 4 2, 6 3'
  93. funcs = []
  94. results = for i in [1..3]
  95. do (i) ->
  96. z = (x * 3 for x in [1..i])
  97. ((a, b, c) -> [a, b, c].join(' ')).apply this, z
  98. ok results.join(', ') is '3 , 3 6 , 3 6 9'
  99. test "Naked ranges are expanded into arrays.", ->
  100. array = [0..10]
  101. ok(num % 2 is 0 for num in array by 2)
  102. test "Nested shared scopes.", ->
  103. foo = ->
  104. for i in [0..7]
  105. do (i) ->
  106. for j in [0..7]
  107. do (j) ->
  108. -> i + j
  109. eq foo()[3][4](), 7
  110. test "Scoped loop pattern matching.", ->
  111. a = [[0], [1]]
  112. funcs = []
  113. for [v] in a
  114. do (v) ->
  115. funcs.push -> v
  116. eq funcs[0](), 0
  117. eq funcs[1](), 1
  118. test "Nested comprehensions.", ->
  119. multiLiner =
  120. for x in [3..5]
  121. for y in [3..5]
  122. [x, y]
  123. singleLiner =
  124. (([x, y] for y in [3..5]) for x in [3..5])
  125. ok multiLiner.length is singleLiner.length
  126. ok 5 is multiLiner[2][2][1]
  127. ok 5 is singleLiner[2][2][1]
  128. test "Comprehensions within parentheses.", ->
  129. result = null
  130. store = (obj) -> result = obj
  131. store (x * 2 for x in [3, 2, 1])
  132. ok result.join(' ') is '6 4 2'
  133. test "Closure-wrapped comprehensions that refer to the 'arguments' object.", ->
  134. expr = ->
  135. result = (item * item for item in arguments)
  136. ok expr(2, 4, 8).join(' ') is '4 16 64'
  137. test "Fast object comprehensions over all properties, including prototypal ones.", ->
  138. class Cat
  139. constructor: -> @name = 'Whiskers'
  140. breed: 'tabby'
  141. hair: 'cream'
  142. whiskers = new Cat
  143. own = (value for own key, value of whiskers)
  144. all = (value for key, value of whiskers)
  145. ok own.join(' ') is 'Whiskers'
  146. ok all.sort().join(' ') is 'Whiskers cream tabby'
  147. test "Optimized range comprehensions.", ->
  148. exxes = ('x' for [0...10])
  149. ok exxes.join(' ') is 'x x x x x x x x x x'
  150. test "#3671: Allow step in optimized range comprehensions.", ->
  151. exxes = ('x' for [0...10] by 2)
  152. eq exxes.join(' ') , 'x x x x x'
  153. test "#3671: Disallow guard in optimized range comprehensions.", ->
  154. throwsCompileError "exxes = ('x' for [0...10] when a)"
  155. test "Loop variables should be able to reference outer variables", ->
  156. outer = 1
  157. do ->
  158. null for outer in [1, 2, 3]
  159. eq outer, 3
  160. test "Lenient on pure statements not trying to reach out of the closure", ->
  161. val = for i in [1]
  162. for j in [] then break
  163. i
  164. ok val[0] is i
  165. test "Comprehensions only wrap their last line in a closure, allowing other lines
  166. to have pure expressions in them.", ->
  167. func = -> for i in [1]
  168. break if i is 2
  169. j for j in [1]
  170. ok func()[0][0] is 1
  171. i = 6
  172. odds = while i--
  173. continue unless i & 1
  174. i
  175. ok odds.join(', ') is '5, 3, 1'
  176. test "Issue #897: Ensure that plucked function variables aren't leaked.", ->
  177. facets = {}
  178. list = ['one', 'two']
  179. (->
  180. for entity in list
  181. facets[entity] = -> entity
  182. )()
  183. eq typeof entity, 'undefined'
  184. eq facets['two'](), 'two'
  185. test "Issue #905. Soaks as the for loop subject.", ->
  186. a = {b: {c: [1, 2, 3]}}
  187. for d in a.b?.c
  188. e = d
  189. eq e, 3
  190. test "Issue #948. Capturing loop variables.", ->
  191. funcs = []
  192. list = ->
  193. [1, 2, 3]
  194. for y in list()
  195. do (y) ->
  196. z = y
  197. funcs.push -> "y is #{y} and z is #{z}"
  198. eq funcs[1](), "y is 2 and z is 2"
  199. test "Cancel the comprehension if there's a jump inside the loop.", ->
  200. result = try
  201. for i in [0...10]
  202. continue if i < 5
  203. i
  204. eq result, 10
  205. test "Comprehensions over break.", ->
  206. arrayEq (break for [1..10]), []
  207. test "Comprehensions over continue.", ->
  208. arrayEq (continue for [1..10]), []
  209. test "Comprehensions over function literals.", ->
  210. a = 0
  211. for f in [-> a = 1]
  212. do (f) ->
  213. do f
  214. eq a, 1
  215. test "Comprehensions that mention arguments.", ->
  216. list = [arguments: 10]
  217. args = for f in list
  218. do (f) ->
  219. f.arguments
  220. eq args[0], 10
  221. test "expression conversion under explicit returns", ->
  222. nonce = {}
  223. fn = ->
  224. return (nonce for x in [1,2,3])
  225. arrayEq [nonce,nonce,nonce], fn()
  226. fn = ->
  227. return [nonce for x in [1,2,3]][0]
  228. arrayEq [nonce,nonce,nonce], fn()
  229. fn = ->
  230. return [(nonce for x in [1..3])][0]
  231. arrayEq [nonce,nonce,nonce], fn()
  232. test "implicit destructuring assignment in object of objects", ->
  233. a={}; b={}; c={}
  234. obj = {
  235. a: { d: a },
  236. b: { d: b }
  237. c: { d: c }
  238. }
  239. result = ([y,z] for y, { d: z } of obj)
  240. arrayEq [['a',a],['b',b],['c',c]], result
  241. test "implicit destructuring assignment in array of objects", ->
  242. a={}; b={}; c={}; d={}; e={}; f={}
  243. arr = [
  244. { a: a, b: { c: b } },
  245. { a: c, b: { c: d } },
  246. { a: e, b: { c: f } }
  247. ]
  248. result = ([y,z] for { a: y, b: { c: z } } in arr)
  249. arrayEq [[a,b],[c,d],[e,f]], result
  250. test "implicit destructuring assignment in array of arrays", ->
  251. a={}; b={}; c={}; d={}; e={}; f={}
  252. arr = [[a, [b]], [c, [d]], [e, [f]]]
  253. result = ([y,z] for [y, [z]] in arr)
  254. arrayEq [[a,b],[c,d],[e,f]], result
  255. test "issue #1124: don't assign a variable in two scopes", ->
  256. lista = [1, 2, 3, 4, 5]
  257. listb = (_i + 1 for _i in lista)
  258. arrayEq [2, 3, 4, 5, 6], listb
  259. test "#1326: `by` value is uncached", ->
  260. a = [0,1,2]
  261. fi = gi = hi = 0
  262. f = -> ++fi
  263. g = -> ++gi
  264. h = -> ++hi
  265. forCompile = []
  266. rangeCompileSimple = []
  267. #exercises For.compile
  268. for v, i in a by f()
  269. forCompile.push i
  270. #exercises Range.compileSimple
  271. rangeCompileSimple = (i for i in [0..2] by g())
  272. arrayEq a, forCompile
  273. arrayEq a, rangeCompileSimple
  274. #exercises Range.compile
  275. eq "#{i for i in [0..2] by h()}", '0,1,2'
  276. test "#1669: break/continue should skip the result only for that branch", ->
  277. ns = for n in [0..99]
  278. if n > 9
  279. break
  280. else if n & 1
  281. continue
  282. else
  283. n
  284. eq "#{ns}", '0,2,4,6,8'
  285. # `else undefined` is implied.
  286. ns = for n in [1..9]
  287. if n % 2
  288. continue unless n % 5
  289. n
  290. eq "#{ns}", "1,,3,,,7,,9"
  291. # Ditto.
  292. ns = for n in [1..9]
  293. switch
  294. when n % 2
  295. continue unless n % 5
  296. n
  297. eq "#{ns}", "1,,3,,,7,,9"
  298. test "#1850: inner `for` should not be expression-ized if `return`ing", ->
  299. eq '3,4,5', do ->
  300. for a in [1..9] then \
  301. for b in [1..9]
  302. c = Math.sqrt a*a + b*b
  303. return String [a, b, c] unless c % 1
  304. test "#1910: loop index should be mutable within a loop iteration and immutable between loop iterations", ->
  305. n = 1
  306. iterations = 0
  307. arr = [0..n]
  308. for v, k in arr
  309. ++iterations
  310. v = k = 5
  311. eq 5, k
  312. eq 2, k
  313. eq 2, iterations
  314. iterations = 0
  315. for v in [0..n]
  316. ++iterations
  317. eq 2, k
  318. eq 2, iterations
  319. arr = ([v, v + 1] for v in [0..5])
  320. iterations = 0
  321. for [v0, v1], k in arr when v0
  322. k += 3
  323. ++iterations
  324. eq 6, k
  325. eq 5, iterations
  326. test "#2007: Return object literal from comprehension", ->
  327. y = for x in [1, 2]
  328. foo: "foo" + x
  329. eq 2, y.length
  330. eq "foo1", y[0].foo
  331. eq "foo2", y[1].foo
  332. x = 2
  333. y = while x
  334. x: --x
  335. eq 2, y.length
  336. eq 1, y[0].x
  337. eq 0, y[1].x
  338. test "#2274: Allow @values as loop variables", ->
  339. obj = {
  340. item: null
  341. method: ->
  342. for @item in [1, 2, 3]
  343. null
  344. }
  345. eq obj.item, null
  346. obj.method()
  347. eq obj.item, 3
  348. test "#4411: Allow @values as loop indices", ->
  349. obj =
  350. index: null
  351. get: -> @index
  352. method: ->
  353. @get() for _, @index in [1, 2, 3]
  354. eq obj.index, null
  355. arrayEq obj.method(), [0, 1, 2]
  356. eq obj.index, 3
  357. test "#2525, #1187, #1208, #1758, looping over an array forwards", ->
  358. list = [0, 1, 2, 3, 4]
  359. ident = (x) -> x
  360. arrayEq (i for i in list), list
  361. arrayEq (index for i, index in list), list
  362. arrayEq (i for i in list by 1), list
  363. arrayEq (i for i in list by ident 1), list
  364. arrayEq (i for i in list by ident(1) * 2), [0, 2, 4]
  365. arrayEq (index for i, index in list by ident(1) * 2), [0, 2, 4]
  366. test "#2525, #1187, #1208, #1758, looping over an array backwards", ->
  367. list = [0, 1, 2, 3, 4]
  368. backwards = [4, 3, 2, 1, 0]
  369. ident = (x) -> x
  370. arrayEq (i for i in list by -1), backwards
  371. arrayEq (index for i, index in list by -1), backwards
  372. arrayEq (i for i in list by ident -1), backwards
  373. arrayEq (i for i in list by ident(-1) * 2), [4, 2, 0]
  374. arrayEq (index for i, index in list by ident(-1) * 2), [4, 2, 0]
  375. test "splats in destructuring in comprehensions", ->
  376. list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
  377. arrayEq (seq for [rep, seq...] in list), [[1, 2], [3, 4], [5, 6]]
  378. test "#156: expansion in destructuring in comprehensions", ->
  379. list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
  380. arrayEq (last for [..., last] in list), [2, 4, 6]
  381. test "#3778: Consistently always cache for loop range boundaries and steps, even
  382. if they are simple identifiers", ->
  383. a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by a then a = 4; n)
  384. a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by +a then a = 4; n)
  385. a = 1; arrayEq [1, 2, 3], (for n in [a..3] then a = 4; n)
  386. a = 1; arrayEq [1, 2, 3], (for n in [+a..3] then a = 4; n)
  387. a = 3; arrayEq [1, 2, 3], (for n in [1..a] then a = 4; n)
  388. a = 3; arrayEq [1, 2, 3], (for n in [1..+a] then a = 4; n)
  389. a = 1; arrayEq [1, 2, 3], (for n in [1..3] by a then a = 4; n)
  390. a = 1; arrayEq [1, 2, 3], (for n in [1..3] by +a then a = 4; n)
  391. test "for pattern variables can linebreak/indent", ->
  392. listOfObjects = [a: 1]
  393. sum = 0
  394. for {
  395. a
  396. somethingElse
  397. anotherProperty
  398. } in listOfObjects
  399. sum += a
  400. eq a, 1
  401. sum = 0
  402. sum += a for {
  403. a,
  404. somethingElse,
  405. anotherProperty,
  406. } in listOfObjects
  407. eq a, 1
  408. listOfArrays = [[2]]
  409. sum = 0
  410. for [
  411. a
  412. nonexistentElement
  413. anotherNonexistentElement
  414. ] in listOfArrays
  415. sum += a
  416. eq a, 2
  417. sum = 0
  418. sum += a for [
  419. a,
  420. nonexistentElement,
  421. anotherNonexistentElement
  422. ] in listOfArrays
  423. eq a, 2
  424. test "#5309: comprehension as postfix condition", ->
  425. doesNotThrowCompileError """
  426. throw new Error "DOOM was called with a null element" unless elm? for elm in elms
  427. """