Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3d88dc406 | ||
|
|
58a6291f17 | ||
|
|
81884247d7 | ||
|
|
c07a34f9a8 | ||
|
|
e05bfc78cb | ||
|
|
cb430702c9 | ||
|
|
6aaa4bb06d | ||
|
|
a6b80a19c5 | ||
|
|
8be0ad0499 | ||
|
|
8da4936ac3 | ||
|
|
bcc92ccc40 | ||
|
|
d64d4b009c | ||
|
|
b1344724b6 | ||
|
|
4a7e541130 | ||
|
|
403c85cebe | ||
|
|
f81489a19e | ||
|
|
65ec108399 | ||
|
|
0f6c9d1594 | ||
|
|
0751f4b873 | ||
|
|
2cee5950e7 | ||
|
|
08a19d0768 | ||
|
|
d5e6296a72 | ||
|
|
c5cb550bfd | ||
|
|
b7813259c2 | ||
|
|
e0fd6dee99 | ||
|
|
7354974ef2 | ||
|
|
e1d5263560 | ||
|
|
4855d08c44 | ||
|
|
1ae3e344f8 | ||
|
|
94c9f39f3d | ||
|
|
4e190e5358 | ||
|
|
14610cb09b | ||
|
|
1703066f60 | ||
|
|
5968df9649 | ||
|
|
a05a1a4692 | ||
|
|
278374202b | ||
|
|
4f2f92c8f1 | ||
|
|
08f1c2b629 | ||
|
|
8310c9b9ac | ||
|
|
f7a1d273bc | ||
|
|
f51b1116e9 | ||
|
|
ac3bbad648 | ||
|
|
88ffb468b5 | ||
|
|
62a36ed8db | ||
|
|
be0e9837e2 | ||
|
|
6e528cd7c9 | ||
|
|
c4d5ee5e68 | ||
|
|
e4685dc81c | ||
|
|
e471e063f8 | ||
|
|
1330de1eda | ||
|
|
076c8dccfd | ||
|
|
359a17a761 | ||
|
|
924222188a | ||
|
|
747d47c792 | ||
|
|
211a1299cc | ||
|
|
74d73acaae | ||
|
|
4c67b83ceb | ||
|
|
57af576192 | ||
|
|
2cf5bbe0fe | ||
|
|
913fa65faa | ||
|
|
5d5d7dadb4 | ||
|
|
f1d4d5b034 | ||
|
|
e6a31aff0b | ||
|
|
e87fc8ff67 | ||
|
|
0ee77de4f2 | ||
|
|
5613c2edd2 | ||
|
|
6cba4f33a9 | ||
|
|
e9aa70a0c2 | ||
|
|
75378611c6 | ||
|
|
e6a8bbdd84 | ||
|
|
91d6856b60 | ||
|
|
80e6942ca9 | ||
|
|
25485ac20d | ||
|
|
519ccc8290 | ||
|
|
778f9b6295 | ||
|
|
737bf1b369 | ||
|
|
12e052ee55 | ||
|
|
ee56234edf | ||
|
|
fca45d8713 | ||
|
|
a4ffd55431 | ||
|
|
16eac65502 | ||
|
|
7113ccf0bc | ||
|
|
6660e7e409 | ||
|
|
860db84865 | ||
|
|
77e142d124 | ||
|
|
6f80bf1bff | ||
|
|
5e15f2eb88 | ||
|
|
f4403742eb | ||
|
|
66964cbd9b | ||
|
|
d9356c3dc5 | ||
|
|
12e3836448 | ||
|
|
c9d4eedbbc | ||
|
|
a2860b32a3 | ||
|
|
7a1758e6c0 | ||
|
|
afe22a2920 | ||
|
|
adaceb36eb | ||
|
|
966c8a27ae | ||
|
|
037340c2c8 | ||
|
|
62ba9a9e4c | ||
|
|
2266d0ec3f | ||
|
|
3e13166ee0 | ||
|
|
5449e7f5aa | ||
|
|
932d303733 | ||
|
|
70cfb853d4 | ||
|
|
677e9ae225 | ||
|
|
3ee44d4a5c | ||
|
|
9737481dc2 | ||
|
|
29340f4d06 | ||
|
|
3520bb24e5 | ||
|
|
45619f1a6c | ||
|
|
de0e9bf8e0 | ||
|
|
0131d0281c | ||
|
|
b39f2f69f7 | ||
|
|
6a5a9f8d47 | ||
|
|
fb9d3c3106 | ||
|
|
b58f7db73a | ||
|
|
e1bd1cca65 | ||
|
|
c932a7fac2 | ||
|
|
857ea3ca19 | ||
|
|
9ac3c2e00d | ||
|
|
71c42e8ebb | ||
|
|
a8e66fd3ae | ||
|
|
a1b3dc8b0e | ||
|
|
20333ebbc8 | ||
|
|
fa991c1940 | ||
|
|
bf019b8a84 | ||
|
|
450b8b37de | ||
|
|
a303f38f71 | ||
|
|
1e40dae6e5 | ||
|
|
69ba432b9c | ||
|
|
0f608c6a48 | ||
|
|
d094160ae9 | ||
|
|
ab7566afb6 | ||
|
|
f23e0856b1 | ||
|
|
ecd86761da | ||
|
|
b18e41020b | ||
|
|
6ed496a119 | ||
|
|
7422c1a750 | ||
|
|
01b4f2fdab | ||
|
|
df15733fc0 | ||
|
|
7a9a69f08b | ||
|
|
ddf0262a3d | ||
|
|
aa297109bf | ||
|
|
aece570083 | ||
|
|
c78755a0fb | ||
|
|
c37ea4e874 | ||
|
|
609e042d61 | ||
|
|
3491434bea | ||
|
|
2d582087c6 | ||
|
|
5a755948fe | ||
|
|
2c5c53834f | ||
|
|
253ede29da | ||
|
|
3715521c10 | ||
|
|
4a80285540 | ||
|
|
d6f0e64164 | ||
|
|
9b9b324241 | ||
|
|
ae12b80e71 | ||
|
|
2891f5734c | ||
|
|
5c1ae1cd1c | ||
|
|
a60d79a0e3 | ||
|
|
e10e000eed | ||
|
|
6d87f137e5 | ||
|
|
5d76724a1f | ||
|
|
ef9c88f8a0 | ||
|
|
14282b03df | ||
|
|
f62c76497d | ||
|
|
5ffb2c570d | ||
|
|
d314230faa | ||
|
|
75b78ba5c6 | ||
|
|
2093e823cd | ||
|
|
944990381f | ||
|
|
05e426248c | ||
|
|
225620f71f | ||
|
|
26dfd5ebb0 | ||
|
|
f35084b7f0 | ||
|
|
5461693dc5 | ||
|
|
06d8e4fec9 | ||
|
|
d2e864d366 | ||
|
|
2cc62d8c38 | ||
|
|
7c1b6a657c | ||
|
|
17f55ac5e1 | ||
|
|
805ee01b5f | ||
|
|
5a7c7d272b | ||
|
|
40096e0ff1 | ||
|
|
644c29aca8 | ||
|
|
d9a0a7f319 | ||
|
|
edb4e4b85b | ||
|
|
64f8c21ba6 | ||
|
|
346e0d1df7 | ||
|
|
5836a70a78 | ||
|
|
1d3781c00f | ||
|
|
15b239748e | ||
|
|
319939338b | ||
|
|
eef1fa3938 | ||
|
|
01898c62e3 | ||
|
|
ef5ba3af05 | ||
|
|
5ebe251f9c | ||
|
|
aa6cfb772d | ||
|
|
7963398aaf | ||
|
|
00042b8578 | ||
|
|
5d8bbbe8d9 | ||
|
|
39c33b6105 | ||
|
|
7acb551132 | ||
|
|
65d4a26a94 | ||
|
|
5ff37dc1b9 | ||
|
|
b9037980bb | ||
|
|
3ba93e780f | ||
|
|
715bdccf2c | ||
|
|
dc741ea4fa | ||
|
|
26fe2fe71d | ||
|
|
5e9a8a90cd | ||
|
|
5b6f257826 | ||
|
|
104d677f4a | ||
|
|
a48686c303 | ||
|
|
d0d4361def | ||
|
|
3605c3c178 | ||
|
|
c06fde60ce | ||
|
|
1dd5c03998 | ||
|
|
9245522fd1 | ||
|
|
c1b2a9048d | ||
|
|
30308917fc | ||
|
|
1333bf31ad | ||
|
|
f656633f0d | ||
|
|
99e0d8ea80 | ||
|
|
7231c45422 | ||
|
|
5b903871a3 | ||
|
|
dda58028e8 | ||
|
|
b4d5c17d0a | ||
|
|
b6ac08ace0 | ||
|
|
73351fecfd | ||
|
|
87396c6189 | ||
|
|
bc2c7ffd72 | ||
|
|
445d5ee074 | ||
|
|
8dba22d8af | ||
|
|
f8a82f604a | ||
|
|
499983e2c5 | ||
|
|
b59de832fd | ||
|
|
aee2d9800f | ||
|
|
aa01677c9f | ||
|
|
8703e9655e | ||
|
|
f7edee834f | ||
|
|
9ddb3eda00 | ||
|
|
c3cc02b8b4 | ||
|
|
36ff2fa703 | ||
|
|
75a77be9a0 | ||
|
|
1b89b6f69f | ||
|
|
f7be7351bb | ||
|
|
940d8a5ec8 | ||
|
|
0ba7dfcd31 | ||
|
|
2246d51a89 | ||
|
|
84f3a7320a | ||
|
|
966838f1c5 | ||
|
|
a394a85aff | ||
|
|
68b4259d75 | ||
|
|
6a1ca846df | ||
|
|
9db6348add | ||
|
|
3a559432d4 | ||
|
|
6bf20163b4 | ||
|
|
6eb746a10c | ||
|
|
60c7d532b0 | ||
|
|
c7d65c3c6f | ||
|
|
79907972e6 | ||
|
|
e6abf5ffb1 | ||
|
|
778f639fec | ||
|
|
fdaefb0d03 | ||
|
|
517b0f1138 | ||
|
|
94149b1e24 | ||
|
|
6b4a65e7ff | ||
|
|
d9ac304b97 | ||
|
|
936b243517 | ||
|
|
e61921eebe | ||
|
|
0c2f3a4e5c | ||
|
|
44bcf394e4 | ||
|
|
d1cf766a00 | ||
|
|
7ab488479b | ||
|
|
0ce5b62582 | ||
|
|
4fa892208e | ||
|
|
18dd2b140a | ||
|
|
7b5f4a5842 | ||
|
|
f79056e168 | ||
|
|
ec84afbb22 | ||
|
|
91d5ba5725 | ||
|
|
8d6a20f054 | ||
|
|
3fbf764e05 | ||
|
|
dac0f2fe27 | ||
|
|
424998828b | ||
|
|
8391b23b8e | ||
|
|
024e8eb71a | ||
|
|
ff04de1592 | ||
|
|
58fcce1bfc | ||
|
|
a70a5fb442 | ||
|
|
92fecf106b | ||
|
|
c855561e96 | ||
|
|
ba79d29aa5 | ||
|
|
93153d4c4f | ||
|
|
56274111b7 | ||
|
|
c9105569a0 | ||
|
|
3d960c5b65 | ||
|
|
ce419c1e66 | ||
|
|
600899d48a | ||
|
|
b2bf633ab5 | ||
|
|
599e7bb89c | ||
|
|
3fa9a642d5 | ||
|
|
a14b2afe4b | ||
|
|
a34d81c253 | ||
|
|
269d897a1b | ||
|
|
76178a6293 | ||
|
|
3487d00ddb | ||
|
|
05e4e26a87 | ||
|
|
d8699068a8 | ||
|
|
52253aa234 | ||
|
|
8180dfe904 | ||
|
|
7adec1548d | ||
|
|
482f33e6ea | ||
|
|
1891a3244f | ||
|
|
ad647a8b20 | ||
|
|
bf4c7cb211 | ||
|
|
cb440f650e | ||
|
|
15a613dd08 | ||
|
|
c803afe90c | ||
|
|
1587efc787 | ||
|
|
2b5dc7f79b | ||
|
|
8428ae1a99 | ||
|
|
412f7cea00 | ||
|
|
16f4975755 | ||
|
|
f68db17d58 | ||
|
|
e4db37c1b0 | ||
|
|
5202b13b9b | ||
|
|
dc17f37292 | ||
|
|
ba7a92e18a | ||
|
|
b485d80bec | ||
|
|
511a175495 | ||
|
|
a1325e1caf | ||
|
|
8951ef5d0d | ||
|
|
7ba0d4d97d | ||
|
|
8488e29346 | ||
|
|
fd81b308fe | ||
|
|
361d2f065d | ||
|
|
cdb163e634 | ||
|
|
3810185cc4 | ||
|
|
760e5015c8 | ||
|
|
a47b730f3c | ||
|
|
b75d47189b | ||
|
|
662c5fb3cb | ||
|
|
038ee4fde2 | ||
|
|
43b489649f | ||
|
|
d9232bd40c | ||
|
|
806c2da15c | ||
|
|
8fd3eb5a2c | ||
|
|
4a0b64886d | ||
|
|
ab80ec5606 | ||
|
|
c721afa04e | ||
|
|
38371fa167 | ||
|
|
e8d1f7a6ec | ||
|
|
d54436a456 | ||
|
|
6c8855f1a7 | ||
|
|
2c25042306 | ||
|
|
7bb02bbbbb | ||
|
|
aebd7e5143 | ||
|
|
977226d8be | ||
|
|
7b1a7b29ce | ||
|
|
39f1bdb7ff | ||
|
|
62641dfa03 | ||
|
|
37e4f20bd6 | ||
|
|
62ca86965f | ||
|
|
24a7e6c690 | ||
|
|
01c8e1cd72 | ||
|
|
ecff4ad729 | ||
|
|
28a279d88d | ||
|
|
a56ba1e377 | ||
|
|
b6dcbdb69a | ||
|
|
62f0f2a528 | ||
|
|
8096300c87 | ||
|
|
19c40ec584 | ||
|
|
911b9de753 | ||
|
|
49e7b8a8b8 | ||
|
|
82d3da0774 | ||
|
|
d757d6f821 | ||
|
|
0c4fcb807a | ||
|
|
ef3c45f994 | ||
|
|
94a07df8d3 | ||
|
|
a0bb2c3563 | ||
|
|
a2d11ab784 | ||
|
|
949fc7a885 | ||
|
|
63c1f07360 | ||
|
|
c33e41295e | ||
|
|
9672ae363d | ||
|
|
20868b9838 | ||
|
|
63295ef204 | ||
|
|
2b7146c802 | ||
|
|
65b39eb582 | ||
|
|
836f6fcfc2 | ||
|
|
4c42568dde | ||
|
|
bd3332c848 | ||
|
|
95082f1d4f | ||
|
|
3b55fda690 | ||
|
|
fc3f273401 | ||
|
|
7c0f21a9c4 | ||
|
|
e87896ec11 | ||
|
|
8aa8fdf93d | ||
|
|
f821e29525 | ||
|
|
ff4da655d9 | ||
|
|
ccb78d65e7 | ||
|
|
8060b793b2 | ||
|
|
1872064062 | ||
|
|
1aa0912493 | ||
|
|
7a2edcbaee | ||
|
|
9697074346 | ||
|
|
1818287700 | ||
|
|
3ef6d24a7e | ||
|
|
6a8b212376 | ||
|
|
95b03739dd | ||
|
|
35fbf0716a | ||
|
|
6128444f4b | ||
|
|
a90bf7fc50 | ||
|
|
89d2b916c1 | ||
|
|
0f1b5e5e23 | ||
|
|
d416772ace | ||
|
|
6de232cab0 | ||
|
|
5b88ef22d2 | ||
|
|
2f7c2da482 | ||
|
|
d9a8a2a93d | ||
|
|
1f7ee6788c | ||
|
|
07d375d6c2 | ||
|
|
4b01bccfbf | ||
|
|
ecf90268eb | ||
|
|
266fb8459d | ||
|
|
1a00eb67e7 | ||
|
|
295a366f72 | ||
|
|
5ec2a43650 | ||
|
|
c87eafab0c | ||
|
|
a52e8538f8 | ||
|
|
7602bda91e | ||
|
|
4ac8d8a87e | ||
|
|
8c79b21112 | ||
|
|
7c7b88d888 | ||
|
|
6de06887cb | ||
|
|
754fdf87c3 | ||
|
|
1224e93ab8 | ||
|
|
b131b17f78 | ||
|
|
31187012fa | ||
|
|
1359c909b5 | ||
|
|
507faf5a7e | ||
|
|
2547506865 | ||
|
|
b3595595a7 | ||
|
|
8d216f8162 | ||
|
|
613760104e | ||
|
|
7e5ea0da6d | ||
|
|
e273f6930e | ||
|
|
e12ca353a9 | ||
|
|
7b5905a2dd | ||
|
|
66582563e7 | ||
|
|
022c007b2c | ||
|
|
be940abece | ||
|
|
bb29ced8b3 | ||
|
|
2de804eb86 | ||
|
|
68ab38736b | ||
|
|
80c94907dd | ||
|
|
9fcaed147c | ||
|
|
d9a93c47ca | ||
|
|
53c85df69a | ||
|
|
e301923d68 | ||
|
|
b54a8a0cbb | ||
|
|
f7069342c0 | ||
|
|
247761b0c7 | ||
|
|
d1b785821c | ||
|
|
d5a0db2940 | ||
|
|
c4fe36275b | ||
|
|
b23ec517d6 | ||
|
|
29cad499f7 | ||
|
|
71e5acc0b7 | ||
|
|
2aad447cbf | ||
|
|
8ac8a4c2e8 | ||
|
|
a637caffc8 | ||
|
|
a7f89b091f | ||
|
|
3ccd5137ef | ||
|
|
94de6615d7 | ||
|
|
c859e49199 | ||
|
|
fd72cb37cc | ||
|
|
accf687a0d | ||
|
|
c983b3db69 | ||
|
|
f29e29b3a4 | ||
|
|
8298041cbc | ||
|
|
2c1a39eebb | ||
|
|
245094f665 | ||
|
|
3f9602bf82 | ||
|
|
b2d459aa29 | ||
|
|
17b3df271d | ||
|
|
49e41ec098 | ||
|
|
9c86f5b167 | ||
|
|
56fbcfc79f | ||
|
|
fb8ac4d85c | ||
|
|
578800734f | ||
|
|
3f1bc7c9bb | ||
|
|
2e1ce93210 | ||
|
|
572eddd9fe | ||
|
|
14ac3c5e97 | ||
|
|
4bbd3b0bbb | ||
|
|
a06a873392 | ||
|
|
70e0b24536 | ||
|
|
cb78e0b334 | ||
|
|
1932b5243e | ||
|
|
9bcd8439e7 | ||
|
|
40fff43ea0 | ||
|
|
7e1073ec49 | ||
|
|
f6b2ddd81b | ||
|
|
c37a90ea4a | ||
|
|
19a8e766a7 | ||
|
|
e2b13ddba4 | ||
|
|
3eeb903534 | ||
|
|
d682c47bf9 | ||
|
|
340717b979 | ||
|
|
07fedf66d0 | ||
|
|
fa865ce5c1 | ||
|
|
ddfcc60505 | ||
|
|
b933d84b05 | ||
|
|
6239bf32db | ||
|
|
3915e6c127 | ||
|
|
02b2bd2fc0 | ||
|
|
40c70ffcfa | ||
|
|
471de4b8a4 | ||
|
|
f2cd34f1a8 | ||
|
|
ca5a9060ef | ||
|
|
f8e2332db9 | ||
|
|
4061a9573d | ||
|
|
0cc63df80d | ||
|
|
ed66646187 | ||
|
|
3f7f074494 | ||
|
|
f70192bb6e | ||
|
|
dabcb4c71f | ||
|
|
c7637bc362 | ||
|
|
0d3d410beb | ||
|
|
50e37d75e0 | ||
|
|
c8172c8341 | ||
|
|
c0c6983363 | ||
|
|
f7654bf66a | ||
|
|
5f36e0b33e | ||
|
|
4d5a6bed63 | ||
|
|
efa505b38a | ||
|
|
b138c6f472 | ||
|
|
b268291c8c | ||
|
|
d5fe8eb068 | ||
|
|
30281d149a | ||
|
|
81503c0a91 | ||
|
|
ced1095e17 | ||
|
|
293933b813 | ||
|
|
af1ef7f47a | ||
|
|
74cbda6d8e | ||
|
|
06c4786ee0 | ||
|
|
266a51510c | ||
|
|
323a51d868 | ||
|
|
fdbaeb0b5f | ||
|
|
ca89db448f | ||
|
|
9f2b778ddc | ||
|
|
f782eced38 | ||
|
|
cd36df6e3d | ||
|
|
77f2558cf5 | ||
|
|
8f869ad471 | ||
|
|
e586fd89cc | ||
|
|
262edd2c25 | ||
|
|
99f0ed1915 | ||
|
|
31d6fbf3b7 | ||
|
|
835f8b974d | ||
|
|
84c2d76c05 | ||
|
|
31a30afafd | ||
|
|
1512b21e60 | ||
|
|
03a7e43372 | ||
|
|
0ce1c441d5 | ||
|
|
7ba752e038 | ||
|
|
6f5ca9b4f5 | ||
|
|
b8c664c739 | ||
|
|
945f8b6466 | ||
|
|
b206ce368f | ||
|
|
0eaa86c230 | ||
|
|
1065858043 | ||
|
|
0f7da65f87 | ||
|
|
b8d57b4bb9 | ||
|
|
6a6ac511a1 | ||
|
|
6967a41472 | ||
|
|
77651cdd30 | ||
|
|
369c252b1a | ||
|
|
2f4d225a76 | ||
|
|
1ca942b29a | ||
|
|
bf02047280 | ||
|
|
dab2a632b6 | ||
|
|
7bec2e1d08 | ||
|
|
e3560a8763 | ||
|
|
f83cc7de5f | ||
|
|
deba783731 | ||
|
|
511cb5d3a5 | ||
|
|
4254b1f901 | ||
|
|
ff51926fe8 | ||
|
|
8b9d270817 | ||
|
|
b115f817e6 | ||
|
|
0e5804fdbf | ||
|
|
3bd0528ba5 | ||
|
|
f524f25c58 | ||
|
|
8a657eb4b2 | ||
|
|
8c57c91103 | ||
|
|
e57db36501 | ||
|
|
a5a29dc47b | ||
|
|
a54d9034f1 | ||
|
|
5edc9e16f0 | ||
|
|
5a06315429 | ||
|
|
bddb8ae9f7 |
@@ -1,10 +1,6 @@
|
||||
language: erlang
|
||||
|
||||
otp_release:
|
||||
- 17.0
|
||||
- 17.3
|
||||
- 17.4
|
||||
- 17.5
|
||||
- 18.0
|
||||
|
||||
sudo: false
|
||||
|
||||
+168
-157
@@ -1,201 +1,212 @@
|
||||
# Changelog for Elixir v1.1
|
||||
# Changelog for Elixir v1.2
|
||||
|
||||
v1.1 brings enhancements, bug fixes, performance improvements and more
|
||||
into Elixir.
|
||||
v1.2 brings enhancements, bug fixes, performance improvements and more
|
||||
into Elixir. Elixir v1.2 relies on many features in Erlang 18, requiring
|
||||
at least Erlang 18+. Upgrading to Erlang 18 is therefore necessary before
|
||||
upgrading Elixir.
|
||||
|
||||
Elixir v1.1 supports both Erlang 17 and Erlang 18. This is, however, the
|
||||
last release supporting Erlang 17, so upgrading to Erlang 18 is advised.
|
||||
Elixir v1.2 will introduce features that are Erlang 18 only.
|
||||
## Erlang 18 support
|
||||
|
||||
On the enhancements side, the most notable changes are the new functions
|
||||
added to `Enum`, `Dict` and `Task` modules, and a new datatype called `MapSet`.
|
||||
`MapSet` implements the `Set` API on top of a map and, for Elixir v1.1,
|
||||
it is useful for holding only dozens of entries. Future Elixir versions,
|
||||
however, will be able to rely on `MapSet` from dozens of keys up to
|
||||
millions, with better performance than `HashSet`.
|
||||
We have brought many features specific to Erlang 18. Here are the highlights:
|
||||
|
||||
On the deprecation side, this release includes one major deprecation and
|
||||
some soft deprecations.
|
||||
* Maps can now scale from dozens to millions of keys. Therefore, usage of
|
||||
the modules `Dict` and `HashDict` is now discouraged and will be
|
||||
deprecated in future releases, instead use `Map`. Similarly, `Set` and
|
||||
`HashSet` will be deprecated in favor of `MapSet`
|
||||
* Compilation times are faster due to improvements in both the Elixir and
|
||||
Erlang compilers
|
||||
* Dialyzer now emits less false negative warnings thanks to new annotations
|
||||
available in the Erlang compiler
|
||||
|
||||
The major deprecation relates to the Access protocol. Due to performance
|
||||
issues, the access syntax `opts[key]` will no longer be powered by the
|
||||
`Access` protocol, instead, it will use a subset of the `Dict` module.
|
||||
Therefore this release will emit warnings if you attempt to implement
|
||||
the `Access` protocol. Note the `Access` module and the `opts[key]`
|
||||
syntax are not affected and they are not deprecated, only the underlying
|
||||
protocol dispatch.
|
||||
## Language improvements
|
||||
|
||||
The soft deprecations are minor and they won't emit warnings. It simply
|
||||
means the documentation has been updated to mention the new best
|
||||
practices. Warnings will be emitted in the future though (when they are
|
||||
promoted to deprecations).
|
||||
This release includes four notable language improvements:
|
||||
|
||||
On the tooling side, ExUnit got the ability to skip tests and a couple
|
||||
new configuration options. Mix got improved warnings and error messages,
|
||||
faster compilation times and the brand new `mix profile.fprof` task.
|
||||
* The addition of multi aliases/imports/require:
|
||||
|
||||
Mix now also ships with `local.public_keys` to safely manage the
|
||||
installation of Hex and Rebar alongside the ability to checksum
|
||||
archive installs. By default, Elixir will always ship with a valid
|
||||
public key and this feature should work transparently for users.
|
||||
alias MyApp.{Foo, Bar, Baz}
|
||||
|
||||
Finally, we have added a `CODE_OF_CONDUCT.md` file to our repository.
|
||||
If you haven't read it yet, please do it. We are here to help!
|
||||
* Support for variables in map keys:
|
||||
|
||||
Note: Erlang 17.1 contains a regression in its wildcard implementation that
|
||||
causes tools like rebar to fail. If you have a project with rebar dependencies
|
||||
and is using Erlang 17.1, remember to update to at least Erlang 17.3.
|
||||
%{key => value}
|
||||
|
||||
## v1.1.0 (2015-09-25)
|
||||
* Support for the pin operator in map keys and function clauses:
|
||||
|
||||
%{^key => value} = %{key => value}
|
||||
fn ^key -> :ok end
|
||||
|
||||
* Addition of the `with` special form to match on multiple expressions:
|
||||
|
||||
with {:ok, contents} <- File.read("my_file.ex"),
|
||||
{res, binding} <- Code.eval_string(contents),
|
||||
do: {:ok, res}
|
||||
|
||||
These improvements aim to make the language more consistent and expressive.
|
||||
|
||||
## Getting started experience
|
||||
|
||||
While we were improving the language, we also improved both the parser and
|
||||
compiler to be even more aware of language constructs, emitting warnings
|
||||
on common pitfalls like when piping to expressions without parentheses or
|
||||
when defining unsafe variables.
|
||||
|
||||
We have also introduced the `i/1` helper in IEx, which allows developers
|
||||
to retrieve information about any data type. This will help newcomers
|
||||
explore the language values while providing experienced developers with
|
||||
crucial information about the value they are introspecting.
|
||||
|
||||
## Workflow improvements
|
||||
|
||||
Umbrella applications are now able to share both build and configuration files.
|
||||
This aims to drastically reduce compilation times in umbrella projects by
|
||||
adding the following configuration to each umbrella app's `mix.exs` file:
|
||||
|
||||
build_path: "../../_build",
|
||||
config_path: "../../config/config.exs",
|
||||
|
||||
Finally, Mix will now consolidate protocols by default as we are now able to
|
||||
consolidate in parallel and cache the consolidation results, providing the
|
||||
best performance across all environments without affecting compilation times.
|
||||
The only downside of this change is that, if you have been implementing
|
||||
protocols exclusively as part of your test suite, inside the `test` directory,
|
||||
those won't be picked up as it happens after compilation. For such cases,
|
||||
consolidation can be disabled by setting `consolidate_protocols: false` in
|
||||
the project config.
|
||||
|
||||
These are great additions on top of the faster compilation times we have
|
||||
achieved when migrating to Erlang 18.
|
||||
|
||||
## Rebar 3 support
|
||||
|
||||
With Rebar 3 gaining more adoption in the Erlang community, Mix is
|
||||
now able to fetch and compile Rebar 3 dependencies. This feature is currently
|
||||
experimental and therefore opt-in: if you have a Rebar 3 dependency, you can
|
||||
ask Mix to use Rebar 3 to compile it by passing the `manager: :rebar3` option.
|
||||
Once configured, Mix will prompt you to install Rebar 3 if it is not yet
|
||||
available.
|
||||
|
||||
## v1.2.1 (2016-01-14)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
* [IEx] Support remote pids/ports with IEx helper `i/1`
|
||||
* [Protocol] Warn when `defimpl` is called for a consolidated protocol
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
* [ExUnit] Ensure `assert` macros can be used from quoted code
|
||||
* [ExUnit] Do not warn in match assertion if variable is reused in pattern
|
||||
* [Macro] Fix a bug in `Macro.to_string/1` where a remote function could be accidentally interpreted as a sigil
|
||||
* [Mix] Ensure dependencies are properly skipped when `--only` option is given to `mix deps.get`
|
||||
|
||||
## v1.2.0 (2016-01-01)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Application] Add `Application.fetch_env!/2`, `Application.loaded_applications/0` and `Application.started_applications/0`
|
||||
* [CLI] Add support for `--werl` in Windows bash-like shells
|
||||
* [Dict] Add `Dict.get_and_update/3` which behaves similar to the now deprecated Access protocol
|
||||
* [Dict] Add `Dict.get_lazy/3`, `Dict.pop_lazy/3` and `Dict.put_new_lazy/3`
|
||||
* [Enum] Add `Enum.random/1`, `Enum.take_random/2`, `Enum.min_max/1`, `Enum.min_max_by/2`, `Enum.reverse_slice/3`, `Enum.reduce_while/3`, `Enum.dedup/1` and `Enum.dedup_by/2`
|
||||
* [Enum] Inline common map usage in `Enum` functions for performance
|
||||
* [File] Add `File.lstat/1` and `File.lstat/1` that works like `File.stat/1` but is able to return symlink information (i.e. it does not traverse symlinks)
|
||||
* [File] Add `File.rename/2`
|
||||
* [Integer] Add `Integer.digits/2` and `Integer.undigits/2`
|
||||
* [Inspect] Add the `:safe` option to `inspect/2` and make it safe by default, meaning failures while inspecting won't trigger other failures. Instead, it will be wrapped in an exception which is properly formatted
|
||||
* [IO] Support fenced code blocks on `IO.ANSI.Docs`
|
||||
* [GenServer] Add `GenServer.whereis/1` that expands `GenServer` dispatches into a proper pid
|
||||
* [Kernel] No longer include `:crypto` and `:syntax_tools` as dependencies. The former is only needed if you have encrypted debug info (therefore you can add `:crypto` as a dependency manually) and the latter is no longer used
|
||||
* [Kernel] Raise when `var.Alias` syntax is used and it does not expand to an atom at compile time (previously it emitted warnings)
|
||||
* [Kernel] Improve generation of argument names for function signatures
|
||||
* [Kernel] `::/2` is now a special form
|
||||
* [Kernel] Warn when a variable with underscore is used
|
||||
* [Kernel] Allow underscores in binary, octal and hex literals
|
||||
* [Kernel] Warn when module attributes, variables, strings and numbers are used in code but the expression has no effect
|
||||
* [Kernel] Support `\uXXXX` and `\u{X*}` in strings and char lists to map to Unicode codepoints
|
||||
* [List] Add `List.keytake/3`
|
||||
* [Module] Improve name inference for function signatures in documentation metadata
|
||||
* [Process] Add `Process.hibernate/3`
|
||||
* [Process] Allow a list of specs in `Process.info/2`
|
||||
* [Set] Introduce `MapSet` data type. This new data type uses maps behind the scenes and is useful for storing a dozens of items in Erlang 17. In future versions when maps efficiently support large collections, it is meant to be the main Set abstraction in Elixir
|
||||
* [Stream] Add `Stream.dedup/1`, `Stream.dedup_by/2` and `Stream.transform/4`
|
||||
* [String] Support calculation of the jaro distance between strings (usually names) via `String.jaro_distance/2`. This is used by Mix to support "Did you mean?" feature when a task does not exist
|
||||
* [String] Add `String.splitter/3` that splits strings as a stream
|
||||
* [StringIO] `StringIO.flush/1` was added to flush the output of a StringIO device
|
||||
* [Task] Introduce `Task.yield/2` and `Task.shutdown/2` to check if a task is still executing and shutdown otherwise
|
||||
* [Tuple] Add `Tuple.append/2`
|
||||
* [URI] Default ports were added for "ws" and "wss" schemas
|
||||
* [URI] Add `URI.to_string/1`
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Add `:trim` option to EEx that automatically trims the left side of `<%` and right side `%>` if only spaces and new lines preceed/follow them
|
||||
* [Application] Add `spec/1` and `spec/2` to retrieve application specification
|
||||
* [Application] Add `get_application/1` to retrieve the application a given module belongs to
|
||||
* [Base] Optimize encode and decode operations about 10 times
|
||||
* [Enum] Use the faster and auto-seeding `:rand` instead of `:random` in `Enum.shuffle/1` and `Enum.random/1` and `Enum.take_random/2`
|
||||
* [Enum] Add `Enum.with_index/2`
|
||||
* [GenServer] Add `GenServer.stop/1` for shutting down servers reliably
|
||||
* [IO] Add `color` related functions to `IO.ANSI`
|
||||
* [Kernel] Support multiple aliases in `alias`, `import`, `require` and `use`. For example, `alias MyApp.{Foo, Bar, Baz}`
|
||||
* [Kernel] Add `struct!/2`. Similar to `struct/2` but raises on invalid keys
|
||||
* [Kernel] Warn if `@doc/@typedoc/@moduledoc` attributes are redefined
|
||||
* [Kernel] Warn if non-variables are used in `defdelegate/2` (as they have no effect)
|
||||
* [Kernel] Mark quoted expressions as generated, avoiding false positives on dialyzer
|
||||
* [Kernel] Allow variables as map keys on creation `%{key => value}` and on matches `%{^key => value}`
|
||||
* [Kernel] Allow the pin operator `^` in `fn` clauses and on the left side of `<-` in `for` comprehensions
|
||||
* [Kernel] Introduce `with` as a special form that allows matching on right side parameters
|
||||
* [Kernel] Warn when right hand side of `->` does not provide any expression
|
||||
* [Kernel] Warn if the Elixir was compiled with a different endianness than the one currently available at runtime
|
||||
* [Kernel] Warn if a variable is used after being defined exclusively in a nested context
|
||||
* [Kernel] Warn if piping into an expression without parentheses
|
||||
* [Macro] Add `Macro.traverse/4` that performs pre and post-walk at once
|
||||
* [Macro] Add `Macro.camelize/1` and `Macro.underscore/1`
|
||||
* [Process] Add `Process.get_keys/0`
|
||||
* [Stream] Add `Stream.with_index/2`
|
||||
* [String] Introduce `String.replace_{prefix,suffix,leading,trailing}/2`. The first two will replace only the first occurrence of the given match in string. The last two will replace all occurrences of the given match
|
||||
* [String] Support `String.normalize/2` and `String.equivalent?/2` that perform NFD and NFC normalization
|
||||
* [System] Add `System.time_offset`, `System.monotonic_time`, `System.system_time`, `System.convert_time_unit` and `System.unique_integer`
|
||||
* [System] Allow `System.cmd/3` to remove variables by specifying nil values
|
||||
* [Task] Add `Task.Supervisor.async_nolink/1/3` that spawns a supervised task without linking to the caller process
|
||||
* [Task] Introduce `Task.yield_many/2`
|
||||
* [Task] Raise an error when a task is queried from a non-owning process (instead of waiting forever)
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Add number of skipped tests to `ExUnit` output
|
||||
* [ExUnit] Make timeout configurable for the whole test suite via the `:timeout` configuration
|
||||
* [ExUnit] Allow moduledoc to be filtered/skipped in doctests
|
||||
* [ExUnit] Provide built-in log capturing functionality
|
||||
* [ExUnit] Allow `assert_receive_timeout` and `refute_receive_timeout` to be configured in the ExUnit application
|
||||
* [ExUnit] Allow tests to be skipped with `@tag :skip` or `@tag skip: "reason"`
|
||||
* [ExUnit] Add tests without implementation (missing the do block) which automatically fail. Such tests are also automatically tagged as `:not_implemented`, allowing them to be skipped
|
||||
* [ExUnit] Increase by default stacktrace depth to 20 (this value is also configurable)
|
||||
* [ExUnit] Improve formatting on `assert_raise` errors for message mismatch
|
||||
* [ExUnit] Improve formatting on `assert_receive` when using pinned variables
|
||||
* [ExUnit] Allow one test to raise multiple errors. The goal is to enable tools in the ecosystem to emit multiple failure reports from the same test
|
||||
* [ExUnit] Support `@tag report: [:foo, :bar]` which will include the values for tags `:foo` and `:bar` whenever a test fails
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Support `IEx.pry` with `--remsh` for remote debugging
|
||||
* [IEx] Add `b/1` helper that shows documentation for behaviour modules and its callback functions
|
||||
* [IEx] Provide tab completion for aliases and allow aliases like `Foo.Bar.Baz` to autocomplete even if `Foo.Bar` is not defined
|
||||
* [IEx] Provide a `pid/3` helper for buildings pids from numbers
|
||||
* [IEx] Allow `IEX_WITH_WERL` to be set on Windows to always run on WERL mode
|
||||
* [IEx] Display type docs for `t(Module.type)` and `t(Module.type/arity)`
|
||||
* [IEx] Add `i/1` helper that prints information about any data type
|
||||
* [IEx] Show source code snippet whenever there is a request to pry a given process
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Support printing pids and refs in Logger metadata
|
||||
* [Logger] Allow Logger metadata to be removed from pdict by setting it to `nil`
|
||||
* [Logger] Add application configuration `translator_inspect_opts` for logger to customize how state and message are formatted when translating OTP errors and reports
|
||||
* [Logger] Automatically include the current application in metadata when compiled via Mix
|
||||
* [Logger] Add file to logger metadata
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Check Elixir version right after archive installation and provide feedback if there is a mismatch
|
||||
* [Mix] Allow rebar dependencies with `mix.exs` to be compiled with Mix
|
||||
* [Mix] Allow rebar dependencies to be specified via `:path`
|
||||
* [Mix] Also consider subdirectories in `config` directory for `Mix.Project.config_files/0`
|
||||
* [Mix] Allow dynamic configuration in Mix projects by storing config in an agent
|
||||
* [Mix] Support rebar3 style Git refs in `rebar.config` files
|
||||
* [Mix] Only recompile compile time dependencies in mix projects. This should considerably speed up recompilation times in Elixir projects
|
||||
* [Mix] Warn when configuring an application that is not available
|
||||
* [Mix] Add `mix profile.fprof` for easy code profiling
|
||||
* [Mix] Abort when dependencies have conflicting `:only` definitions
|
||||
* [Mix] Fully recompile projects if Elixir or SCM changes
|
||||
* [Mix] Allow checksum to be checked on archive install via `--sha512` option
|
||||
* [Mix] Add `mix local.public_keys` to safely manage installation of Hex and Rebar dependencies
|
||||
* [Mix] Cache and always consolidate protocols
|
||||
* [Mix] Add `warn_test_pattern` to `mix test` that will warn on potentially misconfigured test files
|
||||
* [Mix] Introduce `MIX_QUIET` environment variable that configures the underlying Mix task to output only error messages
|
||||
* [Mix] Introduce `MIX_DEBUG` environment variable that prints information about the task being run
|
||||
* [Mix] Validate git options and warn on conflicting ref, branch or tags
|
||||
* [Mix] New umbrella applications will now share configuration and build files
|
||||
* [Mix] Add experimental support for Rebar 3
|
||||
* [Mix] Do not warn when an optional dependency has a conflicting `:only` option with another dependency
|
||||
* [Mix] Raise readable error message when parsertools is not available
|
||||
* [Mix] Add `--build` flag to `mix deps.clean DEP` to only remove artifacts from `_build`
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
#### Kernel
|
||||
|
||||
* [CLI] Ensure Logger messages are flushed when executing commands
|
||||
* [Code] `:delegate_locals_to` failed to delegate to the chosen module in many situations and messed up stacktraces. This option has therefore been replaced by imports
|
||||
* [Code] Store the documentation line in the metadata returned by `Code.get_docs/2`
|
||||
* [Exception] Do not fail when calculating an exception message, even if the message is invalid
|
||||
* [File] Ensure `File.touch/2` and `File.stat/2` receive and return universal times. Previously they would work with local times which are not monotonically increasing, which could present issues on scripts. If the times are being shown to the user, `time: :local` can be given as argument
|
||||
* [Float] Support complete scientific notation in `Float.parse/1`
|
||||
* [Kernel] Do not expand `in/2` argument in module body
|
||||
* [Kernel] Throw syntax error for undefind atom/alias syntax `:foo.Bar`
|
||||
* [Kernel] Improve error message when we can't compile because the target directory is not writeable
|
||||
* [Kernel] Allow capture of non-symbolic operators like `&and/2`, `¬/1` and others
|
||||
* [Kernel] Raise if heredoc terminal is accidentally found in the middle of a line without escaping
|
||||
* [Kernel] Don't warn on missing imports if nothing was imported
|
||||
* [Kernel] Raise if arity bigger than 255 is given to capture operator
|
||||
* [Macro] Properly convert captures in `Macro.to_string/1`
|
||||
* [Module] Do not accept non-Elixir module names in `Module.split/1`
|
||||
* [Protocol] Guarantee that derived protocols go through `Any` instead of `Map`
|
||||
* [Range] Restrict ranges to integers to fix diverse bugs of values being included in the range when they should not (false positives)
|
||||
* [Regex] Fix splitting of empty strings with regexes when trim is set to `true`. Now both `String.split/3` and `Regex.split/3` return an empty list when called with an empty string and trim is enabled
|
||||
* [Regex] Fix `Regex.replace/4` so it doesn't discard escape characters
|
||||
* [Stream] Ensure suspending `Stream.flat_map/2` and `Stream.transform/3` does not consume unecessary items from the given enumerable
|
||||
* [Access] Improve error messages when using Access on non-valid key-value structures
|
||||
* [Kernel] Raise when conflicting `:only` and `:except` are given to import
|
||||
* [Kernel] Change `__ENV__.file` if `@file` is set for the given function
|
||||
* [Kernel] Make `Kernel.ParallelRequire` aware of `:warning_as_errors`
|
||||
* [Kernel] Improve error message for invalid `do`/`do:`
|
||||
* [Macro] Ensure `Macro.to_string/2` respects operator precedence when using the access operator
|
||||
* [Path] Do not crash when expanding paths that go beyond the root, for example, `Path.expand("/../..")`
|
||||
* [String] Ensure `UnicodeConversionError` does not contain invalid string in its error message
|
||||
|
||||
#### EEx
|
||||
#### IEx
|
||||
|
||||
* [EEx] Allow EEx interpolation to also apply inside quotations `<%%= ... %>`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Skipped tests now correctly count towards the total of tests in the result returned by `ExUnit.run/0`
|
||||
* [ExUnit] Fix a bug where failures when inspecting data structure or retrieving error messages could bring the whole ExUnit runner down
|
||||
* [ExUnit] Do not change the semantics of evaluated code with `assert`/`refute`. For example, from now on, `assert nil = some_expr()` will now raise as expected, as the expression still evaluates to a falsy value
|
||||
* [ExUnit] Report proper line number for doctest failures
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Include metadata in `Logger.log/3`, use `Logger.bare_log/3` for runtime-only, with no metadata behaviour
|
||||
* [IEx] Do not start apps on `recompile` helper if `--no-start` was given
|
||||
* [IEx] Avoid copying of data when evaluating every expression in IEx
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Use the safer `https` protocol instead of `git` for `:github` dependencies
|
||||
* [Mix] Ensure automatic protocol consolidation via `:consolidate_protocols` is triggered in umbrella apps
|
||||
* [Mix] Do not raise if wildcard given to `import_config` does not match any file
|
||||
* [Mix] Applications with `:build_embedded` set to true require explicit compilation step
|
||||
* [Mix] Also remove consolidated protocols on `mix clean`
|
||||
* [Mix] Ensure `--exclude` in `mix test` concatenates with test helper excludes
|
||||
* [Mix] Always run non-recursive tasks at the umbrella root
|
||||
* [Mix] Ensure rebar projects work on directory names that contain non-latin characters
|
||||
* [Mix] Ignore directories inside `apps` in umbrellas that do not have a `mix.exs` file
|
||||
* [Mix] Ensure Mix can be used with path dependencies where the app name is different than the path basename
|
||||
* [Mix] Ensure dependencies won't crash when updating from a git repository to a hex repository and the git version did not respect SemVer
|
||||
* [Mix] Do not run remote converger if dependencies have diverged
|
||||
* [Mix] Ensure umbrella dependencies across all environments are loaded on parent deps.get/deps.update
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Include file and line in all compilation errors for doctests
|
||||
|
||||
### 3. Soft deprecations (no warnings emitted)
|
||||
|
||||
#### Elixir
|
||||
#### Kernel
|
||||
|
||||
* [Behaviour] The module `Behaviour` is deprecated. Instead of `defcallback`, one can simply use `@callback`. Instead of `defmacrocallback`, one can simply use `@macrocallback`
|
||||
* [Enum] `Enum.uniq/2` is deprecated in favor of `Enum.uniq_by/2`
|
||||
* [Kernel] `\x` inside strings and charlists is deprecated in favor of `\uXXXX` and `\u{X*}`. The values emitted by `\x` are unfortunately wrong (they should be bytes but currently it emits codepoints). `\u` is meant to correctly map to codepoints and `\x` will be fixed in the future to map to bytes
|
||||
* [Regex] Ungreedy option `r` is deprecated in favor of `U` (which is standard in regular expressions in other languages)
|
||||
* [Dict] `Dict` and `HashDict` are soft deprecated in favor of `Map`
|
||||
* [Keyword] `Keyword.size/1` is deprecated in favor of `length/1`
|
||||
* [Map] `Map.size/1` is deprecated in favor of `map_size/1`
|
||||
* [Set] `Set` and `HashSet` are soft deprecated in favor of `MapSet`
|
||||
|
||||
### 4. Deprecations
|
||||
#### Mix
|
||||
|
||||
#### Elixir
|
||||
* [Mix] `Mix.Utils.camelize/1` and `Mix.Utils.underscore/1` are soft deprecated in favor of `Macro.camelize/1` and `Macro.underscore/1`
|
||||
|
||||
* [Access] Implementing the Access protocol is deprecated. The Access protocol relies on the code server in development and test mode (when protocol consolidation is not applied) and it generated a bottleneck when working with multiple processes and the Access protocol was invoked hundreds of times (which is not uncommon). Note the `Access` module and the `opts[key]` syntax are not affected and they are not deprecated, only the underlying protocol dispatch
|
||||
* [Kernel] `?\xHEX` is deprecated in favor of `0xHEX`. There is no situation where the former should be used in favor of the latter and the latter is always cleaner
|
||||
* [Kernel] Giving `as: true | false` to `alias/2` and `require/2` have been deprecated (it was undocumented behaviour)
|
||||
* [String] Passing an empty string to `starts_with?`, `contains?` and `ends_with?` had dubious behaviour and have been deprecated to help developers identify possible bugs in their source code
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
LEGAL NOTICE INFORMATION
|
||||
------------------------
|
||||
|
||||
All the files in this distribution are copyright (c) 2012 Plataformatec
|
||||
covered under Elixir's license (see the file LICENSE) except the file
|
||||
mentioned below that contains sections that are copyright
|
||||
(c) 1996 Ericsson AB under Erlang's License (EPL):
|
||||
|
||||
lib/elixir/src/elixir_parser.erl (generated by build scripts)
|
||||
@@ -1,6 +1,6 @@
|
||||
REBAR ?= "$(CURDIR)/rebar"
|
||||
PREFIX ?= /usr/local
|
||||
DOCS := master
|
||||
DOCS := v1.2
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -17,13 +17,11 @@ INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
#==> Functions
|
||||
|
||||
# This check should work for older versions like R16B
|
||||
# as well as new verions like 17.1 and 18
|
||||
define CHECK_ERLANG_RELEASE
|
||||
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release)])' -s erlang halt | grep -q '^1[789]'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang 17.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release) >= "18"])' -s erlang halt | grep -q '^true'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang 18.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
endef
|
||||
|
||||
@@ -42,7 +40,7 @@ lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
|
||||
@ rm -rf lib/$(1)/ebin
|
||||
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
test_$(1): compile $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
endef
|
||||
@@ -130,14 +128,14 @@ clean_exbeam:
|
||||
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) -o doc/$(2) -p http://elixir-lang.org/docs.html
|
||||
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) -o doc/$(2) -p http://elixir-lang.org/docs.html $(4)
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
|
||||
|
||||
docs_elixir: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (elixir)"
|
||||
$(Q) rm -rf doc/elixir
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel,-e "lib/elixir/pages/Typespecs.md" -e "lib/elixir/pages/Writing Documentation.md")
|
||||
|
||||
docs_eex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (eex)"
|
||||
@@ -172,12 +170,12 @@ docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
Docs.zip: docs
|
||||
rm -rf Docs-v$(VERSION).zip
|
||||
zip -9 -r Docs-v$(VERSION).zip doc
|
||||
zip -9 -r Docs-v$(VERSION).zip CHANGELOG.md doc NOTICE LICENSE README.md
|
||||
@ echo "Docs file created $(CURDIR)/Docs-v$(VERSION).zip"
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -rf Precompiled-v$(VERSION).zip
|
||||
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE man README.md VERSION
|
||||
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin LICENSE man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
|
||||
|
||||
#==> Publish
|
||||
@@ -188,16 +186,6 @@ publish_docs: docs
|
||||
rm -rf ../docs/$(DOCS)/*/
|
||||
cp -R doc/* ../docs/$(DOCS)
|
||||
|
||||
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
|
||||
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
|
||||
|
||||
publish_mix: compile
|
||||
cd lib/mix && MIX_ENV=prod mix escript.build
|
||||
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
|
||||
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
|
||||
rm lib/mix/mix
|
||||
rm -rf lib/mix/_build
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
test: test_erlang test_elixir
|
||||
@@ -215,11 +203,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
|
||||
$(Q) mkdir -p $(TEST_EBIN)
|
||||
$(Q) $(ERLC) -o $(TEST_EBIN) $<
|
||||
|
||||
test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
test_elixir: test_stdlib test_ex_unit test_logger test_mix test_eex test_iex
|
||||
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
LEGAL NOTICE INFORMATION
|
||||
------------------------
|
||||
|
||||
All the files in this distribution are copyright (c) 2012 Plataformatec
|
||||
covered under Elixir's license (see the file LICENSE) except the cases
|
||||
below.
|
||||
|
||||
== lib/elixir/src/elixir_parser.erl (generated by build scripts)
|
||||
|
||||
Copyright Ericsson AB 1996-2015
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,12 +1,15 @@
|
||||

|
||||
=========
|
||||
[](https://travis-ci.org/elixir-lang/elixir)
|
||||
[](https://travis-ci.org/elixir-lang/elixir)
|
||||
|
||||
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
|
||||
For more about Elixir, installation and documentation,
|
||||
[check Elixir's website](http://elixir-lang.org/).
|
||||
|
||||
## Usage
|
||||
|
||||
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
|
||||
If you want to contribute to Elixir or run it from source, clone this
|
||||
repository to your machine, compile and test it:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/elixir-lang/elixir.git
|
||||
@@ -14,32 +17,50 @@ cd elixir
|
||||
make clean test
|
||||
```
|
||||
|
||||
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
|
||||
> Note: if you are running on Windows,
|
||||
[this article includes important notes for compiling Elixir from source
|
||||
on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
|
||||
|
||||
If Elixir fails to build (specifically when pulling in a new version via `git`), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
|
||||
If Elixir fails to build (specifically when pulling in a new version via
|
||||
`git`), be sure to remove any previous build artifacts by running
|
||||
`make clean`, then `make test`.
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
If tests pass, you are ready to move on to the
|
||||
[Getting Started guide][1] or to try Interactive Elixir by running:
|
||||
`bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version
|
||||
(Elixir requires Erlang 18.0 or later).
|
||||
You can check your Erlang version by calling `erl` in the command line.
|
||||
You will see some information as follows:
|
||||
|
||||
`Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]`
|
||||
`Erlang/OTP 18 [erts-7.0] [source] [smp:2:2] [async-threads:10] [hipe]
|
||||
[kernel-poll:false]`
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
If you have the correct version and tests still fail, feel free to
|
||||
[open an issue][2].
|
||||
|
||||
## Building documentation
|
||||
|
||||
Building the documentation requires [ExDoc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as Elixir.
|
||||
Building the documentation requires
|
||||
[ExDoc](https://github.com/elixir-lang/ex_doc) to be installed and built
|
||||
alongside Elixir.
|
||||
|
||||
```sh
|
||||
# After cloning and compiling Elixir
|
||||
# After cloning and compiling Elixir, in its parent directory:
|
||||
git clone git://github.com/elixir-lang/ex_doc.git
|
||||
cd ex_doc && ../elixir/bin/mix do deps.get, compile
|
||||
cd ../elixir && make docs
|
||||
```
|
||||
|
||||
This will produce documentation sets for `elixir`, `mix`, etc., under the `doc` directory.
|
||||
|
||||
## Contributing
|
||||
|
||||
We appreciate any contribution to Elixir. Check our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [CONTRIBUTING.md](CONTRIBUTING.md) guides for more information. We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
We appreciate any contribution to Elixir.
|
||||
Check our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md) guides for more information.
|
||||
We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
## Important links
|
||||
|
||||
@@ -54,14 +75,15 @@ We appreciate any contribution to Elixir. Check our [CODE_OF_CONDUCT.md](CODE_OF
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: https://groups.google.com/group/elixir-lang-talk
|
||||
[4]: https://groups.google.com/group/elixir-lang-core
|
||||
[5]: irc://chat.freenode.net/elixir-lang
|
||||
[6]: http://www.freenode.net/
|
||||
[5]: https://webchat.freenode.net/?channels=#elixir-lang
|
||||
[6]: http://www.freenode.net
|
||||
[7]: http://elixir-lang.org/docs.html
|
||||
|
||||
## License
|
||||
|
||||
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
|
||||
|
||||
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
|
||||
Elixir source code is released under Apache 2 License.
|
||||
|
||||
Check [LEGAL](LEGAL) and [LICENSE](LICENSE) files for more information.
|
||||
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more
|
||||
information.
|
||||
|
||||
+1
-3
@@ -30,12 +30,10 @@ This document simply outlines the release process:
|
||||
|
||||
12. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable` (change index.html accordingly)
|
||||
|
||||
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
|
||||
15. `make release_docs` and push it to `elixir-lang/docs`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION (make sure there is no newline in this file)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version and exit
|
||||
-v Prints version and exits
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
@echo off
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
setlocal
|
||||
if ""%1""=="""" goto :documentation
|
||||
if ""%1""==""--help"" goto :documentation
|
||||
@@ -9,7 +9,7 @@ goto parseopts
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
echo -v Prints version and exit
|
||||
echo -v Prints version and exits
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@echo off
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
set argc=0
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
|
||||
+4
-2
@@ -1,2 +1,4 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
@if defined IEX_WITH_WERL (@set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %__ELIXIR_IEX_FLAGS% %*
|
||||
@set __ELIXIR_IEX_FLAGS=
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
+3
-3
@@ -9,7 +9,7 @@ end
|
||||
defmodule EEx do
|
||||
@moduledoc ~S"""
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way:
|
||||
Elixir code inside a string in a robust way.
|
||||
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
@@ -85,9 +85,9 @@ defmodule EEx do
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, `<%= @foo %>` is simply translated to:
|
||||
In other words, `<%= @foo %>` translates to:
|
||||
|
||||
<%= Dict.get assigns, :foo %>
|
||||
<%= {:ok, v} = Access.fetch(assigns, :foo); v %>
|
||||
|
||||
The assigns extension is useful when the number of variables
|
||||
required by the template is not specified at compilation time.
|
||||
|
||||
@@ -53,6 +53,9 @@ defmodule EEx.Engine do
|
||||
@doc """
|
||||
Handles assigns in quoted expressions.
|
||||
|
||||
A warning will be printed on missing assigns.
|
||||
Future versions will raise.
|
||||
|
||||
This can be added to any custom engine by invoking
|
||||
`handle_assign/1` with `Macro.prewalk/2`:
|
||||
|
||||
@@ -64,13 +67,28 @@ defmodule EEx.Engine do
|
||||
"""
|
||||
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
line = meta[:line] || 0
|
||||
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
||||
quote line: line, do: EEx.Engine.fetch_assign!(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
def handle_assign(arg) do
|
||||
arg
|
||||
end
|
||||
|
||||
@doc false
|
||||
# TODO: raise on 1.3 or 1.4
|
||||
def fetch_assign!(assigns, key) do
|
||||
case Access.fetch(assigns, key) do
|
||||
{:ok, val} ->
|
||||
val
|
||||
:error ->
|
||||
keys = Enum.map(assigns, &elem(&1, 0))
|
||||
IO.write :stderr, "warning: assign @#{key} not available in eex template. " <>
|
||||
"Please ensure all assigns are given as options. " <>
|
||||
"Available assigns: #{inspect keys}\n" <>
|
||||
Exception.format_stacktrace
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation simply returns the given expression.
|
||||
"""
|
||||
|
||||
@@ -236,6 +236,10 @@ foo
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "respects files" do
|
||||
assert_eval "sample.ex", "<%= __ENV__.file %>", [], file: "sample.ex"
|
||||
end
|
||||
|
||||
test "properly handle functions" do
|
||||
expected = """
|
||||
|
||||
|
||||
+89
-44
@@ -1,15 +1,16 @@
|
||||
defmodule Access do
|
||||
@moduledoc """
|
||||
Dictionary-like access to data structures via the `foo[bar]` syntax.
|
||||
Key-based access to data structures via the `foo[bar]` syntax.
|
||||
|
||||
This module also empowers `Kernel`s nested update functions
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_and_update_in/3`.
|
||||
Elixir provides two syntaxes for accessing values. `user[:name]`
|
||||
is used by dynamic structures, like maps and keywords, while
|
||||
`user.name` is used by structs. The main difference is that
|
||||
`user[:name]` won't raise if the key `:name` is missing but
|
||||
`user.name` will raise if there is no `:name` key.
|
||||
|
||||
## Examples
|
||||
## Key-based lookups
|
||||
|
||||
Out of the box, Access works with built-in dictionaries: `Keyword`
|
||||
and `Map`:
|
||||
Out of the box, Access works with `Keyword` and `Map`:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
@@ -23,13 +24,67 @@ defmodule Access do
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
Access can be combined with `Kernel.put_in/3` to put a value
|
||||
in a given key:
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> put_in map[:a], 3
|
||||
%{a: 3, b: 2}
|
||||
|
||||
This syntax is very convenient as it can be nested arbitrarily:
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in users["john"][:age], 28
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
Furthermore, Access transparently ignores `nil` values:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:c][:unknown]
|
||||
nil
|
||||
|
||||
The key comparison must be implemented using the `===` operator.
|
||||
Since Access is a behaviour, it can be implemented to key-value
|
||||
data structures. Access requires the key comparison to be
|
||||
implemented using the `===` operator.
|
||||
|
||||
## Field-based lookups
|
||||
|
||||
The Access syntax (`foo[bar]`) cannot be used to access fields in
|
||||
structs. That's by design, as Access is meant to be used for
|
||||
dynamic key-value structures, like maps and keywords, and not
|
||||
by static ones like structs.
|
||||
|
||||
However Elixir already provides a field-based lookup for structs.
|
||||
Imagine a struct named `User` with name and age fields. The
|
||||
following would raise:
|
||||
|
||||
user = %User{name: "john"}
|
||||
user[:name]
|
||||
** (UndefinedFunctionError) undefined function User.fetch/2
|
||||
(User does not implement the Access behaviour)
|
||||
|
||||
Structs instead use the `user.name` syntax:
|
||||
|
||||
user.name
|
||||
#=> "john"
|
||||
|
||||
The same `user.name` syntax can also be used by `Kernel.put_in/2`
|
||||
to for updating structs fields:
|
||||
|
||||
put_in user.name, "mary"
|
||||
%User{name: "mary"}
|
||||
|
||||
Differently from `user[:name]`, `user.name` cannot be extended by
|
||||
the developers, and will be always restricted to only maps and
|
||||
structs.
|
||||
|
||||
Summing up:
|
||||
|
||||
* `user[:name]` is used by dynamic structures, is extensible and
|
||||
does not raise on missing keys
|
||||
* `user.name` is used by static structures, it is not extensible
|
||||
and it will raise on missing keys
|
||||
|
||||
"""
|
||||
|
||||
@type t :: list | map | nil
|
||||
@@ -39,6 +94,20 @@ defmodule Access do
|
||||
@callback fetch(t, key) :: {:ok, value} | :error
|
||||
@callback get_and_update(t, key, (value -> {value, value})) :: {value, t}
|
||||
|
||||
defmacrop raise_undefined_behaviour(e, struct, top) do
|
||||
quote do
|
||||
stacktrace = System.stacktrace
|
||||
e =
|
||||
case stacktrace do
|
||||
[unquote(top)|_] ->
|
||||
%{unquote(e) | reason: "#{inspect unquote(struct)} does not implement the Access behaviour"}
|
||||
_ ->
|
||||
unquote(e)
|
||||
end
|
||||
reraise e, stacktrace
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the container's value for the given key.
|
||||
"""
|
||||
@@ -47,19 +116,27 @@ defmodule Access do
|
||||
|
||||
def fetch(%{__struct__: struct} = container, key) do
|
||||
struct.fetch(container, key)
|
||||
rescue
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :fetch, [^container, ^key], _}
|
||||
end
|
||||
|
||||
def fetch(%{} = map, key) do
|
||||
:maps.find(key, map)
|
||||
end
|
||||
|
||||
def fetch(list, key) when is_list(list) do
|
||||
def fetch(list, key) when is_list(list) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, list) do
|
||||
{^key, value} -> {:ok, value}
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(list, key) when is_list(list) do
|
||||
raise ArgumentError,
|
||||
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
|
||||
end
|
||||
|
||||
def fetch(nil, _key) do
|
||||
:error
|
||||
end
|
||||
@@ -91,6 +168,9 @@ defmodule Access do
|
||||
|
||||
def get_and_update(%{__struct__: struct} = container, key, fun) do
|
||||
struct.get_and_update(container, key, fun)
|
||||
rescue
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :get_and_update, [^container, ^key, ^fun], _}
|
||||
end
|
||||
|
||||
def get_and_update(%{} = map, key, fun) do
|
||||
@@ -112,38 +192,3 @@ defmodule Access do
|
||||
"could not put/update key #{inspect key} on a nil value"
|
||||
end
|
||||
end
|
||||
|
||||
# Callbacks invoked when inlining code for *_in in Kernel.
|
||||
# TODO: Remove me on 1.2
|
||||
defmodule Access.Map do
|
||||
@moduledoc false
|
||||
|
||||
def update!(%{} = map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} ->
|
||||
:maps.put(key, fun.(value), map)
|
||||
:error ->
|
||||
raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def update!(other, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
|
||||
def get_and_update!(%{} = map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} ->
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
:error ->
|
||||
raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update!(other, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
+22
-9
@@ -17,7 +17,7 @@ defmodule Agent do
|
||||
|
||||
defmodule Mix.TasksServer do
|
||||
def start_link do
|
||||
Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
|
||||
Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Checks if the task has already executed"
|
||||
@@ -31,13 +31,13 @@ defmodule Agent do
|
||||
@doc "Marks a task as executed"
|
||||
def put_task(task, project) do
|
||||
item = {task, project}
|
||||
Agent.update(__MODULE__, &Set.put(&1, item))
|
||||
Agent.update(__MODULE__, &MapSet.put(&1, item))
|
||||
end
|
||||
|
||||
@doc "Resets the executed tasks and return the previous list of tasks"
|
||||
@doc "Resets the executed tasks and returns the previous list of tasks"
|
||||
def take_all() do
|
||||
Agent.get_and_update(__MODULE__, fn set ->
|
||||
{Enum.into(set, []), HashSet.new}
|
||||
{Enum.into(set, []), MapSet.new}
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -295,12 +295,25 @@ defmodule Agent do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the agent.
|
||||
Stops the agent with the given `reason`.
|
||||
|
||||
Returns `:ok` if the agent is stopped within the given `timeout`.
|
||||
It returns `:ok` if the server terminates with the given
|
||||
reason, if it terminates with another reason, the call will
|
||||
exit.
|
||||
|
||||
This function keeps OTP semantics regarding error reporting.
|
||||
If the reason is any other than `:normal`, `:shutdown` or
|
||||
`{:shutdown, _}`, an error report will be logged.
|
||||
"""
|
||||
@spec stop(agent, timeout) :: :ok
|
||||
def stop(agent, timeout \\ 5000) do
|
||||
GenServer.call(agent, :stop, timeout)
|
||||
@spec stop(agent, reason :: term, timeout) :: :ok
|
||||
def stop(agent, reason \\ :normal, timeout \\ :infinity) do
|
||||
if is_integer(reason) or reason == :infinity do
|
||||
IO.write :stderr, "warning: Agent.stop(agent, timeout) is deprecated, " <>
|
||||
"please use Agent.stop(agent, :normal, timeout) instead\n" <>
|
||||
Exception.format_stacktrace
|
||||
:gen.stop(agent, :normal, reason)
|
||||
else
|
||||
:gen.stop(agent, reason, timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,10 +23,6 @@ defmodule Agent.Server do
|
||||
{:reply, :ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
{:stop, :normal, :ok, state}
|
||||
end
|
||||
|
||||
def handle_call(msg, from, state) do
|
||||
super(msg, from, state)
|
||||
end
|
||||
@@ -43,17 +39,6 @@ defmodule Agent.Server do
|
||||
{:ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
# There is a race condition if the agent is
|
||||
# restarted too fast and it is registered.
|
||||
try do
|
||||
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
defp initial_call(mfa) do
|
||||
_ = Process.put(:"$initial_call", get_initial_call(mfa))
|
||||
:ok
|
||||
|
||||
@@ -44,9 +44,6 @@ defmodule Application do
|
||||
including new values that are not defined in the environment file (although
|
||||
this should be avoided).
|
||||
|
||||
In the future, we plan to support configuration files which allow
|
||||
developers to configure the environment of their dependencies.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the functions in this module for directly accessing or modifying
|
||||
the environment of other applications (as it may lead to inconsistent
|
||||
@@ -112,6 +109,55 @@ defmodule Application do
|
||||
@type value :: term
|
||||
@type start_type :: :permanent | :transient | :temporary
|
||||
|
||||
@application_keys [:description, :id, :vsn, :modules, :maxP, :maxT, :registered,
|
||||
:included_applications, :applications, :mod, :start_phases]
|
||||
|
||||
@doc """
|
||||
Returns the spec for `app`.
|
||||
|
||||
The following keys are returned:
|
||||
|
||||
* #{Enum.map_join @application_keys, "\n * ", &inspect/1}
|
||||
|
||||
Note the environment is not returned as it can be accessed via
|
||||
`fetch_env/2`. Returns `nil` if the application is not loaded.
|
||||
"""
|
||||
@spec spec(app) :: [{key, value}] | nil
|
||||
def spec(app) do
|
||||
case :application.get_all_key(app) do
|
||||
{:ok, info} -> :lists.keydelete(:env, 1, info)
|
||||
:undefined -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for `key` in `app`'s specification.
|
||||
|
||||
See `spec/1` for the supporte keys. If the given
|
||||
specification parameter does not exist, this function
|
||||
will raise.
|
||||
"""
|
||||
@spec spec(app, key) :: value
|
||||
def spec(app, key) when key in @application_keys do
|
||||
{:ok, value} = :application.get_key(app, key)
|
||||
value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the application for the given module.
|
||||
|
||||
The application is located by analyzing the spec
|
||||
of all loaded applications. Returns `nil` if
|
||||
the module is not listed in any application spec.
|
||||
"""
|
||||
@spec get_application(atom) :: atom | nil
|
||||
def get_application(module) when is_atom(module) do
|
||||
case :application.get_application(module) do
|
||||
{:ok, app} -> app
|
||||
:undefined -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all key-value pairs for `app`.
|
||||
"""
|
||||
|
||||
+253
-108
@@ -98,43 +98,40 @@ defmodule Base do
|
||||
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
|
||||
Enum.each [ {:enc16, :dec16, b16_alphabet},
|
||||
{:enc64, :dec64, b64_alphabet},
|
||||
{:enc32, :dec32, b32_alphabet},
|
||||
{:enc64url, :dec64url, b64url_alphabet},
|
||||
{:enc32hex, :dec32hex, b32hex_alphabet} ], fn({enc, dec, alphabet}) ->
|
||||
Enum.each [{:enc16, :dec16, b16_alphabet},
|
||||
{:enc32, :dec32, b32_alphabet},
|
||||
{:enc64, :dec64, b64_alphabet},
|
||||
{:enc64url, :dec64url, b64url_alphabet},
|
||||
{:enc32hex, :dec32hex, b32hex_alphabet}], fn({enc, dec, alphabet}) ->
|
||||
for {encoding, value} <- alphabet do
|
||||
defp unquote(enc)(unquote(value)), do: unquote(encoding)
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
end
|
||||
defp unquote(dec)(c) do
|
||||
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
|
||||
raise ArgumentError, "non-alphabet digit found: #{inspect <<c>>, binaries: :as_strings} (byte #{c})"
|
||||
end
|
||||
end
|
||||
|
||||
defp encode_case(:upper, func),
|
||||
do: func
|
||||
defp encode_case(:lower, func),
|
||||
do: &to_lower(func.(&1))
|
||||
|
||||
defp decode_case(:upper, func),
|
||||
do: func
|
||||
defp decode_case(:lower, func),
|
||||
do: &func.(from_lower(&1))
|
||||
defp decode_case(:mixed, func),
|
||||
do: &func.(from_mixed(&1))
|
||||
@compile {:inline, from_upper: 1, from_lower: 1, from_mixed: 1,
|
||||
to_lower: 1, to_upper: 1, enc16: 1, dec16: 1,
|
||||
enc32: 1, dec32: 1, enc32hex: 1, dec32hex: 1,
|
||||
enc64: 1, dec64: 1, enc64url: 1, dec64url: 1}
|
||||
|
||||
defp to_lower(char) when char in ?A..?Z,
|
||||
do: char + (?a - ?A)
|
||||
defp to_lower(char),
|
||||
do: char
|
||||
|
||||
defp to_upper(char), do: char
|
||||
|
||||
defp from_upper(char), do: char
|
||||
|
||||
defp from_lower(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_lower(char) when not char in ?A..?Z,
|
||||
do: char
|
||||
defp from_lower(char),
|
||||
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
|
||||
do: raise(ArgumentError, "non-alphabet digit found: \"#{<<char>>}\" (byte #{char})")
|
||||
|
||||
defp from_mixed(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
@@ -160,10 +157,9 @@ defmodule Base do
|
||||
@spec encode16(binary, Keyword.t) :: binary
|
||||
def encode16(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode16(data, encode_case(case, &enc16/1))
|
||||
do_encode16(case, data)
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
@@ -185,9 +181,8 @@ defmodule Base do
|
||||
"""
|
||||
@spec decode16(binary) :: {:ok, binary} | :error
|
||||
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode16(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
|
||||
def decode16(string, opts \\ []) do
|
||||
{:ok, decode16!(string, opts)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -216,9 +211,15 @@ defmodule Base do
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
@spec decode16!(binary, Keyword.t) :: binary
|
||||
def decode16!(string, opts \\ []) when is_binary(string) do
|
||||
def decode16!(string, opts \\ [])
|
||||
|
||||
def decode16!(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode16(string, decode_case(case, &dec16/1))
|
||||
do_decode16(case, string)
|
||||
end
|
||||
|
||||
def decode16!(string, _opts) when is_binary(string) do
|
||||
raise ArgumentError, "odd-length string"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -232,7 +233,7 @@ defmodule Base do
|
||||
"""
|
||||
@spec encode64(binary) :: binary
|
||||
def encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64/1)
|
||||
do_encode64(data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -246,7 +247,7 @@ defmodule Base do
|
||||
"""
|
||||
@spec decode64(binary) :: {:ok, binary} | :error
|
||||
def decode64(string) when is_binary(string) do
|
||||
{:ok, do_decode64(string, &dec64/1)}
|
||||
{:ok, decode64!(string)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -266,8 +267,12 @@ defmodule Base do
|
||||
|
||||
"""
|
||||
@spec decode64!(binary) :: binary
|
||||
def decode64!(string) when is_binary(string) and rem(byte_size(string), 4) == 0 do
|
||||
do_decode64(string)
|
||||
end
|
||||
|
||||
def decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64/1)
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -282,7 +287,7 @@ defmodule Base do
|
||||
"""
|
||||
@spec url_encode64(binary) :: binary
|
||||
def url_encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64url/1)
|
||||
do_encode64url(data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -297,7 +302,7 @@ defmodule Base do
|
||||
"""
|
||||
@spec url_decode64(binary) :: {:ok, binary} | :error
|
||||
def url_decode64(string) when is_binary(string) do
|
||||
{:ok, do_decode64(string, &dec64url/1)}
|
||||
{:ok, url_decode64!(string)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -316,8 +321,12 @@ defmodule Base do
|
||||
|
||||
"""
|
||||
@spec url_decode64!(binary) :: binary
|
||||
def url_decode64!(string) when is_binary(string) and rem(byte_size(string), 4) == 0 do
|
||||
do_decode64url(string)
|
||||
end
|
||||
|
||||
def url_decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64url/1)
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -339,7 +348,7 @@ defmodule Base do
|
||||
@spec encode32(binary, Keyword.t) :: binary
|
||||
def encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32/1))
|
||||
do_encode32(case, data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -364,8 +373,7 @@ defmodule Base do
|
||||
@spec decode32(binary) :: {:ok, binary} | :error
|
||||
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode32(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
|
||||
{:ok, decode32!(string, opts)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -394,9 +402,15 @@ defmodule Base do
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
@spec decode32!(binary, Keyword.t) :: binary
|
||||
def decode32!(string, opts \\ []) do
|
||||
def decode32!(string, opts \\ [])
|
||||
|
||||
def decode32!(string, opts) when is_binary(string) and rem(byte_size(string), 8) == 0 do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32/1))
|
||||
do_decode32(case, string)
|
||||
end
|
||||
|
||||
def decode32!(string, _opts) when is_binary(string) do
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -419,7 +433,7 @@ defmodule Base do
|
||||
@spec hex_encode32(binary, Keyword.t) :: binary
|
||||
def hex_encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32hex/1))
|
||||
do_hex_encode32(case, data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -444,9 +458,8 @@ defmodule Base do
|
||||
"""
|
||||
@spec hex_decode32(binary) :: {:ok, binary} | :error
|
||||
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def hex_decode32(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
|
||||
def hex_decode32(string, opts \\ []) do
|
||||
{:ok, hex_decode32!(string, opts)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -476,117 +489,249 @@ defmodule Base do
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
@spec hex_decode32!(binary, Keyword.t) :: binary
|
||||
def hex_decode32!(string, opts \\ []) when is_binary(string) do
|
||||
def hex_decode32!(string, opts \\ [])
|
||||
|
||||
def hex_decode32!(string, opts) when is_binary(string) and rem(byte_size(string), 8) == 0 do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32hex/1))
|
||||
do_hex_decode32(case, string)
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
defp do_encode16(data, enc) do
|
||||
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
def hex_decode32!(string, _opts) when is_binary(string) do
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_decode16(<<>>, _), do: <<>>
|
||||
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec.(c1)::4, dec.(c2)::4>>
|
||||
defp do_encode16(_, <<>>), do: <<>>
|
||||
defp do_encode16(:upper, data) do
|
||||
for <<c::4 <- data>>, into: <<>>, do: <<enc16(c)::8>>
|
||||
end
|
||||
defp do_encode16(:lower, data) do
|
||||
for <<c::4 <- data>>, into: <<>>, do: <<to_lower(enc16(c))::8>>
|
||||
end
|
||||
|
||||
defp do_decode16(_, <<>>), do: <<>>
|
||||
defp do_decode16(:upper, string) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec16(c1)::4, dec16(c2)::4>>
|
||||
end
|
||||
end
|
||||
defp do_decode16(_, _) do
|
||||
raise ArgumentError, "odd-length string"
|
||||
defp do_decode16(:lower, string) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec16(from_lower(c1))::4, dec16(from_lower(c2))::4>>
|
||||
end
|
||||
end
|
||||
defp do_decode16(:mixed, string) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec16(from_mixed(c1))::4, dec16(from_mixed(c2))::4>>
|
||||
end
|
||||
end
|
||||
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
defp do_encode64(<<>>), do: <<>>
|
||||
defp do_encode64(data) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
|
||||
<<main::binary, enc64(c1)::8, enc64(c2)::8, enc64(bsl(c3, 2))::8, ?=>>
|
||||
<<c1::6, c2::2>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
|
||||
<<main::binary, enc64(c1)::8, enc64(bsl(c2, 4))::8, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
defp do_decode64(<<>>), do: <<>>
|
||||
defp do_decode64(string) do
|
||||
split = byte_size(string) - 4
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
|
||||
<<main::binary, dec64(c1)::6, bsr(dec64(c2), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
|
||||
<<main::binary, dec64(c1)::6, dec64(c2)::6, bsr(dec64(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
|
||||
<<main::binary, dec64(c1)::6, dec64(c2)::6, dec64(c3)::6, dec64(c4)::6>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode64(_, _) do
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
defp do_encode64url(<<>>), do: <<>>
|
||||
defp do_encode64url(data) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64url(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::1>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::3>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
<<main::binary, enc64url(c1)::8, enc64url(c2)::8, enc64url(bsl(c3, 2))::8, ?=>>
|
||||
<<c1::6, c2::2>> ->
|
||||
<<main::binary, enc64url(c1)::8, enc64url(bsl(c2, 4))::8, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
defp do_decode64url(<<>>), do: <<>>
|
||||
defp do_decode64url(string) do
|
||||
split = byte_size(string) - 4
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64url(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
bsr(dec.(c5), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::binary, dec64url(c1)::6, bsr(dec64url(c2), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::binary, dec64url(c1)::6, dec64url(c2)::6, bsr(dec64url(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<main::binary, dec64url(c1)::6, dec64url(c2)::6, dec64url(c3)::6, dec64url(c4)::6>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode32(_, _) do
|
||||
raise ArgumentError, "incorrect padding"
|
||||
|
||||
|
||||
|
||||
defp do_encode32(_, <<>>), do: <<>>
|
||||
|
||||
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
|
||||
defp do_encode32(unquote(case), data) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<unquote(fun)(enc32(c))::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
|
||||
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(c4))::8,
|
||||
unquote(fun)(enc32(c5))::8, unquote(fun)(enc32(c6))::8,
|
||||
unquote(fun)(enc32(bsl(c7, 3)))::8, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
|
||||
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(c4))::8,
|
||||
unquote(fun)(enc32(bsl(c5, 1)))::8, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::1>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
|
||||
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(bsl(c4, 4)))::8,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::3>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(bsl(c2, 2)))::8, ?=, ?=,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode32(_, <<>>), do: <<>>
|
||||
|
||||
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
|
||||
defp do_decode32(unquote(case), string) do
|
||||
split = byte_size(string) - 8
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec32(unquote(fun)(c))::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary, dec32(unquote(fun)(c1))::5,
|
||||
bsr(dec32(unquote(fun)(c2)), 2)::3>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
|
||||
dec32(unquote(fun)(c3))::5, bsr(dec32(unquote(fun)(c4)), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
|
||||
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
|
||||
bsr(dec32(unquote(fun)(c5)), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<main::binary,
|
||||
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
|
||||
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
|
||||
dec32(unquote(fun)(c5))::5, dec32(unquote(fun)(c6))::5,
|
||||
bsr(dec32(unquote(fun)(c7)), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<main::binary,
|
||||
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
|
||||
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
|
||||
dec32(unquote(fun)(c5))::5, dec32(unquote(fun)(c6))::5,
|
||||
dec32(unquote(fun)(c7))::5, dec32(unquote(fun)(c8))::5>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_hex_encode32(_, <<>>), do: <<>>
|
||||
|
||||
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
|
||||
defp do_hex_encode32(unquote(case), data) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<unquote(fun)(enc32hex(c))::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
|
||||
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(c4))::8,
|
||||
unquote(fun)(enc32hex(c5))::8, unquote(fun)(enc32hex(c6))::8,
|
||||
unquote(fun)(enc32hex(bsl(c7, 3)))::8, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
|
||||
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(c4))::8,
|
||||
unquote(fun)(enc32hex(bsl(c5, 1)))::8, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::1>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
|
||||
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(bsl(c4, 4)))::8,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::3>> ->
|
||||
<<main::binary,
|
||||
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(bsl(c2, 2)))::8, ?=, ?=,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_hex_decode32(_, <<>>), do: <<>>
|
||||
|
||||
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
|
||||
defp do_hex_decode32(unquote(case), string) do
|
||||
split = byte_size(string) - 8
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec32hex(unquote(fun)(c))::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary, dec32hex(unquote(fun)(c1))::5,
|
||||
bsr(dec32hex(unquote(fun)(c2)), 2)::3>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
|
||||
dec32hex(unquote(fun)(c3))::5, bsr(dec32hex(unquote(fun)(c4)), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
|
||||
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
|
||||
bsr(dec32hex(unquote(fun)(c5)), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<main::binary,
|
||||
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
|
||||
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
|
||||
dec32hex(unquote(fun)(c5))::5, dec32hex(unquote(fun)(c6))::5,
|
||||
bsr(dec32hex(unquote(fun)(c7)), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<main::binary,
|
||||
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
|
||||
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
|
||||
dec32hex(unquote(fun)(c5))::5, dec32hex(unquote(fun)(c6))::5,
|
||||
dec32hex(unquote(fun)(c7))::5, dec32hex(unquote(fun)(c8))::5>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
This module provides macro-based operators that perform calculations
|
||||
on (sets of) bits. In general, you should `use` the Bitwise module
|
||||
as a whole:
|
||||
on (sets of) bits.
|
||||
|
||||
In general, you should `use` the Bitwise module as a whole:
|
||||
|
||||
iex> use Bitwise
|
||||
iex> bnot 1
|
||||
|
||||
+12
-10
@@ -374,7 +374,8 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
Code.compiler_options
|
||||
#=> [debug_info: true, docs: true, warnings_as_errors: false]
|
||||
#=> %{debug_info: true, docs: true,
|
||||
warnings_as_errors: false, ignore_module_conflict: false}
|
||||
|
||||
"""
|
||||
def compiler_options do
|
||||
@@ -421,17 +422,18 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
Code.compiler_options(debug_info: true)
|
||||
#=> [debug_info: true, docs: true, warnings_as_errors: false]
|
||||
#=> %{debug_info: true, docs: true,
|
||||
warnings_as_errors: false, ignore_module_conflict: false}
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
{opts, bad} = Keyword.split(opts, available_compiler_options)
|
||||
if bad != [] do
|
||||
bad = bad |> Keyword.keys |> Enum.join(", ")
|
||||
raise ArgumentError, message: "unknown compiler options: #{bad}"
|
||||
end
|
||||
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
|
||||
:elixir_config.update :compiler_options, update
|
||||
available = available_compiler_options()
|
||||
|
||||
for {k, _} <- opts,
|
||||
not k in available,
|
||||
do: raise "unknown compiler options: #{k}"
|
||||
|
||||
:elixir_config.update :compiler_options, &Enum.into(opts, &1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -602,7 +604,7 @@ defmodule Code do
|
||||
"Converts an atom to a char list."
|
||||
|
||||
# Module doesn't exist
|
||||
iex> docs = Code.get_docs(ModuleNotGood, :all)
|
||||
iex> Code.get_docs(ModuleNotGood, :all)
|
||||
nil
|
||||
|
||||
"""
|
||||
|
||||
+11
-504
@@ -1,118 +1,20 @@
|
||||
defmodule Dict do
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries.
|
||||
WARNING: this module is deprecated.
|
||||
|
||||
It also provides functions that redirect to the underlying
|
||||
Dict, allowing a developer to work with different Dict
|
||||
implementations using one API.
|
||||
|
||||
To create a new dict, use the `new` functions defined
|
||||
by each dict type:
|
||||
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
In the examples below, `dict_impl` means a specific
|
||||
`Dict` implementation, for example `HashDict` or `Map`.
|
||||
|
||||
## Warning
|
||||
|
||||
Do not use this module if you expect a certain `Dict`
|
||||
implementation. For example, if you are working with
|
||||
maps and you don't need polymorphism, it is preferrable
|
||||
to use the `Map` module instead of the `Dict` one.
|
||||
|
||||
## Protocols
|
||||
|
||||
Besides implementing the functions in this module, all
|
||||
dictionaries are required to implement the `Access`
|
||||
protocol:
|
||||
|
||||
iex> dict = dict_impl.new
|
||||
iex> dict = Dict.put(dict, :hello, :world)
|
||||
iex> dict[:hello]
|
||||
:world
|
||||
|
||||
As well as the `Enumerable` and `Collectable` protocols.
|
||||
|
||||
## Match
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator.
|
||||
|
||||
## Default implementation
|
||||
|
||||
Default implementations for some functions in the `Dict` module
|
||||
are provided via `use Dict`.
|
||||
|
||||
For example:
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict
|
||||
|
||||
# implement required functions (see below)
|
||||
# override default implementations if optimization
|
||||
# is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `get_lazy/3`
|
||||
* `get_and_update/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `pop_lazy/3`
|
||||
* `put_new/3`
|
||||
* `put_new_lazy/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide
|
||||
your own implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
# ...
|
||||
end
|
||||
|
||||
defmodule MyTests do
|
||||
use ExUnit.Case
|
||||
doctest Dict
|
||||
defp dict_impl, do: MyDict
|
||||
end
|
||||
If you need a general dictionary, use the `Map` module.
|
||||
If you need to manipulate keyword lists, use `Keyword`.
|
||||
|
||||
To convert maps into keywords and vice-versa, use the
|
||||
`new` function in the respective modules.
|
||||
"""
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type t :: list | map
|
||||
|
||||
# TODO: Remove callbacks on 1.3
|
||||
# TODO: Deprecate every function on 1.3
|
||||
@callback new :: t
|
||||
@callback delete(t, key) :: t
|
||||
@callback drop(t, Enum.t) :: t
|
||||
@@ -145,6 +47,9 @@ defmodule Dict do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
%{file: file, line: line} = __CALLER__
|
||||
:elixir_errors.warn(line, file, "the Dict module is deprecated")
|
||||
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
@@ -325,276 +230,71 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all keys in `dict`.
|
||||
The keys are not guaranteed to be in any order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(dict))
|
||||
[:a, :b]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(dict) do
|
||||
target(dict).keys(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all values in `dict`.
|
||||
The values are not guaranteed to be in any order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(dict))
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(dict) do
|
||||
target(dict).values(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(dict)
|
||||
2
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(dict) do
|
||||
target(dict).size(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the given `key` exists in the given `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(dict, :a)
|
||||
true
|
||||
iex> Dict.has_key?(dict, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(dict, key) do
|
||||
target(dict).has_key?(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, returns `default` (or `nil` if not provided).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
iex> Dict.get(dict, :b)
|
||||
nil
|
||||
iex> Dict.get(dict, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default \\ nil) do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, it lazily evaluates `fun` and returns its result.
|
||||
|
||||
This is useful if the default value is very expensive to calculate or
|
||||
generally difficult to set-up and tear-down again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> :result
|
||||
...> end
|
||||
iex> Dict.get_lazy(dict, :a, fun)
|
||||
1
|
||||
iex> Dict.get_lazy(dict, :b, fun)
|
||||
:result
|
||||
|
||||
"""
|
||||
@spec get_lazy(t, key, (() -> value)) :: value
|
||||
def get_lazy(dict, key, fun) do
|
||||
target(dict).get_lazy(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a value from `dict` and updates the value at `key` in one pass.
|
||||
|
||||
This `fun` argument receives the value of `key` in `dict` (or `nil` if `key`
|
||||
is not present) and must return a two-elements tuple: the "get" value (the
|
||||
value retrieved from the dict which can be operated on before being returned)
|
||||
and the new value to be stored under `key` in `dict`.
|
||||
|
||||
The returned value is a tuple with the "get" value returned by `fun` and a new
|
||||
dict with the updated value under `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {get, new_dict} = Dict.get_and_update dict, :a, fn(current_value) ->
|
||||
...> {current_value + 1, "foo"}
|
||||
...> end
|
||||
iex> get
|
||||
2
|
||||
iex> Dict.get(new_dict, :a)
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
|
||||
def get_and_update(dict, key, fun) do
|
||||
target(dict).get_and_update(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `{:ok, value}` associated with `key` in `dict`.
|
||||
If `dict` does not contain `key`, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(dict, :a)
|
||||
{:ok, 1}
|
||||
iex> Dict.fetch(dict, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(dict, key) do
|
||||
target(dict).fetch(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, it raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(dict, key) do
|
||||
target(dict).fetch!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stores the given `value` under `key` in `dict`.
|
||||
If `dict` already has `key`, the stored value is replaced by the new one.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
def put(dict, key, val) do
|
||||
target(dict).put(dict, key, val)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key` in `dict` unless `key` is already present.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put_new(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
def put_new(dict, key, val) do
|
||||
target(dict).put_new(dict, key, val)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates `fun` and puts the result under `key` in `dict` unless `key`
|
||||
is already present.
|
||||
|
||||
This is useful if the value is very expensive to calculate or generally
|
||||
difficult to set-up and tear-down again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> 3
|
||||
...> end
|
||||
iex> dict = Dict.put_new_lazy(dict, :a, fun)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
iex> dict = Dict.put_new_lazy(dict, :c, fun)
|
||||
iex> Dict.get(dict, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec put_new_lazy(t, key, (() -> value)) :: t
|
||||
def put_new_lazy(dict, key, fun) do
|
||||
target(dict).put_new_lazy(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the entry stored under the given `key` from `dict`.
|
||||
If `dict` does not contain `key`, returns the dictionary unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.delete(dict, :a)
|
||||
iex> Dict.get(dict, :a)
|
||||
nil
|
||||
|
||||
iex> dict = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(dict, :a) == dict
|
||||
true
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(dict, key) do
|
||||
target(dict).delete(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the dict `dict2` into dict `dict1`.
|
||||
|
||||
If one of the `dict2` entries is found in `dict1`, the
|
||||
conflicting entries in `dict2` have higher precedence.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
type. Each dict implementation also provides a `merge` function,
|
||||
but they can only merge dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> dict = Dict.merge(dict1, dict2)
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
@@ -607,27 +307,6 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the dict `dict2` into dict `dict1`.
|
||||
|
||||
If one of the `dict2` entries is found in `dict1`, the function
|
||||
will be invoked to resolve the conflict.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
type. Each dict implementation also provides a `merge` function,
|
||||
but they can only merge dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(dict1, dict2, fun) do
|
||||
target1 = target(dict1)
|
||||
@@ -646,207 +325,46 @@ defmodule Dict do
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict` as
|
||||
well as the `dict` without `key`.
|
||||
|
||||
If `key` is not present in `dict`, then the `dict` will
|
||||
be returned unmodified.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, dict} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{1, []}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, dict} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{nil, [a: 1]}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, dict} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{3, [a: 1]}
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default \\ nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict` as
|
||||
well as the `dict` without `key`.
|
||||
|
||||
If `key` is not present in `dict`, then the `dict` will
|
||||
be returned unmodified, and it will lazily evaluate `fun`
|
||||
and return its result instead of the missing value.
|
||||
|
||||
This is useful if the default value is very expensive to calculate or
|
||||
generally difficult to set-up and tear-down again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> :result
|
||||
...> end
|
||||
iex> {v, dict} = Dict.pop_lazy dict, :a, fun
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{1, []}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> :result
|
||||
...> end
|
||||
iex> {v, dict} = Dict.pop_lazy dict, :b, fun
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{:result, [a: 1]}
|
||||
|
||||
"""
|
||||
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
|
||||
def pop_lazy(dict, key, fun) do
|
||||
target(dict).pop_lazy(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a value in `dict` by calling `fun` on the value to get a new
|
||||
value. An exception is generated if `key` is not present in the dict.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(dict, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@spec update!(t, key, (value -> value)) :: t
|
||||
def update!(dict, key, fun) do
|
||||
target(dict).update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a value in `dict` by calling `fun` on the value to get a new value. If
|
||||
`key` is not present in `dict` then `initial` will be stored as the first
|
||||
value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(dict, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
def update(dict, key, initial, fun) do
|
||||
target(dict).update(dict, key, initial, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a tuple of two dicts, where the first dict contains only
|
||||
entries from `dict` with keys in `keys`, and the second dict
|
||||
contains only entries from `dict` with keys not in `keys`.
|
||||
|
||||
All non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
|
||||
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> dict = Enum.into([], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
|
||||
iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
|
||||
{[], []}
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
|
||||
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
|
||||
{[a: 1, b: 2], []}
|
||||
|
||||
"""
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where the given `keys` are removed from `dict`.
|
||||
All non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.drop(dict, [:a, :c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[b: 2]
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.drop(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec drop(t, [key]) :: t
|
||||
def drop(dict, keys) do
|
||||
target(dict).drop(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are included.
|
||||
|
||||
All non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.take(dict, [:a, :c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[a: 1]
|
||||
iex> dict = Dict.take(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, [key]) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
target(dict).empty(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two dicts are equal using `===`.
|
||||
|
||||
Notice this function is polymorphic as it compares dicts of any
|
||||
type. Each dict implementation also provides an `equal?` function,
|
||||
but they can only compare dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
true
|
||||
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = []
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
@@ -860,7 +378,7 @@ defmodule Dict do
|
||||
Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
|
||||
@@ -869,17 +387,6 @@ defmodule Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of key-value pairs stored in `dict`.
|
||||
No particular order is enforced.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = dict_impl.new
|
||||
iex> dict = Dict.put(dict, :a, 1)
|
||||
iex> Dict.to_list(dict)
|
||||
[a: 1]
|
||||
"""
|
||||
@spec to_list(t) :: list
|
||||
def to_list(dict) do
|
||||
target(dict).to_list(dict)
|
||||
|
||||
+718
-581
File diff suppressed because it is too large
Load Diff
+28
-13
@@ -42,7 +42,7 @@ defmodule Exception do
|
||||
def exception?(_), do: false
|
||||
|
||||
@doc """
|
||||
Gets the message for an exception.
|
||||
Gets the message for an `exception`.
|
||||
"""
|
||||
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
|
||||
try do
|
||||
@@ -95,7 +95,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats any throw, error and exit.
|
||||
Normalizes and formats any throw/error/exit.
|
||||
|
||||
The message is formatted and displayed in the same
|
||||
format as used by Elixir's CLI.
|
||||
@@ -128,7 +128,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats throw/errors/exits and stacktrace.
|
||||
Normalizes and formats throw/errors/exits and stacktraces.
|
||||
|
||||
It relies on `format_banner/3` and `format_stacktrace/1`
|
||||
to generate the final format.
|
||||
@@ -155,7 +155,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats an exit, returns a string.
|
||||
Formats an exit. It returns a string.
|
||||
|
||||
Often there are errors/exceptions inside exits. Exits are often
|
||||
wrapped by the caller and provide stacktraces too. This function
|
||||
@@ -365,9 +365,10 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
|
||||
:undefined -> ""
|
||||
if app = Application.get_application(module) do
|
||||
"(" <> Atom.to_string(app) <> ") "
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -394,7 +395,7 @@ defmodule Exception do
|
||||
|
||||
## Examples
|
||||
|
||||
Exception.format_fa(fn -> end, 1)
|
||||
Exception.format_fa(fn -> nil end, 1)
|
||||
#=> "#Function<...>/1"
|
||||
|
||||
"""
|
||||
@@ -447,7 +448,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
Formats the given `file` and `line` as shown in stacktraces.
|
||||
If any of the values are `nil`, they are omitted.
|
||||
|
||||
## Examples
|
||||
@@ -626,19 +627,23 @@ defmodule UndefinedFunctionError do
|
||||
end
|
||||
|
||||
def message(%{reason: :"module could not be loaded", module: module, function: function, arity: arity}) do
|
||||
"undefined function: " <> Exception.format_mfa(module, function, arity) <>
|
||||
"undefined function " <> Exception.format_mfa(module, function, arity) <>
|
||||
" (module #{inspect module} is not available)"
|
||||
end
|
||||
|
||||
def message(%{reason: :"function not exported", module: module, function: function, arity: arity}) do
|
||||
"undefined function: " <> Exception.format_mfa(module, function, arity)
|
||||
"undefined function " <> Exception.format_mfa(module, function, arity)
|
||||
end
|
||||
|
||||
def message(%{reason: :"function not available", module: module, function: function, arity: arity}) do
|
||||
"nil." <> fa = Exception.format_mfa(nil, function, arity)
|
||||
"undefined function: " <> Exception.format_mfa(module, function, arity) <>
|
||||
"undefined function " <> Exception.format_mfa(module, function, arity) <>
|
||||
" (function #{fa} is not available)"
|
||||
end
|
||||
|
||||
def message(%{reason: reason, module: module, function: function, arity: arity}) do
|
||||
"undefined function " <> Exception.format_mfa(module, function, arity) <> " (#{reason})"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule FunctionClauseError do
|
||||
@@ -703,9 +708,13 @@ defmodule UnicodeConversionError do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
defp detail([h|_]) when is_integer(h) do
|
||||
"code point #{h}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
detail(h)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Enum.OutOfBoundsError do
|
||||
@@ -791,6 +800,8 @@ defmodule ErlangError do
|
||||
def normalize({:badkey, key}, stacktrace) do
|
||||
term =
|
||||
case stacktrace || :erlang.get_stacktrace do
|
||||
[{Map, :get_and_update!, [map, _, _], _}|_] -> map
|
||||
[{Map, :update!, [map, _, _], _}|_] -> map
|
||||
[{:maps, :update, [_, _, map], _}|_] -> map
|
||||
[{:maps, :get, [_, map], _}|_] -> map
|
||||
_ -> nil
|
||||
@@ -798,6 +809,10 @@ defmodule ErlangError do
|
||||
%KeyError{key: key, term: term}
|
||||
end
|
||||
|
||||
def normalize({:badkey, key, map}, _stacktrace) do
|
||||
%KeyError{key: key, term: map}
|
||||
end
|
||||
|
||||
def normalize({:case_clause, term}, _stacktrace) do
|
||||
%CaseClauseError{term: term}
|
||||
end
|
||||
|
||||
+45
-44
@@ -3,35 +3,35 @@ defmodule File do
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `open/2`,
|
||||
to interact with files or IO devices, like `open/2`,
|
||||
`copy/3` and others. This module also provides higher
|
||||
level functions that work with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `cp/3` and remove files and directories recursively
|
||||
via `rm_rf/1`
|
||||
via `rm_rf/1`.
|
||||
|
||||
## Encoding
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
in the `IO` module. By default, a file is opened in binary mode,
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file, then the slower `IO.read/2` and
|
||||
`IO.write/2` functions must be used as they are responsible for
|
||||
doing the proper conversions and data guarantees.
|
||||
doing the proper conversions and providing the proper data guarantees.
|
||||
|
||||
Note that filenames when given as char lists in Elixir are
|
||||
always treated as UTF-8. In particular, we expect that the
|
||||
shell and the operating system are configured to use UTF8
|
||||
encoding. Binary filenames are considering raw and passed
|
||||
shell and the operating system are configured to use UTF-8
|
||||
encoding. Binary filenames are considered raw and passed
|
||||
to the OS as is.
|
||||
|
||||
## API
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{:ok, result}` in case of success, `{:error, reason}`
|
||||
otherwise. Those function are also followed by a variant
|
||||
that ends with `!` which returns the result (without the
|
||||
otherwise. Those functions also have a variant
|
||||
that ends with `!` which returns the result (instead of the
|
||||
`{:ok, result}` tuple) in case of success or raises an
|
||||
exception in case it fails. For example:
|
||||
|
||||
@@ -55,7 +55,7 @@ defmodule File do
|
||||
## Processes and raw files
|
||||
|
||||
Every time a file is opened, Elixir spawns a new process. Writing
|
||||
to a file is equivalent to sending messages to that process that
|
||||
to a file is equivalent to sending messages to the process that
|
||||
writes to the file descriptor.
|
||||
|
||||
This means files can be passed between nodes and message passing
|
||||
@@ -63,7 +63,7 @@ defmodule File do
|
||||
|
||||
However, you may not always want to pay the price for this abstraction.
|
||||
In such cases, a file can be opened in `:raw` mode. The options `:read_ahead`
|
||||
and `:delayed_write` are also useful when operating large files or
|
||||
and `:delayed_write` are also useful when operating on large files or
|
||||
working with files in tight loops.
|
||||
|
||||
Check [`:file.open/2`](http://www.erlang.org/doc/man/file.html#open-2) for more information
|
||||
@@ -232,7 +232,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns binary with the contents of the given filename or raises
|
||||
Returns a binary with the contents of the given filename or raises
|
||||
`File.Error` if an error occurs.
|
||||
"""
|
||||
@spec read!(Path.t) :: binary | no_return
|
||||
@@ -261,7 +261,8 @@ defmodule File do
|
||||
The values for `:time` can be:
|
||||
|
||||
* `:universal` - returns a `{date, time}` tuple in UTC (default)
|
||||
* `:local` - returns a `{date, time}` tuple using the machine time
|
||||
* `:local` - returns a `{date, time}` tuple using the same time zone as the
|
||||
machine
|
||||
* `:posix` - returns the time as integer seconds since epoch
|
||||
|
||||
"""
|
||||
@@ -291,8 +292,8 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the `path`. If the file is a symlink sets
|
||||
the `type` to `:symlink` and returns `File.Stat` for the link. For any
|
||||
Returns information about the `path`. If the file is a symlink, sets
|
||||
the `type` to `:symlink` and returns a `File.Stat` struct for the link. For any
|
||||
other file, returns exactly the same values as `stat/2`.
|
||||
|
||||
For more details, see [`:file.read_link_info/2`](http://www.erlang.org/doc/man/file.html#read_link_info-2).
|
||||
@@ -322,7 +323,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `lstat/2` but returns the `File.Stat` directly and
|
||||
Same as `lstat/2` but returns the `File.Stat` struct directly and
|
||||
throws `File.Error` if an error is returned.
|
||||
"""
|
||||
@spec lstat!(Path.t, stat_options) :: File.Stat.t | no_return
|
||||
@@ -363,7 +364,7 @@ defmodule File do
|
||||
Updates modification time (mtime) and access time (atime) of
|
||||
the given file.
|
||||
|
||||
File is created if it doesn’t exist. Requires datetime in UTC.
|
||||
The file is created if it doesn’t exist. Requires datetime in UTC.
|
||||
"""
|
||||
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
|
||||
def touch(path, time \\ :calendar.universal_time) do
|
||||
@@ -449,12 +450,12 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renames the `source` file to `destination` file. If can be used to move files
|
||||
Renames the `source` file to `destination` file. It can be used to move files
|
||||
(and directories) between directories. If moving a file, you must fully
|
||||
specify the `destination` filename, it is not sufficient to simply specify
|
||||
it's directory.
|
||||
its directory.
|
||||
|
||||
It returns `:ok` in case of success, returns `{:error, reason}` otherwise
|
||||
It returns `:ok` in case of success, returns `{:error, reason}` otherwise.
|
||||
|
||||
Note: The command `mv` in Unix systems behaves differently depending
|
||||
if `source` is a file and the `destination` is an existing directory.
|
||||
@@ -478,9 +479,9 @@ defmodule File do
|
||||
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return `true` if the existing file
|
||||
should be overwritten, `false` otherwise. It defaults to return `true`.
|
||||
should be overwritten, `false` otherwise. The callback defaults to return `true`.
|
||||
|
||||
It returns `:ok` in case of success, returns
|
||||
The function returns `:ok` in case of success, returns
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
If you want to copy contents from an io device to another device
|
||||
@@ -534,20 +535,20 @@ defmodule File do
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
`true` if the existing file should be overwritten,
|
||||
`false` otherwise. It defaults to return `true`.
|
||||
`false` otherwise. The callback defaults to return `true`.
|
||||
|
||||
If a directory already exists in the destination
|
||||
where a file is meant to be (or otherwise), this
|
||||
where a file is meant to be (or vice versa), this
|
||||
function will fail.
|
||||
|
||||
This function may fail while copying files,
|
||||
in such cases, it will leave the destination
|
||||
directory in a dirty state, where already
|
||||
copied files won't be removed.
|
||||
directory in a dirty state, where file which have already been copied
|
||||
won't be removed.
|
||||
|
||||
It returns `{:ok, files_and_directories}` in case of
|
||||
success with all files and directories copied in no
|
||||
specific order, `{:error, reason, file}` otherwise.
|
||||
The function returns `{:ok, files_and_directories}` in case of
|
||||
success, `files_and_directories` lists all files and directories copied in no
|
||||
specific order. It returns `{:error, reason, file}` otherwise.
|
||||
|
||||
Note: The command `cp` in Unix systems behaves differently
|
||||
depending if `destination` is an existing directory or not.
|
||||
@@ -682,13 +683,13 @@ defmodule File do
|
||||
and a new process is spawned to write to the file. For this reason, if you are
|
||||
doing multiple writes in a loop, opening the file via `File.open/2` and using
|
||||
the functions in `IO` to write to the file will yield much better performance
|
||||
then calling this function multiple times.
|
||||
than calling this function multiple times.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* `:enoent` - a component of the file name does not exist
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
on some platforms, `:enoent` is returned instead
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:eacces` - missing permission for writing the file or searching one of
|
||||
the parent directories
|
||||
@@ -727,7 +728,7 @@ defmodule File do
|
||||
* `:eacces` - missing permission for the file or one of its parents
|
||||
* `:eperm` - the file is a directory and user is not super-user
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
on some platforms, `:enoent` is returned instead
|
||||
* `:einval` - filename had an improper type, such as tuple
|
||||
|
||||
## Examples
|
||||
@@ -921,7 +922,7 @@ defmodule File do
|
||||
Opens the given `path` according to the given list of modes.
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
in the `IO` module. By default, a file is opened in binary mode,
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions from
|
||||
@@ -955,10 +956,10 @@ defmodule File do
|
||||
|
||||
* `:utf8` - this option denotes how data is actually stored in the disk
|
||||
file and makes the file perform automatic translation of characters to
|
||||
and from utf-8.
|
||||
and from UTF-8.
|
||||
|
||||
If data is sent to a file in a format that cannot be converted to the
|
||||
utf-8 or if data is read by a function that returns data in a format that
|
||||
UTF-8 or if data is read by a function that returns data in a format that
|
||||
cannot cope with the character range of the data, an error occurs and the
|
||||
file will be closed.
|
||||
|
||||
@@ -999,9 +1000,9 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `open/2` but expects a function as last argument.
|
||||
Similar to `open/2` but expects a function as its last argument.
|
||||
|
||||
The file is opened, given to the function as argument and
|
||||
The file is opened, given to the function as an argument and
|
||||
automatically closed after the function returns, regardless
|
||||
if there was an error when executing the function.
|
||||
|
||||
@@ -1065,7 +1066,7 @@ defmodule File do
|
||||
Gets the current working directory.
|
||||
|
||||
In rare circumstances, this function can fail on Unix. It may happen
|
||||
if read permission does not exist for the parent directories of the
|
||||
if read permissions do not exist for the parent directories of the
|
||||
current directory. For this reason, returns `{:ok, cwd}` in case
|
||||
of success, `{:error, reason}` otherwise.
|
||||
"""
|
||||
@@ -1123,8 +1124,8 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Changes the current directory to the given `path`,
|
||||
executes the given function and then revert back
|
||||
to the previous path regardless if there is an exception.
|
||||
executes the given function and then reverts back
|
||||
to the previous path regardless of whether there is an exception.
|
||||
|
||||
Raises an error if retrieving or changing the current
|
||||
directory fails.
|
||||
@@ -1141,7 +1142,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns list of files in the given directory.
|
||||
Returns the list of files in the given directory.
|
||||
|
||||
It returns `{:ok, [files]}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
@@ -1191,9 +1192,9 @@ defmodule File do
|
||||
streaming, by `:line` (default) or by a given number of bytes.
|
||||
|
||||
Operating the stream can fail on open for the same reasons as
|
||||
`File.open!/2`. Note that the file is automatically opened only and
|
||||
every time streaming begins. There is no need to pass `:read` and
|
||||
`:write` modes, as those are automatically set by Elixir.
|
||||
`File.open!/2`. Note that the file is automatically opened each time streaming
|
||||
begins. There is no need to pass `:read` and `:write` modes, as those are
|
||||
automatically set by Elixir.
|
||||
|
||||
## Raw files
|
||||
|
||||
@@ -1267,7 +1268,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the user group given by the group id `gid`
|
||||
Changes the group given by the group id `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@ require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
A struct that holds file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
@@ -41,14 +41,15 @@ defmodule File.Stat do
|
||||
* `inode` - gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
|
||||
* `uid` - indicates the owner of the file.
|
||||
* `uid` - indicates the owner of the file. Will be zero for non-Unix file
|
||||
systems.
|
||||
|
||||
* `gid` - gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
* `gid` - indicates the group that owns the file. Will be zero for
|
||||
non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
`:universal`, or `:posix`. Default is `:universal`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
|
||||
+12
-8
@@ -9,7 +9,11 @@ defmodule Float do
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{float, remainder_of_binary}`;
|
||||
otherwise, `:error`.
|
||||
when the binary cannot be coerced into a valid float, the atom `:error` is
|
||||
returned.
|
||||
|
||||
If the size of float exceeds the maximum size of `1.7976931348623157e+308`,
|
||||
the `ArgumentError` exception is raised.
|
||||
|
||||
If a float formatted string wants to be directly converted to a float,
|
||||
`String.to_float/1` can be used instead.
|
||||
@@ -51,16 +55,16 @@ defmodule Float do
|
||||
defp parse_unsigned(binary) when is_binary(binary), do:
|
||||
:error
|
||||
|
||||
defp parse_unsigned(<<digit, rest :: binary>>, dot?, e?, acc) when digit in ?0..?9, do:
|
||||
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9, do:
|
||||
parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
|
||||
|
||||
defp parse_unsigned(<<?., digit, rest :: binary>>, false, false, acc) when digit in ?0..?9, do:
|
||||
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9, do:
|
||||
parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
|
||||
|
||||
defp parse_unsigned(<<exp_marker, digit, rest :: binary>>, dot?, false, acc) when exp_marker in 'eE' and digit in ?0..?9, do:
|
||||
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and digit in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
|
||||
|
||||
defp parse_unsigned(<<exp_marker, sign, digit, rest :: binary>>, dot?, false, acc) when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9, do:
|
||||
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
|
||||
|
||||
defp parse_unsigned(rest, dot?, _e?, acc), do:
|
||||
@@ -86,7 +90,7 @@ defmodule Float do
|
||||
iex> Float.floor(-56.5)
|
||||
-57.0
|
||||
|
||||
iex> Float.floor(34.253, 2)
|
||||
iex> Float.floor(34.259, 2)
|
||||
34.25
|
||||
|
||||
"""
|
||||
@@ -100,7 +104,7 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer greater than or equal to `num`.
|
||||
Rounds a float to the smallest integer greater than or equal to `num`.
|
||||
|
||||
`ceil/2` also accepts a precision to round a floating point value down
|
||||
to an arbitrary number of fractional digits (between 0 and 15).
|
||||
@@ -116,7 +120,7 @@ defmodule Float do
|
||||
iex> Float.ceil(-56.5)
|
||||
-56.0
|
||||
|
||||
iex> Float.ceil(34.253, 2)
|
||||
iex> Float.ceil(34.251, 2)
|
||||
34.26
|
||||
|
||||
"""
|
||||
|
||||
+25
-17
@@ -22,6 +22,7 @@ defmodule GenEvent do
|
||||
As an example, let's have a GenEvent that accumulates messages until
|
||||
they are collected by an explicit call.
|
||||
|
||||
# Define a Event Handler
|
||||
defmodule LoggerHandler do
|
||||
use GenEvent
|
||||
|
||||
@@ -36,17 +37,21 @@ defmodule GenEvent do
|
||||
end
|
||||
end
|
||||
|
||||
# Start a new event manager.
|
||||
{:ok, pid} = GenEvent.start_link([])
|
||||
|
||||
# Attach an event handler to the event manager.
|
||||
GenEvent.add_handler(pid, LoggerHandler, [])
|
||||
#=> :ok
|
||||
|
||||
# Send some events to the event manager.
|
||||
GenEvent.notify(pid, {:log, 1})
|
||||
#=> :ok
|
||||
|
||||
GenEvent.notify(pid, {:log, 2})
|
||||
#=> :ok
|
||||
|
||||
# Call functions on specific handlers in the manager.
|
||||
GenEvent.call(pid, LoggerHandler, :messages)
|
||||
#=> [1, 2]
|
||||
|
||||
@@ -81,7 +86,7 @@ defmodule GenEvent do
|
||||
asynchronously.
|
||||
|
||||
On `GenEvent.sync_notify/2`, the manager acknowledges an event
|
||||
just after it was processed by all event handlers.
|
||||
just after it is processed by all event handlers.
|
||||
|
||||
On `GenEvent.notify/2`, all events are processed asynchronously and
|
||||
there is no ack (which means there is no backpressure).
|
||||
@@ -125,7 +130,7 @@ defmodule GenEvent do
|
||||
too much kool aid" section of the "Learn you some Erlang" link above. Due
|
||||
to those changes, Elixir's GenEvent does not trap exits by default.
|
||||
|
||||
Furthermore, Elixir's also normalizes the `{:error, _}` tuples returned
|
||||
Furthermore, Elixir also normalizes the `{:error, _}` tuples returned
|
||||
by many functions, in order to be more consistent with themselves and
|
||||
the `GenServer` module.
|
||||
"""
|
||||
@@ -290,7 +295,7 @@ defmodule GenEvent do
|
||||
@type manager :: pid | name | {atom, node}
|
||||
|
||||
@typedoc "Supported values for new handlers"
|
||||
@type handler :: atom | {atom, term} | {pid, reference}
|
||||
@type handler :: atom | {atom, term}
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -417,6 +422,10 @@ defmodule GenEvent do
|
||||
|
||||
If the given handler was previously installed at the manager, this
|
||||
function returns `{:error, :already_present}`.
|
||||
|
||||
For installing multiple instances of the same handler, `{Module, id}` instead
|
||||
of `Module` must be used. The handler could be then referenced with
|
||||
`{Module, id}` instead of just `Module`.
|
||||
"""
|
||||
@spec add_handler(manager, handler, term) :: :ok | {:error, term}
|
||||
def add_handler(manager, handler, args) do
|
||||
@@ -607,18 +616,24 @@ defmodule GenEvent do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the event `manager`.
|
||||
Stops the manager with the given `reason`.
|
||||
|
||||
Before terminating, the event manager will call `terminate(:stop, ...)`
|
||||
for each installed event handler.
|
||||
Before terminating, the event manager will call
|
||||
`terminate(:stop, ...)` for each installed event handler.
|
||||
It returns `:ok` if the manager terminates with the given
|
||||
reason, if it terminates with another reason, the call will
|
||||
exit.
|
||||
|
||||
This function keeps OTP semantics regarding error reporting.
|
||||
If the reason is any other than `:normal`, `:shutdown` or
|
||||
`{:shutdown, _}`, an error report will be logged.
|
||||
"""
|
||||
@spec stop(manager) :: :ok
|
||||
def stop(manager) do
|
||||
rpc(manager, :stop)
|
||||
@spec stop(manager, reason :: term, timeout) :: :ok
|
||||
def stop(manager, reason \\ :normal, timeout \\ :infinity) do
|
||||
:gen.stop(manager, reason, timeout)
|
||||
end
|
||||
|
||||
defp rpc(module, cmd) do
|
||||
# TODO: Change the tag on OTP 18
|
||||
{:ok, reply} = :gen.call(module, self(), cmd, :infinity)
|
||||
reply
|
||||
end
|
||||
@@ -727,13 +742,6 @@ defmodule GenEvent do
|
||||
{hib, reply, handlers} = server_swap_handler(handler1, args1, handler2, args2, handlers, mon, name)
|
||||
reply(tag, reply)
|
||||
loop(parent, name, handlers, debug, hib)
|
||||
{_from, tag, :stop} ->
|
||||
try do
|
||||
server_terminate(:normal, parent, handlers, name)
|
||||
catch
|
||||
:exit, :normal -> :ok
|
||||
end
|
||||
reply(tag, :ok)
|
||||
{_from, tag, :which_handlers} ->
|
||||
reply(tag, server_which_handlers(handlers))
|
||||
loop(parent, name, handlers, debug, false)
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule GenEvent.Stream do
|
||||
@moduledoc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
This is a struct returned by `GenEvent.stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule GenServer do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing the server of a client-server relation.
|
||||
|
||||
A GenServer is a process as any other Elixir process and it can be used
|
||||
A GenServer is a process like any other Elixir process and it can be used
|
||||
to keep state, execute code asynchronously and so on. The advantage of using
|
||||
a generic server process (GenServer) implemented using this module is that it
|
||||
will have a standard set of interface functions and include functionality for
|
||||
@@ -76,8 +76,11 @@ defmodule GenServer do
|
||||
term using the functions in the `:global` module.
|
||||
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism alongside a name which can be any term.
|
||||
mechanism and name. The `:via` option expects a module that exports
|
||||
`register_name/2`, `unregister_name/1`, `whereis_name/1` and `send/2`.
|
||||
One such example is the `:global` module which uses these functions
|
||||
for keeping the list of names of processes and their associated pid's
|
||||
that are available globally for a network of Erlang nodes.
|
||||
|
||||
For example, we could start and register our Stack server locally as follows:
|
||||
|
||||
@@ -222,7 +225,7 @@ defmodule GenServer do
|
||||
|
||||
Returning `{:reply, reply, new_state, timeout}` is similar to
|
||||
`{:reply, reply, new_state}` except `handle_info(:timeout, new_state)` will be
|
||||
called after `timeout` milliseconds if no messages are receved.
|
||||
called after `timeout` milliseconds if no messages are received.
|
||||
|
||||
Returning `{:reply, reply, new_state, :hibernate}` is similar to
|
||||
`{:reply, reply, new_state}` except the process is hibernated and will
|
||||
@@ -514,6 +517,23 @@ defmodule GenServer do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the server with the given `reason`.
|
||||
|
||||
The `terminate/2` callback will be invoked before exiting.
|
||||
It returns `:ok` if the server terminates with the given
|
||||
reason, if it terminates with another reason, the call will
|
||||
exit.
|
||||
|
||||
This function keeps OTP semantics regarding error reporting.
|
||||
If the reason is any other than `:normal`, `:shutdown` or
|
||||
`{:shutdown, _}`, an error report will be logged.
|
||||
"""
|
||||
@spec stop(server, reason :: term, timeout) :: :ok
|
||||
def stop(server, reason \\ :normal, timeout \\ :infinity) do
|
||||
:gen.stop(server, reason, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the `server` and waits for its reply.
|
||||
|
||||
@@ -533,7 +553,7 @@ defmodule GenServer do
|
||||
failure and continues running, and the server is just late with the reply,
|
||||
it may arrive at any time later into the caller's message queue. The caller
|
||||
must in this case be prepared for this and discard any such garbage messages
|
||||
that are two element tuples with a reference as the first element.
|
||||
that are two-element tuples with a reference as the first element.
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
defmodule HashDict do
|
||||
@moduledoc """
|
||||
A key-value store.
|
||||
WARNING: this module is deprecated.
|
||||
|
||||
The `HashDict` is represented internally as a struct, therefore
|
||||
`%HashDict{}` can be used whenever there is a need to match
|
||||
on any `HashDict`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Dict` module.
|
||||
|
||||
Implementation-wise, `HashDict` is implemented using tries, which
|
||||
grows in space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
Use the `Map` module instead.
|
||||
"""
|
||||
|
||||
use Dict
|
||||
@@ -236,7 +227,7 @@ end
|
||||
defimpl Collectable, for: HashDict do
|
||||
def into(original) do
|
||||
{original, fn
|
||||
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
|
||||
dict, {:cont, {k, v}} -> HashDict.put(dict, k, v)
|
||||
dict, :done -> dict
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
WARNING: this module is deprecated.
|
||||
|
||||
The `HashSet` is represented internally as a struct, therefore
|
||||
`%HashSet{}` can be used whenever there is a need to match
|
||||
on any `HashSet`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Set` module.
|
||||
|
||||
The `HashSet` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
functions and their APIs, please consult the `Set` module.
|
||||
Use the `MapSet` module instead.
|
||||
"""
|
||||
|
||||
@behaviour Set
|
||||
@@ -29,9 +20,6 @@ defmodule HashSet do
|
||||
@compile :inline_list_funcs
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
%HashSet{}
|
||||
|
||||
+65
-62
@@ -16,20 +16,20 @@ defprotocol Inspect do
|
||||
## Examples
|
||||
|
||||
Many times, inspecting a structure can be implemented in function
|
||||
of existing entities. For example, here is `HashSet`'s `inspect`
|
||||
of existing entities. For example, here is `MapSet`'s `inspect`
|
||||
implementation:
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
defimpl Inspect, for: MapSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashSet<", to_doc(HashSet.to_list(dict), opts), ">"]
|
||||
concat ["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
The `concat` function comes from `Inspect.Algebra` and it
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
it is concatenating the string `"MapSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
|
||||
other string `">"`.
|
||||
@@ -45,10 +45,10 @@ defprotocol Inspect do
|
||||
to a raw representation for printing the structure.
|
||||
|
||||
You can however access the underlying error by invoking the Inspect
|
||||
implementation directly. For example, to test Inspect.HashSet above,
|
||||
implementation directly. For example, to test Inspect.MapSet above,
|
||||
you can invoke it as:
|
||||
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
Inspect.MapSet.inspect(MapSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
|
||||
@@ -88,7 +88,7 @@ defimpl Inspect, for: Atom do
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
|
||||
<<?:, ?", Inspect.BitString.escape(binary, ?")::binary, ?">>
|
||||
end
|
||||
end
|
||||
|
||||
@@ -104,7 +104,7 @@ defimpl Inspect, for: Atom do
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
defp valid_ref_piece?(<<?., h, t::binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
@@ -113,7 +113,7 @@ defimpl Inspect, for: Atom do
|
||||
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
defp valid_atom_identifier?(<<h, t::binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
valid_atom_piece?(t)
|
||||
end
|
||||
|
||||
@@ -129,7 +129,7 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
defp valid_identifier?(<<h, t::binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
@@ -143,7 +143,7 @@ end
|
||||
defimpl Inspect, for: BitString do
|
||||
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<<?", escape(thing, ?") :: binary, ?">>
|
||||
<<?", escape(thing, ?")::binary, ?">>
|
||||
else
|
||||
inspect_bitstring(thing, opts)
|
||||
end
|
||||
@@ -160,54 +160,54 @@ defimpl Inspect, for: BitString do
|
||||
escape(other, char, <<>>)
|
||||
end
|
||||
|
||||
defp escape(<< char, t :: binary >>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
defp escape(<<char, t::binary >>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, char>>)
|
||||
end
|
||||
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{>>)
|
||||
defp escape(<<?#, ?{, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?#, ?{>>)
|
||||
end
|
||||
defp escape(<<?\a, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
defp escape(<<?\a, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?a>>)
|
||||
end
|
||||
defp escape(<<?\b, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
defp escape(<<?\b, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?b>>)
|
||||
end
|
||||
defp escape(<<?\d, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
defp escape(<<?\d, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?d>>)
|
||||
end
|
||||
defp escape(<<?\e, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
defp escape(<<?\e, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?e>>)
|
||||
end
|
||||
defp escape(<<?\f, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
defp escape(<<?\f, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?f>>)
|
||||
end
|
||||
defp escape(<<?\n, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
defp escape(<<?\n, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?n>>)
|
||||
end
|
||||
defp escape(<<?\r, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
defp escape(<<?\r, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?r>>)
|
||||
end
|
||||
defp escape(<<?\\, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
defp escape(<<?\\, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?\\>>)
|
||||
end
|
||||
defp escape(<<?\t, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
defp escape(<<?\t, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?t>>)
|
||||
end
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
defp escape(<<?\v, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, ?\\, ?v>>)
|
||||
end
|
||||
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
|
||||
head = << h :: utf8 >>
|
||||
defp escape(<<h::utf8, t::binary>>, char, binary) do
|
||||
head = <<h::utf8 >>
|
||||
if String.printable?(head) do
|
||||
escape(t, char, append(head, binary))
|
||||
else
|
||||
<< byte :: size(8), h :: binary >> = head
|
||||
t = << h :: binary, t :: binary >>
|
||||
escape(t, char, << binary :: binary, escape_char(byte) :: binary >>)
|
||||
<<byte::8, h::binary >> = head
|
||||
t = <<h::binary, t::binary>>
|
||||
escape(t, char, <<binary::binary, escape_char(byte)::binary>>)
|
||||
end
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, escape_char(h) :: binary >>)
|
||||
defp escape(<<h, t::binary>>, char, binary) do
|
||||
escape(t, char, <<binary::binary, escape_char(h)::binary>>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
@@ -218,17 +218,17 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::size(8)>>
|
||||
<<a::4, b::4>> = <<char::8>>
|
||||
<<?\\, ?x, to_hex(a), to_hex(b)>>
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::16>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
end
|
||||
|
||||
def escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}>>
|
||||
end
|
||||
@@ -236,7 +236,7 @@ defimpl Inspect, for: BitString do
|
||||
defp to_hex(c) when c in 0..9, do: ?0+c
|
||||
defp to_hex(c) when c in 10..15, do: ?A+c-10
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<h, t::binary>>, binary), do: append(t, <<binary::binary, h>>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
## Bitstrings
|
||||
@@ -249,11 +249,11 @@ defimpl Inspect, for: BitString do
|
||||
acc <> "..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
|
||||
defp each_bit(<<h, t::bitstring>>, counter, acc) when t != <<>> do
|
||||
each_bit(t, decrement(counter), acc <> Integer.to_string(h) <> ", ")
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter, acc) do
|
||||
defp each_bit(<<h::8>>, _counter, acc) do
|
||||
acc <> Integer.to_string(h)
|
||||
end
|
||||
|
||||
@@ -263,7 +263,7 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
defp each_bit(bitstring, _counter, acc) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
<<h::size(size)>> = bitstring
|
||||
acc <> Integer.to_string(h) <> "::size(" <> Integer.to_string(size) <> ")"
|
||||
end
|
||||
|
||||
@@ -277,7 +277,7 @@ defimpl Inspect, for: List do
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
cond do
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
|
||||
<<?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?')::binary, ?'>>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts, &keyword/2)
|
||||
true ->
|
||||
@@ -285,6 +285,7 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
@@ -292,6 +293,7 @@ defimpl Inspect, for: List do
|
||||
)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
case Atom.to_char_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
@@ -302,6 +304,19 @@ defimpl Inspect, for: List do
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
@doc false
|
||||
def printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
|
||||
def printable?([?\n|cs]), do: printable?(cs)
|
||||
def printable?([?\r|cs]), do: printable?(cs)
|
||||
def printable?([?\t|cs]), do: printable?(cs)
|
||||
def printable?([?\v|cs]), do: printable?(cs)
|
||||
def printable?([?\b|cs]), do: printable?(cs)
|
||||
def printable?([?\f|cs]), do: printable?(cs)
|
||||
def printable?([?\e|cs]), do: printable?(cs)
|
||||
def printable?([?\a|cs]), do: printable?(cs)
|
||||
def printable?([]), do: true
|
||||
def printable?(_), do: false
|
||||
|
||||
## Private
|
||||
|
||||
defp key_to_binary(key) do
|
||||
@@ -310,18 +325,6 @@ defimpl Inspect, for: List do
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
|
||||
defp printable?([?\n|cs]), do: printable?(cs)
|
||||
defp printable?([?\r|cs]), do: printable?(cs)
|
||||
defp printable?([?\t|cs]), do: printable?(cs)
|
||||
defp printable?([?\v|cs]), do: printable?(cs)
|
||||
defp printable?([?\b|cs]), do: printable?(cs)
|
||||
defp printable?([?\f|cs]), do: printable?(cs)
|
||||
defp printable?([?\e|cs]), do: printable?(cs)
|
||||
defp printable?([?\a|cs]), do: printable?(cs)
|
||||
defp printable?([]), do: true
|
||||
defp printable?(_), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
|
||||
@@ -11,6 +11,14 @@ defmodule Integer do
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
|
||||
Allowed in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.is_odd(3)
|
||||
true
|
||||
|
||||
iex> Integer.is_odd(4)
|
||||
false
|
||||
"""
|
||||
defmacro is_odd(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
@@ -22,6 +30,14 @@ defmodule Integer do
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
|
||||
Allowed in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.is_even(10)
|
||||
true
|
||||
|
||||
iex> Integer.is_even(5)
|
||||
false
|
||||
"""
|
||||
defmacro is_even(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
@@ -126,14 +142,14 @@ defmodule Integer do
|
||||
raise ArgumentError, "invalid base #{base}"
|
||||
end
|
||||
|
||||
defp parse_in_base(<< ?-, bin :: binary >>, base) do
|
||||
defp parse_in_base("-" <> bin, base) do
|
||||
case do_parse(bin, base) do
|
||||
:error -> :error
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_in_base(<< ?+, bin :: binary >>, base) do
|
||||
defp parse_in_base("+" <> bin, base) do
|
||||
do_parse(bin, base)
|
||||
end
|
||||
|
||||
@@ -141,9 +157,9 @@ defmodule Integer do
|
||||
do_parse(bin, base)
|
||||
end
|
||||
|
||||
defp do_parse(<< char, bin :: binary >>, base) do
|
||||
defp do_parse(<<char, rest::binary>>, base) do
|
||||
if valid_digit_in_base?(char, base) do
|
||||
do_parse(bin, base, parse_digit(char, base))
|
||||
do_parse(rest, base, parse_digit(char, base))
|
||||
else
|
||||
:error
|
||||
end
|
||||
@@ -151,11 +167,11 @@ defmodule Integer do
|
||||
|
||||
defp do_parse(_, _), do: :error
|
||||
|
||||
defp do_parse(<< char, rest :: binary >>, base, acc) do
|
||||
defp do_parse(<<char, rest::binary>>, base, acc) do
|
||||
if valid_digit_in_base?(char, base) do
|
||||
do_parse(rest, base, base * acc + parse_digit(char, base))
|
||||
else
|
||||
{acc, << char, rest :: binary >>}
|
||||
{acc, <<char, rest::binary>>}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+15
-2
@@ -9,7 +9,7 @@ defmodule IO do
|
||||
|
||||
The majority of the functions expect char data, i.e. strings or
|
||||
lists of characters and strings. In case another type is given,
|
||||
it will do a conversion to string via the `String.Chars` protocol
|
||||
functions will convert to string via the `String.Chars` protocol
|
||||
(as shown in typespecs).
|
||||
|
||||
The functions starting with `bin*` expect iodata as an argument,
|
||||
@@ -27,6 +27,11 @@ defmodule IO do
|
||||
* `:stderr` - a shortcut for the named process `:standard_error`
|
||||
provided in Erlang
|
||||
|
||||
IO devices maintain their position, that means subsequent calls to any
|
||||
reading or writing functions will start from the place when the device
|
||||
was last accessed. Position of files can be changed using the
|
||||
`:file.position/2` function.
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@@ -247,7 +252,9 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
Reads a line from the IO device.
|
||||
|
||||
It returns:
|
||||
|
||||
* `data` - the characters in the line terminated
|
||||
by a line-feed (LF) or end of file (EOF)
|
||||
@@ -257,6 +264,12 @@ defmodule IO do
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
|
||||
## Examples
|
||||
|
||||
To display "What is your name?" as a prompt and await user input:
|
||||
|
||||
IO.gets "What is your name?"
|
||||
"""
|
||||
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
|
||||
@@ -42,6 +42,34 @@ defmodule IO.ANSI do
|
||||
Application.get_env(:elixir, :ansi_enabled, false)
|
||||
end
|
||||
|
||||
@doc "Sets foreground color"
|
||||
@spec color(0..255) :: String.t
|
||||
def color(code) when code in 0..255, do: "\e[38;5;#{code}m"
|
||||
|
||||
@doc ~S"""
|
||||
Sets the foreground color from individual RGB values.
|
||||
|
||||
Valid values for each color are in the range 0 to 5.
|
||||
"""
|
||||
@spec color(0..5, 0..5, 0..5) :: String.t
|
||||
def color(r, g, b) when r in 0..5 and g in 0..5 and b in 0..5 do
|
||||
color(16 + (36 * r) + (6 * g) + b)
|
||||
end
|
||||
|
||||
@doc "Sets background color"
|
||||
@spec color_background(0..255) :: String.t
|
||||
def color_background(code) when code in 0..255, do: "\e[48;5;#{code}m"
|
||||
|
||||
@doc ~S"""
|
||||
Sets the background color from individual RGB values.
|
||||
|
||||
Valid values for each color are in the range 0 to 5.
|
||||
"""
|
||||
@spec color_background(0..5, 0..5, 0..5) :: String.t
|
||||
def color_background(r, g, b) when r in 0..5 and g in 0..5 and b in 0..5 do
|
||||
color_background(16 + (36 * r) + (6 * g) + b)
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
defsequence :reset, 0
|
||||
|
||||
@@ -131,12 +159,12 @@ defmodule IO.ANSI do
|
||||
|
||||
@doc "Clears screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
|
||||
@doc "Clears line"
|
||||
defsequence :clear_line, "2", "K"
|
||||
|
||||
defp format_sequence(other) do
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{inspect other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
|
||||
@@ -104,23 +104,15 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp process(all=[line | rest], text, indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0, :infinity)
|
||||
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
|
||||
write_text(text, indent, options)
|
||||
process_table(all, indent, options)
|
||||
else
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(text, indent, options)
|
||||
process_list("• ", item, rest, count, indent, options)
|
||||
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
|
||||
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
|
||||
_ ->
|
||||
process(rest, [stripped | text], indent, options)
|
||||
end
|
||||
cond do
|
||||
link_label?(stripped, count) ->
|
||||
write_text([line], indent, options, true)
|
||||
process(rest, text, indent, options)
|
||||
table_line?(stripped) and rest != [] and table_line?(hd(rest)) ->
|
||||
write_text(text, indent, options)
|
||||
process_table(all, indent, options)
|
||||
true ->
|
||||
process_rest(stripped, rest, count, text, indent, options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -143,6 +135,22 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_rest(stripped, rest, count, text, indent, options) do
|
||||
case stripped do
|
||||
<<bullet, ?\s, item::binary>> when bullet in @bullets ->
|
||||
write_text(text, indent, options)
|
||||
process_list("• ", item, rest, count, indent, options)
|
||||
<<d1, ?., ?\s, item::binary>> when d1 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
|
||||
<<d1, d2, ?., ?\s, item::binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
|
||||
_ ->
|
||||
process(rest, [stripped | text], indent, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list(entry, line, rest, count, indent, options) do
|
||||
# The first list always win some extra padding
|
||||
if indent == "", do: entry = " " <> entry
|
||||
@@ -170,11 +178,11 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp process_list_next_kind(stripped, rest, count, next_count) do
|
||||
case {stripped, rest} do
|
||||
{<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
|
||||
{<<bullet, ?\s, _::binary>>, _} when bullet in @bullets and next_count <= count ->
|
||||
:list
|
||||
{<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
|
||||
{<<d1, ?., ?\s, _::binary>>, _} when d1 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
{<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
|
||||
{<<d1, d2, ?., ?\s, _::binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
|
||||
:list
|
||||
{"", [" " <> _ | _]} ->
|
||||
:next
|
||||
@@ -254,7 +262,7 @@ defmodule IO.ANSI.Docs do
|
||||
## Tables
|
||||
|
||||
defp process_table(lines, indent, options) do
|
||||
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
|
||||
{table, rest} = Enum.split_while(lines, &table_line?/1)
|
||||
table_lines(table, options)
|
||||
newline_after_block
|
||||
process(rest, [], indent, options)
|
||||
@@ -334,7 +342,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
end
|
||||
|
||||
defp is_table_line?(line) do
|
||||
defp table_line?(line) do
|
||||
Regex.match?(~r'''
|
||||
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
||||
|
|
||||
@@ -344,6 +352,14 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp link_label?("[" <> rest, count) when count <= 3, do: link_label?(rest)
|
||||
defp link_label?(_, _), do: false
|
||||
|
||||
defp link_label?("]: " <> _), do: true
|
||||
defp link_label?("]" <> _), do: false
|
||||
defp link_label?(""), do: false
|
||||
defp link_label?(<<_>> <> rest), do: link_label?(rest)
|
||||
|
||||
defp strip_spaces(" " <> line, acc, max) when acc < max,
|
||||
do: strip_spaces(line, acc + 1, max)
|
||||
defp strip_spaces(rest, acc, _max),
|
||||
@@ -385,11 +401,11 @@ defmodule IO.ANSI.Docs do
|
||||
{Enum.reverse(acc), []}
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
|
||||
defp length_without_escape(<<?\e, ?[, _, _, ?m>> <> rest, count) do
|
||||
length_without_escape(rest, count)
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, ?m, rest :: binary >>, count) do
|
||||
defp length_without_escape(<<?\e, ?[, _, ?m>> <> rest, count) do
|
||||
length_without_escape(rest, count)
|
||||
end
|
||||
|
||||
@@ -407,10 +423,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true -> Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ -> text
|
||||
end
|
||||
Regex.replace(~r{https?\S*}, text, &String.replace(&1, "_", "\\_"))
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
@@ -437,11 +450,11 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# Inline start
|
||||
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
|
||||
defp handle_inline(<<?*, ?*, rest::binary>>, options) do
|
||||
handle_inline(rest, ?d, ["**"], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
|
||||
defp handle_inline(<<mark, rest::binary>>, options) when mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [], options)
|
||||
end
|
||||
|
||||
@@ -451,72 +464,72 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# Inline delimiters
|
||||
|
||||
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
|
||||
defp handle_inline(<<delimiter, ?*, ?*, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and delimiter in @delimiters do
|
||||
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<delimiter, mark, rest :: binary>>, nil, buffer, acc, options)
|
||||
defp handle_inline(<<delimiter, mark, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and delimiter in @delimiters and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?`, rest :: binary>>, nil, buffer, acc, options)
|
||||
defp handle_inline(<<?`, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
|
||||
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, nil, buffer, acc, options)
|
||||
defp handle_inline(<<?\\, ?\\, mark, rest::binary>>, nil, buffer, acc, options)
|
||||
when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
|
||||
defp handle_inline(<<?\\, ?\\, rest::binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, options)
|
||||
end
|
||||
|
||||
# An escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
|
||||
defp handle_inline(<<?\\, mark, rest::binary>>, limit, buffer, acc, options)
|
||||
when not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
|
||||
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, ?d, buffer, acc, options)
|
||||
defp handle_inline(<<?*, ?*, delimiter, rest::binary>>, ?d, buffer, acc, options)
|
||||
when delimiter in @delimiters do
|
||||
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, delimiter, rest :: binary>>, mark, buffer, acc, options)
|
||||
defp handle_inline(<<mark, delimiter, rest::binary>>, mark, buffer, acc, options)
|
||||
when delimiter in @delimiters and mark in @single do
|
||||
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?*, ?*, rest:: binary>>, ?d, buffer, acc, options)
|
||||
defp handle_inline(<<?*, ?*, rest::binary>>, ?d, buffer, acc, options)
|
||||
when rest == "" do
|
||||
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options)
|
||||
defp handle_inline(<<mark, rest::binary>>, mark, buffer, acc, options)
|
||||
when rest == "" and mark in @single do
|
||||
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?`, rest :: binary>>, ?`, buffer, acc, options) do
|
||||
defp handle_inline(<<?`, rest::binary>>, ?`, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
# Catch all
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
|
||||
defp handle_inline(<<char, rest::binary>>, mark, buffer, acc, options) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, options)
|
||||
end
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
Defines an `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
|
||||
+129
-88
@@ -7,11 +7,12 @@ import :elixir_bootstrap
|
||||
|
||||
defmodule Kernel do
|
||||
@moduledoc """
|
||||
`Kernel` provides the default macros and functions
|
||||
Elixir imports into your environment. These macros and functions
|
||||
can be skipped or cherry-picked via the `import` macro. For
|
||||
instance, if you want to tell Elixir not to import the `if`
|
||||
macro, you can do:
|
||||
Provides the default macros and functions Elixir imports into your
|
||||
environment.
|
||||
|
||||
These macros and functions can be skipped or cherry-picked via the
|
||||
`import` macro. For instance, if you want to tell Elixir not to
|
||||
import the `if` macro, you can do:
|
||||
|
||||
import Kernel, except: [if: 2]
|
||||
|
||||
@@ -1339,19 +1340,31 @@ defmodule Kernel do
|
||||
:erlang.error unquote(alias).exception([])
|
||||
end
|
||||
_ ->
|
||||
quote do
|
||||
case unquote(msg) do
|
||||
msg when is_binary(msg) ->
|
||||
:erlang.error RuntimeError.exception(msg)
|
||||
atom when is_atom(atom) ->
|
||||
:erlang.error atom.exception([])
|
||||
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
|
||||
:erlang.error other
|
||||
other ->
|
||||
message = "raise/1 expects an alias, string or exception as the first argument, got: #{inspect other}"
|
||||
:erlang.error ArgumentError.exception(message)
|
||||
end
|
||||
generated = fn fun, var ->
|
||||
{fun, [generated: true, line: -1], [{var, [], __MODULE__}]}
|
||||
end
|
||||
|
||||
{fun, meta, [arg, [do: clauses]]} =
|
||||
quote do
|
||||
case unquote(msg) do
|
||||
msg when unquote(generated.(:is_binary, :msg)) ->
|
||||
:erlang.error RuntimeError.exception(msg)
|
||||
atom when unquote(generated.(:is_atom, :atom)) ->
|
||||
:erlang.error atom.exception([])
|
||||
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
|
||||
:erlang.error other
|
||||
other ->
|
||||
message = "raise/1 expects an alias, string or exception as the first argument, got: #{inspect other}"
|
||||
:erlang.error ArgumentError.exception(message)
|
||||
end
|
||||
end
|
||||
|
||||
clauses =
|
||||
:lists.map(fn {:->, meta, args} ->
|
||||
{:->, [generated: true] ++ Keyword.put(meta, :line, -1), args}
|
||||
end, clauses)
|
||||
|
||||
{fun, meta, [arg, [do: clauses]]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1590,33 +1603,47 @@ defmodule Kernel do
|
||||
|
||||
"""
|
||||
@spec struct(module | map, Enum.t) :: map
|
||||
def struct(struct, kv \\ [])
|
||||
|
||||
def struct(struct, []) when is_atom(struct) do
|
||||
apply(struct, :__struct__, [])
|
||||
end
|
||||
|
||||
def struct(struct, kv) when is_atom(struct) do
|
||||
struct(apply(struct, :__struct__, []), kv)
|
||||
end
|
||||
|
||||
def struct(%{__struct__: _} = struct, []) do
|
||||
struct
|
||||
end
|
||||
|
||||
def struct(%{__struct__: _} = struct, kv) do
|
||||
Enum.reduce(kv, struct, fn {k, v}, acc ->
|
||||
case :maps.is_key(k, acc) and k != :__struct__ do
|
||||
true -> :maps.put(k, v, acc)
|
||||
def struct(struct, kv \\ []) do
|
||||
struct(struct, kv, fn({key, val}, acc) ->
|
||||
case :maps.is_key(key, acc) and key != :__struct__ do
|
||||
true -> :maps.put(key, val, acc)
|
||||
false -> acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `struct/2` but raises if any of provided keys doesn't exist in the struct.
|
||||
"""
|
||||
@spec struct!(module | map, Enum.t) :: map | no_return
|
||||
def struct!(struct, kv \\ []) do
|
||||
struct(struct, kv, fn
|
||||
{:__struct__, _}, acc -> acc
|
||||
{key, val}, acc ->
|
||||
:maps.update(key, val, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
defp struct(struct, [], _fun) when is_atom(struct) do
|
||||
apply(struct, :__struct__, [])
|
||||
end
|
||||
|
||||
defp struct(struct, kv, fun) when is_atom(struct) do
|
||||
struct(apply(struct, :__struct__, []), kv, fun)
|
||||
end
|
||||
|
||||
defp struct(%{__struct__: _} = struct, [], _fun) do
|
||||
struct
|
||||
end
|
||||
|
||||
defp struct(%{__struct__: _} = struct, kv, fun) do
|
||||
Enum.reduce(kv, struct, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a value from a nested structure.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
Uses the `Access` module to traverse the structures
|
||||
according to the given `keys`, unless the `key` is a
|
||||
function.
|
||||
|
||||
@@ -1635,7 +1662,7 @@ defmodule Kernel do
|
||||
27
|
||||
|
||||
In case any of entries in the middle returns `nil`, `nil` will be returned
|
||||
as per the Access protocol:
|
||||
as per the Access module:
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, ["unknown", :age])
|
||||
@@ -1675,7 +1702,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Puts a value in a nested structure.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
Uses the `Access` module to traverse the structures
|
||||
according to the given `keys`, unless the `key` is a
|
||||
function. If the key is a function, it will be invoked
|
||||
as specified in `get_and_update_in/3`.
|
||||
@@ -1697,7 +1724,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Updates a key in a nested structure.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
Uses the `Access` module to traverse the structures
|
||||
according to the given `keys`, unless the `key` is a
|
||||
function. If the key is a function, it will be invoked
|
||||
as specified in `get_and_update_in/3`.
|
||||
@@ -1722,7 +1749,7 @@ defmodule Kernel do
|
||||
It expects a tuple to be returned, containing the value
|
||||
retrieved and the update one.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
Uses the `Access` module to traverse the structures
|
||||
according to the given `keys`, unless the `key` is a
|
||||
function.
|
||||
|
||||
@@ -2161,17 +2188,17 @@ defmodule Kernel do
|
||||
true ->
|
||||
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
|
||||
false ->
|
||||
arg = case name do
|
||||
:behavior ->
|
||||
cond do
|
||||
name == :behavior ->
|
||||
:elixir_errors.warn env.line, env.file,
|
||||
"@behavior attribute is not supported, please use @behaviour instead"
|
||||
:doc -> {env.line, arg}
|
||||
:typedoc -> {env.line, arg}
|
||||
:moduledoc -> {env.line, arg}
|
||||
_ -> arg
|
||||
:lists.member(name, [:moduledoc, :typedoc, :doc]) ->
|
||||
{stack, _} = :elixir_quote.escape(env_stacktrace(env), false)
|
||||
arg = {env.line, arg}
|
||||
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg), unquote(stack))
|
||||
true ->
|
||||
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg))
|
||||
end
|
||||
|
||||
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2241,7 +2268,7 @@ defmodule Kernel do
|
||||
defmacro binding(context \\ nil) do
|
||||
in_match? = Macro.Env.in_match?(__CALLER__)
|
||||
for {v, c} <- __CALLER__.vars, c == context do
|
||||
{v, wrap_binding(in_match?, {v, [warn: false], c})}
|
||||
{v, wrap_binding(in_match?, {v, [generated: true], c})}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2390,19 +2417,9 @@ defmodule Kernel do
|
||||
do).
|
||||
"""
|
||||
defmacro destructure(left, right) when is_list(left) do
|
||||
Enum.reduce left, right, fn item, acc ->
|
||||
{:case, meta, args} =
|
||||
quote do
|
||||
case unquote(acc) do
|
||||
[h|t] ->
|
||||
unquote(item) = h
|
||||
t
|
||||
other when other == [] or other == nil ->
|
||||
unquote(item) = nil
|
||||
[]
|
||||
end
|
||||
end
|
||||
{:case, meta, args}
|
||||
quote do
|
||||
unquote(left) =
|
||||
Kernel.Utils.destructure(unquote(right), unquote(length(left)))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2429,16 +2446,21 @@ defmodule Kernel do
|
||||
defmacro first .. last do
|
||||
case is_float(first) or is_float(last) or
|
||||
is_atom(first) or is_atom(last) or
|
||||
is_binary(first) or is_binary(last) do
|
||||
is_binary(first) or is_binary(last) or
|
||||
is_list(first) or is_list(last) do
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
"ranges (left .. right) expect both sides to be integers, " <>
|
||||
"ranges (first..last) expect both sides to be integers, " <>
|
||||
"got: #{Macro.to_string({:.., [], [first, last]})}"
|
||||
false ->
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
case __CALLER__.context do
|
||||
nil -> quote do: Elixir.Range.new(unquote(first), unquote(last))
|
||||
_ -> {:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Provides a short-circuit operator that evaluates and returns
|
||||
the second expression only if the first one evaluates to `true`
|
||||
@@ -2462,9 +2484,8 @@ defmodule Kernel do
|
||||
false
|
||||
|
||||
|
||||
Note that, unlike Erlang's `and` operator,
|
||||
this operator accepts any expression as the first argument,
|
||||
not only booleans.
|
||||
Note that, unlike `and/2`, this operator accepts any expression
|
||||
as the first argument, not only booleans.
|
||||
"""
|
||||
defmacro left && right do
|
||||
quote do
|
||||
@@ -2498,9 +2519,8 @@ defmodule Kernel do
|
||||
iex> Enum.empty?([]) || throw(:bad)
|
||||
true
|
||||
|
||||
Note that, unlike Erlang's `or` operator,
|
||||
this operator accepts any expression as the first argument,
|
||||
not only booleans.
|
||||
Note that, unlike `or/2`, this operator accepts any expression
|
||||
as the first argument, not only booleans.
|
||||
"""
|
||||
defmacro left || right do
|
||||
quote do
|
||||
@@ -2662,7 +2682,7 @@ defmodule Kernel do
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]} ->
|
||||
in_range(left, Macro.expand(first, __CALLER__), Macro.expand(last, __CALLER__))
|
||||
_ ->
|
||||
raise ArgumentError, <<"invalid args for operator in, it expects a compile time list ",
|
||||
raise ArgumentError, <<"invalid args for operator \"in\", it expects a compile time list ",
|
||||
"or range on the right side when used in guard expressions, got: ",
|
||||
Macro.to_string(right) :: binary>>
|
||||
end
|
||||
@@ -3036,7 +3056,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
Foo.bar #=> 3
|
||||
Foo.sum(1, 2) #=> ** (UndefinedFunctionError) undefined function: Foo.sum/2
|
||||
Foo.sum(1, 2) #=> ** (UndefinedFunctionError) undefined function Foo.sum/2
|
||||
|
||||
"""
|
||||
defmacro defp(call, expr \\ nil) do
|
||||
@@ -3190,7 +3210,7 @@ defmodule Kernel do
|
||||
"""
|
||||
defmacro defstruct(fields) do
|
||||
quote bind_quoted: [fields: fields] do
|
||||
fields = Kernel.Def.struct(__MODULE__, fields)
|
||||
fields = Kernel.Utils.defstruct(__MODULE__, fields)
|
||||
@struct fields
|
||||
|
||||
case Module.get_attribute(__MODULE__, :derive) do
|
||||
@@ -3355,15 +3375,16 @@ defmodule Kernel do
|
||||
|
||||
The real benefit of protocols comes when mixed with structs.
|
||||
For instance, Elixir ships with many data types implemented as
|
||||
structs, like `HashDict` and `HashSet`. We can implement the
|
||||
`Blank` protocol for those types as well:
|
||||
structs, like `MapSet`. We can implement the `Blank` protocol
|
||||
for those types as well:
|
||||
|
||||
defimpl Blank, for: [HashDict, HashSet] do
|
||||
defimpl Blank, for: MapSet do
|
||||
def blank?(enum_like), do: Enum.empty?(enum_like)
|
||||
end
|
||||
|
||||
When implementing a protocol for a struct, the `:for` option can be omitted if
|
||||
the `defimpl` call is inside the module that defines the struct:
|
||||
When implementing a protocol for a struct, the `:for` option can
|
||||
be omitted if the `defimpl` call is inside the module that defines
|
||||
the struct:
|
||||
|
||||
defmodule User do
|
||||
defstruct [:email, :name]
|
||||
@@ -3599,17 +3620,31 @@ defmodule Kernel do
|
||||
please define it a module which will be imported accordingly.
|
||||
"""
|
||||
defmacro use(module, opts \\ []) do
|
||||
expanded = Macro.expand(module, __CALLER__)
|
||||
|
||||
case is_atom(expanded) do
|
||||
false ->
|
||||
raise ArgumentError, "invalid arguments for use, expected an atom or alias as argument"
|
||||
true ->
|
||||
calls = Enum.map(expand_aliases(module, __CALLER__), fn
|
||||
expanded when is_atom(expanded) ->
|
||||
quote do
|
||||
require unquote(expanded)
|
||||
unquote(expanded).__using__(unquote(opts))
|
||||
end
|
||||
end
|
||||
_otherwise ->
|
||||
raise ArgumentError, "invalid arguments for use, expected a compile time atom or alias, got: #{Macro.to_string(module)}"
|
||||
end)
|
||||
quote(do: (unquote_splicing calls))
|
||||
end
|
||||
|
||||
defp expand_aliases({{:., _, [base, :{}]}, _, refs}, env) do
|
||||
base = Macro.expand(base, env)
|
||||
Enum.map(refs, fn
|
||||
{:__aliases__, _, ref} ->
|
||||
Module.concat([base | ref])
|
||||
ref when is_atom(ref) ->
|
||||
Module.concat(base, ref)
|
||||
other -> other
|
||||
end)
|
||||
end
|
||||
|
||||
defp expand_aliases(module, env) do
|
||||
[Macro.expand(module, env)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -3665,7 +3700,10 @@ defmodule Kernel do
|
||||
raise ArgumentError, "expected to: to be given as argument"
|
||||
|
||||
for fun <- List.wrap(funs) do
|
||||
{name, args, as, as_args} = Kernel.Def.delegate(fun, opts)
|
||||
{name, args, as, as_args} = Kernel.Utils.defdelegate(fun, opts, __ENV__)
|
||||
unless Module.get_attribute(__MODULE__, :doc) do
|
||||
@doc "See `#{inspect target}.#{as}/#{:erlang.length as_args}`."
|
||||
end
|
||||
def unquote(name)(unquote_splicing(args)) do
|
||||
unquote(target).unquote(as)(unquote_splicing(as_args))
|
||||
end
|
||||
@@ -3691,7 +3729,7 @@ defmodule Kernel do
|
||||
|
||||
"""
|
||||
defmacro sigil_S(term, modifiers)
|
||||
defmacro sigil_S(string, []), do: string
|
||||
defmacro sigil_S({:<<>>, _, [binary]}, []) when is_binary(binary), do: binary
|
||||
|
||||
@doc ~S"""
|
||||
Handles the sigil `~s`.
|
||||
@@ -3712,6 +3750,9 @@ defmodule Kernel do
|
||||
|
||||
"""
|
||||
defmacro sigil_s(term, modifiers)
|
||||
defmacro sigil_s({:<<>>, _, [piece]}, []) when is_binary(piece) do
|
||||
Macro.unescape_string(piece)
|
||||
end
|
||||
defmacro sigil_s({:<<>>, line, pieces}, []) do
|
||||
{:<<>>, line, Macro.unescape_tokens(pieces)}
|
||||
end
|
||||
|
||||
@@ -146,7 +146,18 @@ defmodule Kernel.CLI do
|
||||
# Parse shared options
|
||||
|
||||
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
IO.puts "Elixir #{System.version}"
|
||||
if function_exported?(IEx, :started?, 0) and IEx.started? do
|
||||
IO.puts "IEx #{System.version}"
|
||||
else
|
||||
IO.puts :erlang.system_info(:system_version)
|
||||
{:ok, v} = Version.parse(System.version)
|
||||
|
||||
case v.pre do
|
||||
[] -> IO.puts "Elixir #{System.version}"
|
||||
_ -> IO.puts "Elixir #{System.version} (#{System.build_info().revision})"
|
||||
end
|
||||
end
|
||||
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Kernel.ErrorHandler do
|
||||
end
|
||||
|
||||
def release() do
|
||||
# On release, no further allow elixir_ensure_compiled
|
||||
# On release, no longer allow elixir_ensure_compiled
|
||||
# directives and revert to the original error handler.
|
||||
# Note we should not delete the elixir_compiler_pid though,
|
||||
# as we still want to send notifications to the compiler.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# This is an Elixir module responsible for tracking
|
||||
# the usage of aliases, imports and requires in the Elixir scope.
|
||||
#
|
||||
# The implementation simply stores dispatch information in an
|
||||
|
||||
@@ -55,9 +55,9 @@ defmodule Kernel.ParallelCompiler do
|
||||
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
# compilation status will be set to error.
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok ->
|
||||
:ok ->
|
||||
result
|
||||
:error ->
|
||||
IO.puts :stderr, "Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
|
||||
@@ -3,8 +3,6 @@ defmodule Kernel.ParallelRequire do
|
||||
A module responsible for requiring files in parallel.
|
||||
"""
|
||||
|
||||
defmacrop default_callback, do: quote(do: fn x -> x end)
|
||||
|
||||
@doc """
|
||||
Requires the given files.
|
||||
|
||||
@@ -13,9 +11,21 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
Returns the modules generated by each required file.
|
||||
"""
|
||||
def files(files, callback \\ default_callback) do
|
||||
def files(files, callback \\ fn x -> x end) do
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_requires(files, [], callback, schedulers, [])
|
||||
result = spawn_requires(files, [], callback, schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error.
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok ->
|
||||
result
|
||||
:error ->
|
||||
IO.puts :stderr, "Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
defp spawn_requires([], [], _callback, _schedulers, result), do: result
|
||||
@@ -29,22 +39,9 @@ defmodule Kernel.ParallelRequire do
|
||||
end
|
||||
|
||||
defp spawn_requires([h|t], waiting, callback, schedulers, result) do
|
||||
parent = self
|
||||
|
||||
compiler_pid = :erlang.get(:elixir_compiler_pid)
|
||||
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
|
||||
{:error_handler, handler} = :erlang.process_info(parent, :error_handler)
|
||||
|
||||
parent = self()
|
||||
{pid, ref} = :erlang.spawn_monitor fn ->
|
||||
if compiler_pid != :undefined do
|
||||
:erlang.put(:elixir_compiler_pid, compiler_pid)
|
||||
end
|
||||
|
||||
if ensure_compiled != :undefined do
|
||||
:erlang.put(:elixir_ensure_compiled, ensure_compiled)
|
||||
end
|
||||
|
||||
:erlang.process_flag(:error_handler, handler)
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
|
||||
exit(try do
|
||||
new = Code.require_file(h) || []
|
||||
@@ -75,6 +72,14 @@ defmodule Kernel.ParallelRequire do
|
||||
end
|
||||
end
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
{:module_available, child, ref, _, _, _} ->
|
||||
send(child, {ref, :ack})
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
{:struct_available, _} ->
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
{:waiting, :struct, child, ref, _} ->
|
||||
send(child, {ref, :release})
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
defmodule Kernel.SpecialForms do
|
||||
@moduledoc """
|
||||
In this module we define Elixir special forms. Special forms
|
||||
cannot be overridden by the developer and are the basic
|
||||
building blocks of Elixir code.
|
||||
Special forms are the basic building blocks of Elixir, and therefore
|
||||
they cannot be overridden by the developer.
|
||||
|
||||
Some of those forms are lexical (like `alias`, `case`, etc).
|
||||
The macros `{}` and `<<>>` are also special forms used to define
|
||||
tuple and binary data structures respectively.
|
||||
We define them in this module. Some of these forms are lexical (like
|
||||
`alias`, `case`, etc). The macros `{}` and `<<>>` are also special
|
||||
forms used to define tuple and binary data structures respectively.
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__ENV__`,
|
||||
`__MODULE__`, `__DIR__` and `__CALLER__`). Pseudo variables return
|
||||
@@ -88,7 +87,7 @@ defmodule Kernel.SpecialForms do
|
||||
%{:a => :c}
|
||||
|
||||
Notice the update syntax requires the given keys to exist.
|
||||
Trying to update a key that does not exist will raise an `ArgumentError`.
|
||||
Trying to update a key that does not exist will raise an `KeyError`.
|
||||
|
||||
## AST representation
|
||||
|
||||
@@ -96,8 +95,8 @@ defmodule Kernel.SpecialForms do
|
||||
always represented internally as a list of two-items tuples
|
||||
for simplicity:
|
||||
|
||||
iex> quote do: %{:a => :b, c: :d}
|
||||
{:%{}, [], [{:a, :b}, {:c, :d}]}
|
||||
iex> quote do: %{"a" => :b, c: :d}
|
||||
{:%{}, [], [{"a", :b}, {:c, :d}]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:%{})(args)
|
||||
@@ -169,8 +168,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> << 1, 2, 3 >>
|
||||
<< 1, 2, 3 >>
|
||||
iex> <<1, 2, 3>>
|
||||
<<1, 2, 3>>
|
||||
|
||||
## Types
|
||||
|
||||
@@ -193,7 +192,7 @@ defmodule Kernel.SpecialForms do
|
||||
<<1, 2, 3>>
|
||||
|
||||
Elixir also accepts by default the segment to be a literal
|
||||
string or a literal char list, which are by expanded to integers:
|
||||
string or a literal char list, which are by default expanded to integers:
|
||||
|
||||
iex> <<0, "foo">>
|
||||
<<0, 102, 111, 111>>
|
||||
@@ -207,15 +206,15 @@ defmodule Kernel.SpecialForms do
|
||||
We can solve this by explicitly tagging it as a binary:
|
||||
|
||||
iex> rest = "oo"
|
||||
iex> <<102, rest :: binary>>
|
||||
iex> <<102, rest::binary>>
|
||||
"foo"
|
||||
|
||||
The utf8, utf16, and utf32 types are for unicode codepoints. They
|
||||
can also be applied to literal strings and char lists:
|
||||
|
||||
iex> <<"foo" :: utf16>>
|
||||
iex> <<"foo"::utf16>>
|
||||
<<0, 102, 0, 111, 0, 111>>
|
||||
iex> <<"foo" :: utf32>>
|
||||
iex> <<"foo"::utf32>>
|
||||
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
|
||||
|
||||
## Options
|
||||
@@ -223,13 +222,13 @@ defmodule Kernel.SpecialForms do
|
||||
Many options can be given by using `-` as separator. Order is
|
||||
arbitrary, so the following are all equivalent:
|
||||
|
||||
<<102 :: integer-native, rest :: binary>>
|
||||
<<102 :: native-integer, rest :: binary>>
|
||||
<<102 :: unsigned-big-integer, rest :: binary>>
|
||||
<<102 :: unsigned-big-integer-size(8), rest :: binary>>
|
||||
<<102 :: unsigned-big-integer-8, rest :: binary>>
|
||||
<<102 :: 8-integer-big-unsigned, rest :: binary>>
|
||||
<<102, rest :: binary>>
|
||||
<<102::integer-native, rest::binary>>
|
||||
<<102::native-integer, rest::binary>>
|
||||
<<102::unsigned-big-integer, rest::binary>>
|
||||
<<102::unsigned-big-integer-size(8), rest::binary>>
|
||||
<<102::unsigned-big-integer-8, rest::binary>>
|
||||
<<102::8-integer-big-unsigned, rest::binary>>
|
||||
<<102, rest::binary>>
|
||||
|
||||
### Unit and Size
|
||||
|
||||
@@ -268,9 +267,9 @@ defmodule Kernel.SpecialForms do
|
||||
when passing integer values:
|
||||
|
||||
iex> x = 1
|
||||
iex> << x :: 8 >> == << x :: size(8) >>
|
||||
iex> <<x::8>> == <<x::size(8)>>
|
||||
true
|
||||
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
iex> <<x::8 * 4>> == <<x::size(8)-unit(4)>>
|
||||
true
|
||||
|
||||
This syntax reflects the fact the effective size is given by
|
||||
@@ -293,11 +292,11 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
|
||||
|
||||
iex> <<int::integer>> = <<-100>>
|
||||
iex> <<int::integer>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
156
|
||||
iex> <<int::integer-signed>> = <<-100>>
|
||||
iex> <<int::integer-signed>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
-100
|
||||
@@ -305,7 +304,7 @@ defmodule Kernel.SpecialForms do
|
||||
`signed` and `unsigned` are only used for matching binaries (see below) and
|
||||
are only used for integers.
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
iex> <<-100::signed, _rest::binary>> = <<-100, "foo">>
|
||||
<<156, 102, 111, 111>>
|
||||
|
||||
### Endianness
|
||||
@@ -541,7 +540,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro require(module, opts)
|
||||
|
||||
@doc """
|
||||
Imports function and macros from other modules.
|
||||
Imports functions and macros from other modules.
|
||||
|
||||
`import` allows one to easily access functions or macros from
|
||||
others modules without using the qualified name.
|
||||
@@ -821,7 +820,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Returning 5
|
||||
Returning 5
|
||||
25
|
||||
Got 25
|
||||
|
||||
Notice how "Returning 5" was printed twice, instead of just once. This is
|
||||
because a macro receives an expression and not a value (which is what we
|
||||
@@ -881,7 +880,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
import Math
|
||||
squared(5)
|
||||
x #=> ** (RuntimeError) undefined function or variable: x
|
||||
x #=> ** (CompileError) undefined variable x or undefined function x/0
|
||||
|
||||
We can see that `x` did not leak to the user context. This happens
|
||||
because Elixir macros are hygienic, a topic we will discuss at length
|
||||
@@ -940,7 +939,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Hygiene.write
|
||||
Hygiene.read
|
||||
#=> ** (RuntimeError) undefined function or variable: a
|
||||
#=> ** (RuntimeError) undefined variable a or undefined function a/0
|
||||
|
||||
For such, you can explicitly pass the current module scope as
|
||||
argument:
|
||||
@@ -969,34 +968,34 @@ defmodule Kernel.SpecialForms do
|
||||
Consider the following example:
|
||||
|
||||
defmodule Hygiene do
|
||||
alias HashDict, as: D
|
||||
alias Map, as: M
|
||||
|
||||
defmacro no_interference do
|
||||
quote do: D.new
|
||||
quote do: M.new
|
||||
end
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference #=> #HashDict<[]>
|
||||
Hygiene.no_interference #=> %{}
|
||||
|
||||
Notice that, even though the alias `D` is not available
|
||||
Notice that, even though the alias `M` is not available
|
||||
in the context the macro is expanded, the code above works
|
||||
because `D` still expands to `HashDict`.
|
||||
because `M` still expands to `Map`.
|
||||
|
||||
Similarly, even if we defined an alias with the same name
|
||||
before invoking a macro, it won't affect the macro's result:
|
||||
|
||||
defmodule Hygiene do
|
||||
alias HashDict, as: D
|
||||
alias Map, as: M
|
||||
|
||||
defmacro no_interference do
|
||||
quote do: D.new
|
||||
quote do: M.new
|
||||
end
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
alias SomethingElse, as: D
|
||||
Hygiene.no_interference #=> #HashDict<[]>
|
||||
alias SomethingElse, as: M
|
||||
Hygiene.no_interference #=> %{}
|
||||
|
||||
In some cases, you want to access an alias or a module defined
|
||||
in the caller. For such, you can use the `alias!` macro:
|
||||
@@ -1033,54 +1032,54 @@ defmodule Kernel.SpecialForms do
|
||||
following code:
|
||||
|
||||
defmodule Hygiene do
|
||||
defmacrop get_size do
|
||||
defmacrop get_length do
|
||||
quote do
|
||||
size("hello")
|
||||
length([1,2,3])
|
||||
end
|
||||
end
|
||||
|
||||
def return_size do
|
||||
import Kernel, except: [size: 1]
|
||||
get_size
|
||||
def return_length do
|
||||
import Kernel, except: [length: 1]
|
||||
get_length
|
||||
end
|
||||
end
|
||||
|
||||
Hygiene.return_size #=> 5
|
||||
Hygiene.return_length #=> 3
|
||||
|
||||
Notice how `return_size` returns 5 even though the `size/1`
|
||||
function is not imported. In fact, even if `return_size` imported
|
||||
a function from another module, it wouldn't affect the function
|
||||
result:
|
||||
Notice how `return_length` returns 5 even though the `length/1`
|
||||
function is not imported. In fact, even if `return_length`
|
||||
imported a function with the same name and arity from another
|
||||
module, it wouldn't affect the function result:
|
||||
|
||||
def return_size do
|
||||
import Dict, only: [size: 1]
|
||||
get_size
|
||||
def return_length do
|
||||
import String, only: [length: 1]
|
||||
get_length
|
||||
end
|
||||
|
||||
Calling this new `return_size` will still return 5 as result.
|
||||
Calling this new `return_length` will still return 3 as result.
|
||||
|
||||
Elixir is smart enough to delay the resolution to the latest
|
||||
moment possible. So, if you call `size("hello")` inside quote,
|
||||
but no `size/1` function is available, it is then expanded in
|
||||
moment possible. So, if you call `length([1, 2, 3])` inside quote,
|
||||
but no `length/1` function is available, it is then expanded in
|
||||
the caller:
|
||||
|
||||
defmodule Lazy do
|
||||
defmacrop get_size do
|
||||
import Kernel, except: [size: 1]
|
||||
defmacrop get_length do
|
||||
import Kernel, except: [length: 1]
|
||||
|
||||
quote do
|
||||
size([a: 1, b: 2])
|
||||
length("hello")
|
||||
end
|
||||
end
|
||||
|
||||
def return_size do
|
||||
import Kernel, except: [size: 1]
|
||||
import Dict, only: [size: 1]
|
||||
get_size
|
||||
def return_length do
|
||||
import Kernel, except: [length: 1]
|
||||
import String, only: [length: 1]
|
||||
get_length
|
||||
end
|
||||
end
|
||||
|
||||
Lazy.return_size #=> 2
|
||||
Lazy.return_length #=> 5
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
@@ -1252,7 +1251,7 @@ defmodule Kernel.SpecialForms do
|
||||
Note generators can also be used to filter as it removes any value
|
||||
that doesn't match the left side of `<-`:
|
||||
|
||||
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
|
||||
iex> for {:user, name} <- [user: "john", admin: "james", user: "meg"] do
|
||||
...> String.upcase(name)
|
||||
...> end
|
||||
["JOHN", "MEG"]
|
||||
@@ -1291,6 +1290,42 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro for(args)
|
||||
|
||||
@doc """
|
||||
Used to combine matching clauses.
|
||||
|
||||
Let's start with an example:
|
||||
|
||||
iex> opts = %{width: 10, height: 15}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, width * height}
|
||||
{:ok, 150}
|
||||
|
||||
If all clauses match, the `do` block is executed, returning its result.
|
||||
Otherwise the chain is aborted and a non-matched value is returned:
|
||||
|
||||
iex> opts = %{width: 10}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, width * height}
|
||||
:error
|
||||
|
||||
Similarly to `for`/1, variables bound inside `with/1` won't leak,
|
||||
and also it allows "bare expressions":
|
||||
|
||||
iex> width = nil
|
||||
iex> opts = %{width: 10, height: 15}
|
||||
iex> with {:ok, width} <- Map.fetch(opts, :width),
|
||||
...> double_width = width * 2,
|
||||
...> {:ok, height} <- Map.fetch(opts, :height),
|
||||
...> do: {:ok, double_width * height}
|
||||
{:ok, 300}
|
||||
iex> width
|
||||
nil
|
||||
|
||||
"""
|
||||
defmacro with(args)
|
||||
|
||||
@doc """
|
||||
Defines an anonymous function.
|
||||
|
||||
@@ -1676,7 +1711,7 @@ defmodule Kernel.SpecialForms do
|
||||
This means the VM no longer needs to keep the stacktrace once inside
|
||||
an else clause and so tail recursion is possible when using a `try`
|
||||
with a tail call as the final call inside an else clause. The same
|
||||
is `true` for `rescue` and `catch` clauses.
|
||||
is true for `rescue` and `catch` clauses.
|
||||
|
||||
## Variable handling
|
||||
|
||||
|
||||
@@ -1,173 +1,5 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc ~S"""
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
dynamically typed, as such typespecs are never used by the compiler to
|
||||
optimize or modify code. Still, using typespecs is useful as documentation and
|
||||
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
|
||||
analyze the code with typespecs to find bugs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec`, `@callback` and
|
||||
`@macrocallback` available in modules are handled by the equivalent macros
|
||||
defined by this module. See sub-sections "Defining a type" and "Defining a
|
||||
specification" below.
|
||||
|
||||
## Types and their syntax
|
||||
|
||||
The type syntax provided by Elixir is fairly similar to [the one in
|
||||
Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`) are
|
||||
expressed the same way: `pid()` or simply `pid`. Parameterized types are also
|
||||
supported (`list(integer)`) and so are remote types (`Enum.t`).
|
||||
|
||||
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
|
||||
`false`). All other types are built of unions of predefined types. Certain
|
||||
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
|
||||
|
||||
### Basic types
|
||||
|
||||
type :: any() # the top type, the set of all terms
|
||||
| none() # the bottom type, contains no terms
|
||||
| pid()
|
||||
| port()
|
||||
| reference()
|
||||
| tuple()
|
||||
| atom()
|
||||
| integer()
|
||||
| non_neg_integer() # 0, 1, 2, 3, ...
|
||||
| pos_integer() # 1, 2, 3, ...
|
||||
| neg_integer() # ..., -3, -2, -1
|
||||
| float()
|
||||
| map()
|
||||
| struct()
|
||||
| list(type)
|
||||
| nonempty_list(type)
|
||||
| improper_list(type1, type2)
|
||||
| maybe_improper_list(type1, type2)
|
||||
| Literals # Described in section "Literals"
|
||||
| Builtin # Described in section "Builtin-types"
|
||||
| Remotes # Described in section "Remotes"
|
||||
|
||||
### Literals
|
||||
|
||||
The following literals are also supported in typespecs:
|
||||
|
||||
type :: :atom ## Atoms
|
||||
| 1 ## Integers
|
||||
| 1..10 ## Integers from 1 to 10
|
||||
| 1.0 ## Floats
|
||||
|
||||
| <<>> ## Bitstrings
|
||||
| <<_ :: size>> # size is 0 or a positive integer
|
||||
| <<_ :: _ * unit>> # unit is an integer from 1 to 256
|
||||
| <<_ :: size * unit>>
|
||||
|
||||
| [type] ## Lists
|
||||
| [] # empty list
|
||||
| [...] # shorthand for nonempty_list(any())
|
||||
| [type, ...] # shorthand for nonempty_list(type)
|
||||
| [key: type] # keyword lists
|
||||
|
||||
| (... -> type) ## Functions
|
||||
| (... -> type) # any arity, returns type
|
||||
| (() -> type) # 0-arity, returns type
|
||||
| (type1, type2 -> type) # 2-arity, returns type
|
||||
|
||||
| %{} ## Maps
|
||||
| %{key: type} # map with key :key with value of type
|
||||
| %{type1 => type2} # map with keys of type1 with values of type2
|
||||
| %SomeStruct{}
|
||||
| %SomeStruct{key: type}
|
||||
|
||||
| {} ## Tuples
|
||||
| {:ok, type} # two element tuple with an atom and any type
|
||||
|
||||
### Built-in types
|
||||
|
||||
Those types are also provided by Elixir as shortcuts on top of the
|
||||
basic and literal types.
|
||||
|
||||
Built-in type | Defined as
|
||||
:---------------------- | :---------
|
||||
`term()` | `any()`
|
||||
`binary()` | `<< _ :: _ * 8 >>`
|
||||
`bitstring()` | `<< _ :: _ * 1 >>`
|
||||
`boolean()` | `false` \| `true`
|
||||
`byte()` | `0..255`
|
||||
`char()` | `0..0x10ffff`
|
||||
`number()` | `integer()` \| `float()`
|
||||
`char_list()` | `[char()]`
|
||||
`list()` | `[any()]`
|
||||
`maybe_improper_list()` | `maybe_improper_list(any(), any())`
|
||||
`nonempty_list()` | `nonempty_list(any())`
|
||||
`iodata()` | `iolist()` \| `binary()`
|
||||
`iolist()` | `maybe_improper_list(byte()` \| `binary()` \| `iolist(), binary()` \| `[])`
|
||||
`module()` | `atom()` \| `tuple()`
|
||||
`arity()` | `0..255`
|
||||
`mfa()` | `{atom(), atom(), arity()}`
|
||||
`node()` | `atom()`
|
||||
`timeout()` | `:infinity` \| `non_neg_integer()`
|
||||
`no_return()` | `none()`
|
||||
`fun()` | `(... -> any)`
|
||||
`struct()` | `%{__struct__: atom()}`
|
||||
|
||||
### Remote types
|
||||
|
||||
Any module is also able to define their own type and the modules in
|
||||
Elixir are no exception. For example, a string is `String.t`, a
|
||||
range is `Range.t`, any enumerable can be `Enum.t` and so on.
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
A type defined with `@typep` is private. An opaque type, defined with
|
||||
`@opaque` is a type where the internal structure of the type will not be
|
||||
visible, but the type is still public.
|
||||
|
||||
Types can be parameterized by defining variables as parameters, these variables
|
||||
can then be used to define the type.
|
||||
|
||||
@type dict(key, value) :: [{key, value}]
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@callback function_name(type1, type2) :: return_type
|
||||
@macrocallback macro_name(type1, type2) :: Macro.t
|
||||
|
||||
Callbacks are used to define the callbacks functions of behaviours (see
|
||||
`Behaviour`).
|
||||
|
||||
Guards can be used to restrict type variables given as arguments to the
|
||||
function.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: atom
|
||||
|
||||
Type variables with no restriction can also be defined.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: var
|
||||
|
||||
Specifications can be overloaded just like ordinary functions.
|
||||
|
||||
@spec function(integer) :: atom
|
||||
@spec function(atom) :: integer
|
||||
|
||||
## Notes
|
||||
|
||||
Elixir discourages the use of type `string` as it might be confused with
|
||||
binaries which are referred to as "strings" in Elixir (as opposed to character
|
||||
lists). In order to use the type that is called `string` in Erlang, one has to
|
||||
use the `char_list` type which is a synonym for `string`. If you use `string`,
|
||||
you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated on by functions in
|
||||
the `String` module), use `String.t` type instead.
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
@@ -508,13 +340,9 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp get_doc_info(table, attr, caller) do
|
||||
# TODO: Use :ets.take/2 with Erlang 18
|
||||
case :ets.lookup(table, attr) do
|
||||
[{^attr, {line, doc}}] ->
|
||||
:ets.delete(table, attr)
|
||||
{line, doc}
|
||||
[] ->
|
||||
{caller.line, nil}
|
||||
case :ets.take(table, attr) do
|
||||
[{^attr, {line, doc}}] -> {line, doc}
|
||||
[] -> {caller.line, nil}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -846,9 +674,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c :: binary-size(1), rest :: binary>> ->
|
||||
<<"_", c::binary-1, rest::binary>> ->
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: binary-size(1), rest :: binary>> ->
|
||||
<<c::binary-1, rest::binary>> ->
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
@@ -894,18 +722,17 @@ defmodule Kernel.Typespec do
|
||||
{:type, line(meta), :map, :any}
|
||||
end
|
||||
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
defp typespec({:%{}, meta, fields} = map, vars, caller) do
|
||||
fields =
|
||||
# TODO: Remove else once we support only OTP >18
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
:lists.map(fn {k, v} ->
|
||||
:lists.map(fn
|
||||
{k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
|
||||
end, fields)
|
||||
else
|
||||
:lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end, fields)
|
||||
end
|
||||
{:|, _, [_, _]} ->
|
||||
compile_error(caller, "invalid map specification. When using the | operator in the map key, " <>
|
||||
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}")
|
||||
_ ->
|
||||
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
|
||||
end, fields)
|
||||
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
@@ -921,14 +748,16 @@ defmodule Kernel.Typespec do
|
||||
module.__struct__
|
||||
end
|
||||
|
||||
struct = struct |> Map.from_struct |> Map.to_list
|
||||
|
||||
unless Keyword.keyword?(fields) do
|
||||
compile_error(caller, "expected key-value pairs in struct #{Macro.to_string(name)}")
|
||||
end
|
||||
|
||||
struct =
|
||||
types =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, quote do: term()}
|
||||
end, Map.to_list(struct))
|
||||
{field, Keyword.get(fields, field, quote(do: term()))}
|
||||
end, struct)
|
||||
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(struct, field) do
|
||||
@@ -936,8 +765,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end, fields)
|
||||
|
||||
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
|
||||
typespec({:%{}, meta, fields}, vars, caller)
|
||||
typespec({:%{}, meta, [__struct__: module] ++ types}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle records
|
||||
@@ -948,9 +776,9 @@ defmodule Kernel.Typespec do
|
||||
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
|
||||
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
||||
keyword when is_list(keyword) ->
|
||||
keyword =
|
||||
types =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, quote do: term()}
|
||||
Keyword.get(fields, field, quote(do: term()))
|
||||
end, keyword)
|
||||
|
||||
:lists.foreach(fn {field, _} ->
|
||||
@@ -959,9 +787,6 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end, fields)
|
||||
|
||||
fields = Keyword.merge(keyword, fields)
|
||||
types = Keyword.values(fields)
|
||||
|
||||
typespec({:{}, meta, [atom|types]}, vars, caller)
|
||||
_ ->
|
||||
compile_error(caller, "unknown record #{inspect atom}")
|
||||
@@ -1068,14 +893,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
arity = length(arguments)
|
||||
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
|
||||
{type, line(meta), name, arguments}
|
||||
else
|
||||
{:type, line(meta), name, arguments}
|
||||
end
|
||||
arity = length(arguments)
|
||||
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
|
||||
{type, line(meta), name, arguments}
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
defmodule Kernel.Def do
|
||||
import Kernel, except: [destructure: 2, defdelegate: 2, defstruct: 2]
|
||||
|
||||
defmodule Kernel.Utils do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Callback invoked at compile time for `defdelegate`.
|
||||
"""
|
||||
def delegate(fun, opts) do
|
||||
def destructure(list, count) when is_list(list), do: destructure_list(list, count)
|
||||
def destructure(nil, count), do: destructure_nil(count)
|
||||
|
||||
defp destructure_list(_, 0), do: []
|
||||
defp destructure_list([], count), do: destructure_nil(count)
|
||||
defp destructure_list([h|t], count), do: [h|destructure_list(t, count - 1)]
|
||||
|
||||
defp destructure_nil(0), do: []
|
||||
defp destructure_nil(count), do: [nil|destructure_nil(count - 1)]
|
||||
|
||||
def defdelegate(fun, opts, env) do
|
||||
append_first = Keyword.get(opts, :append_first, false)
|
||||
|
||||
{name, args} =
|
||||
@@ -13,6 +22,8 @@ defmodule Kernel.Def do
|
||||
_ -> raise ArgumentError, "invalid syntax in defdelegate #{Macro.to_string(fun)}"
|
||||
end
|
||||
|
||||
:ok = check_defdelegate_args(args, env)
|
||||
|
||||
as_args =
|
||||
case append_first and args != [] do
|
||||
true -> tl(args) ++ [hd(args)]
|
||||
@@ -23,10 +34,18 @@ defmodule Kernel.Def do
|
||||
{name, args, as, as_args}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked at compile time for `defstruct`.
|
||||
"""
|
||||
def struct(module, fields) do
|
||||
# TODO: Convert this to an error on 1.3
|
||||
defp check_defdelegate_args([], _env),
|
||||
do: :ok
|
||||
defp check_defdelegate_args([{var, _, mod}|rest], env) when is_atom(var) and is_atom(mod),
|
||||
do: check_defdelegate_args(rest, env)
|
||||
defp check_defdelegate_args([code|_], env) do
|
||||
:elixir_errors.warn(env.line, env.file,
|
||||
"defdelegate/2 will only accept variable names in upcoming versions, " <>
|
||||
"got: #{Macro.to_string(code)}")
|
||||
end
|
||||
|
||||
def defstruct(module, fields) do
|
||||
case fields do
|
||||
fs when is_list(fs) -> :ok
|
||||
other ->
|
||||
+198
-95
@@ -1,13 +1,15 @@
|
||||
defmodule Keyword do
|
||||
@moduledoc """
|
||||
A keyword is a list of tuples where the first element
|
||||
of the tuple is an atom and the second element can be
|
||||
any value.
|
||||
A set of functions for working with keywords.
|
||||
|
||||
A keyword is a list of 2-element tuples where the first
|
||||
element of the tuple is an atom and the second element
|
||||
can be any value.
|
||||
|
||||
A keyword may have duplicated keys so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
behave exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
behave exactly as a dictionary so they work similarly to
|
||||
the functions you would find in the `Map` module.
|
||||
|
||||
For example, `Keyword.get/3` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
@@ -27,7 +29,6 @@ defmodule Keyword do
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@behaviour Dict
|
||||
|
||||
@type key :: atom
|
||||
@type value :: any
|
||||
@@ -37,18 +38,38 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Returns `true` if `term` is a keyword list; otherwise returns `false`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.keyword?([])
|
||||
true
|
||||
iex> Keyword.keyword?([a: 1])
|
||||
true
|
||||
iex> Keyword.keyword?([{Foo, 1}])
|
||||
true
|
||||
iex> Keyword.keyword?([{}])
|
||||
false
|
||||
iex> Keyword.keyword?([:key])
|
||||
false
|
||||
iex> Keyword.keyword?(%{})
|
||||
false
|
||||
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?(term)
|
||||
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key),
|
||||
do: keyword?(rest)
|
||||
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key), do: keyword?(rest)
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
@doc """
|
||||
Returns an empty keyword list, i.e. an empty list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new()
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec new :: t
|
||||
def new, do: []
|
||||
@@ -57,13 +78,16 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
Unlike `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
Unlike `Enum.into(enumerable, [])`, `Keyword.new(enumerable)`
|
||||
guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([{:b, 1}, {:a, 2}])
|
||||
[a: 2, b: 1]
|
||||
[b: 1, a: 2]
|
||||
|
||||
iex> Keyword.new([{:a, 1}, {:a, 2}, {:a, 3}])
|
||||
[a: 3]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: t
|
||||
@@ -81,37 +105,45 @@ defmodule Keyword do
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end)
|
||||
[b: :b, a: :a]
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, (term -> {key, value})) :: t
|
||||
def new(pairs, transform) when is_function(transform, 1) do
|
||||
def new(pairs, transform) do
|
||||
fun = fn el, acc ->
|
||||
{k, v} = transform.(el)
|
||||
put_new(acc, k, v)
|
||||
end
|
||||
:lists.foldr(fun, [], Enum.reverse(pairs))
|
||||
:lists.foldl(fun, [], Enum.reverse(pairs))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If `key` does not exist, return the default value (`nil` if no default value).
|
||||
If `key` does not exist, return the default value
|
||||
(`nil` if no default value).
|
||||
|
||||
If duplicated entries exist, the first one is returned.
|
||||
Use `get_values/2` to retrieve all entries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get([], :a)
|
||||
nil
|
||||
iex> Keyword.get([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.get([a: 1], :b)
|
||||
nil
|
||||
|
||||
iex> Keyword.get([a: 1], :b, 3)
|
||||
3
|
||||
|
||||
With duplicated keys:
|
||||
|
||||
iex> Keyword.get([a: 1, a: 2], :a, 3)
|
||||
1
|
||||
iex> Keyword.get([a: 1, a: 2], :b, 3)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -128,7 +160,7 @@ defmodule Keyword do
|
||||
If `key` does not exist, lazily evaluates `fun` and returns its result.
|
||||
|
||||
This is useful if the default value is very expensive to calculate or
|
||||
generally difficult to set-up and tear-down again.
|
||||
generally difficult to setup and teardown again.
|
||||
|
||||
If duplicated entries exist, the first one is returned.
|
||||
Use `get_values/2` to retrieve all entries.
|
||||
@@ -138,12 +170,12 @@ defmodule Keyword do
|
||||
iex> keyword = [a: 1]
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> :result
|
||||
...> 13
|
||||
...> end
|
||||
iex> Keyword.get_lazy(keyword, :a, fun)
|
||||
1
|
||||
iex> Keyword.get_lazy(keyword, :b, fun)
|
||||
:result
|
||||
13
|
||||
|
||||
"""
|
||||
@spec get_lazy(t, key, (() -> value)) :: value
|
||||
@@ -168,13 +200,18 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get_and_update [a: 1], :a, fn(current_value) ->
|
||||
iex> Keyword.get_and_update([a: 1], :a, fn current_value ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end
|
||||
...> end)
|
||||
{1, [a: "new value!"]}
|
||||
|
||||
iex> Keyword.get_and_update([a: 1], :b, fn current_value ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
{nil, [b: "new value!", a: 1]}
|
||||
|
||||
"""
|
||||
@spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
|
||||
@spec get_and_update(t, key, (value -> {get, value})) :: {get, t} when get: term
|
||||
def get_and_update(keywords, key, fun)
|
||||
when is_list(keywords) and is_atom(key),
|
||||
do: get_and_update(keywords, [], key, fun)
|
||||
@@ -192,6 +229,48 @@ defmodule Keyword do
|
||||
{get, [{key, update}|:lists.reverse(acc)]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value from `key` and updates it. Raises if there is no `key`.
|
||||
|
||||
This `fun` argument receives the value of `key` and must return a
|
||||
two-elements tuple: the "get" value (the retrieved value, which can be
|
||||
operated on before being returned) and the new value to be stored under
|
||||
`key`.
|
||||
|
||||
The returned value is a tuple with the "get" value returned by `fun` and a new
|
||||
keyword list with the updated value under `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get_and_update!([a: 1], :a, fn(current_value) ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
{1, [a: "new value!"]}
|
||||
|
||||
iex> Keyword.get_and_update!([a: 1], :b, fn current_value ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec get_and_update!(t, key, (value -> {get, value})) :: {get, t} | no_return when get: term
|
||||
def get_and_update!(keywords, key, fun) do
|
||||
get_and_update!(keywords, key, fun, [])
|
||||
end
|
||||
|
||||
defp get_and_update!([{key, value}|keywords], key, fun, acc) do
|
||||
{get, value} = fun.(value)
|
||||
{get, :lists.reverse(acc, [{key, value}|delete(keywords, key)])}
|
||||
end
|
||||
|
||||
defp get_and_update!([{_, _} = e|keywords], key, fun, acc) do
|
||||
get_and_update!(keywords, key, fun, [e|acc])
|
||||
end
|
||||
|
||||
defp get_and_update!([], key, _fun, acc) when is_atom(key) do
|
||||
raise(KeyError, key: key, term: acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
|
||||
@@ -201,7 +280,6 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.fetch([a: 1], :a)
|
||||
{:ok, 1}
|
||||
|
||||
iex> Keyword.fetch([a: 1], :b)
|
||||
:error
|
||||
|
||||
@@ -223,7 +301,6 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
@@ -241,6 +318,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get_values([], :a)
|
||||
[]
|
||||
iex> Keyword.get_values([a: 1], :a)
|
||||
[1]
|
||||
iex> Keyword.get_values([a: 1, a: 2], :a)
|
||||
[1, 2]
|
||||
|
||||
@@ -251,7 +332,6 @@ defmodule Keyword do
|
||||
{k, v} when k === key -> {true, v}
|
||||
{_, _} -> false
|
||||
end
|
||||
|
||||
:lists.filtermap(fun, keywords)
|
||||
end
|
||||
|
||||
@@ -264,7 +344,6 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.keys([a: 1, b: 2])
|
||||
[:a, :b]
|
||||
|
||||
iex> Keyword.keys([a: 1, b: 2, a: 3])
|
||||
[:a, :b, :a]
|
||||
|
||||
@@ -277,10 +356,14 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Returns all values from the keyword list.
|
||||
|
||||
Values from duplicated keys will be kept in the final list of values.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.values([a: 1, b: 2])
|
||||
[1, 2]
|
||||
iex> Keyword.values([a: 1, b: 2, a: 3])
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
@@ -297,12 +380,12 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a, 1)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a, 5)
|
||||
[b: 2]
|
||||
iex> Keyword.delete([a: 1], :a, 5)
|
||||
[a: 1]
|
||||
iex> Keyword.delete([a: 1], :b, 5)
|
||||
[a: 1]
|
||||
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
@@ -321,10 +404,8 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
@@ -343,7 +424,6 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2, a: 3]
|
||||
|
||||
iex> Keyword.delete_first([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
@@ -361,9 +441,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
iex> Keyword.put([a: 1, b: 2], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
@@ -377,8 +458,8 @@ defmodule Keyword do
|
||||
Evaluates `fun` and puts the result under `key`
|
||||
in keyword list unless `key` is already present.
|
||||
|
||||
This is useful if the value is very expensive to calculate or generally
|
||||
difficult to set-up and tear-down again.
|
||||
This is useful if the value is very expensive to calculate or
|
||||
generally difficult to setup and teardown again.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -410,7 +491,6 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.put_new([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
|
||||
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
@@ -433,6 +513,10 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
|
||||
true
|
||||
iex> Keyword.equal?([a: 1, b: 2], [b: 1, a: 2])
|
||||
false
|
||||
iex> Keyword.equal?([a: 1, b: 2, a: 3], [b: 2, a: 3, a: 1])
|
||||
true
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
@@ -443,44 +527,72 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the one given in the second argument wins.
|
||||
All keys, including duplicated keys, given in `keywords2` will be added
|
||||
to `keywords1`, overriding any existing one.
|
||||
|
||||
There are no guarantees about the order of keys in the returned keyword.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4])
|
||||
[a: 3, d: 4, b: 2]
|
||||
[b: 2, a: 3, d: 4]
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4, a: 5])
|
||||
[b: 2, a: 3, d: 4, a: 5]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(keywords1, keywords2) when is_list(keywords1) and is_list(keywords2) do
|
||||
fun = fn {k, _v} -> not has_key?(keywords2, k) end
|
||||
keywords2 ++ :lists.filter(fun, keywords1)
|
||||
:lists.filter(fun, keywords1) ++ keywords2
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the given function is invoked to solve conflicts.
|
||||
All keys, including duplicated keys, given in `keywords2` will be added
|
||||
to `keywords1`. The given function will be invoked to solve conflicts.
|
||||
|
||||
If `keywords2` has duplicate keys, the given function will be invoked
|
||||
for each matching pair in `keywords1`.
|
||||
|
||||
There are no guarantees about the order of keys in the returned keyword.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
[b: 2, a: 4, d: 4]
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4, a: 5], fn :a, v1, v2 ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
[b: 2, a: 4, d: 4, a: 5]
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2, a: 3], [a: 3, d: 4, a: 5], fn :a, v1, v2 ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
[b: 2, a: 4, d: 4, a: 8]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(keywords1, keywords2, fun) when is_list(keywords1) and is_list(keywords2) do
|
||||
do_merge(keywords2, keywords1, fun)
|
||||
do_merge(keywords2, [], keywords1, keywords1, fun)
|
||||
end
|
||||
|
||||
defp do_merge([{k, v2}|t], acc, fun) do
|
||||
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
|
||||
defp do_merge([{k, v2}|t], acc, rest, original, fun) do
|
||||
case :lists.keyfind(k, 1, original) do
|
||||
{^k, v1} ->
|
||||
do_merge(t, [{k, fun.(k, v1, v2)}|acc],
|
||||
delete(rest, k), :lists.keydelete(k, 1, original), fun)
|
||||
false ->
|
||||
do_merge(t, [{k, v2}|acc], rest, original, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_merge([], acc, _fun) do
|
||||
acc
|
||||
defp do_merge([], acc, rest, _original, _fun) do
|
||||
rest ++ :lists.reverse(acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -490,7 +602,6 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.has_key?([a: 1], :a)
|
||||
true
|
||||
|
||||
iex> Keyword.has_key?([a: 1], :b)
|
||||
false
|
||||
|
||||
@@ -512,6 +623,8 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
|
||||
[a: 2]
|
||||
iex> Keyword.update!([a: 1, a: 2], :a, &(&1 * 2))
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
@@ -546,7 +659,8 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1, a: 2], :a, 13, &(&1 * 2))
|
||||
[a: 2]
|
||||
iex> Keyword.update([a: 1], :b, 11, &(&1 * 2))
|
||||
[a: 1, b: 11]
|
||||
|
||||
@@ -578,13 +692,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{[a: 1, c: 3, a: 5], [b: 2, d: 4]}
|
||||
iex> Keyword.split([a: 1, b: 2, c: 3], [:a, :c, :e])
|
||||
{[a: 1, c: 3], [b: 2]}
|
||||
iex> Keyword.split([a: 1, b: 2, c: 3, a: 4], [:a, :c, :e])
|
||||
{[a: 1, c: 3, a: 4], [b: 2]}
|
||||
|
||||
"""
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
@@ -608,12 +719,9 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.take(d, [:a, :c, :e])
|
||||
iex> Keyword.take([a: 1, b: 2, c: 3], [:a, :c, :e])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.take(d, [:a, :c, :e])
|
||||
iex> Keyword.take([a: 1, b: 2, c: 3, a: 5], [:a, :c, :e])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
@@ -628,12 +736,9 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
iex> Keyword.drop([a: 1, b: 2, c: 3], [:b, :d])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
iex> Keyword.drop([a: 1, b: 2, b: 3, c: 3, a: 5], [:b, :d])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
@@ -642,24 +747,20 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without `key`.
|
||||
Returns and removes all values associated with `key` in the keyword list.
|
||||
|
||||
All duplicated keys are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.pop [a: 1], :a
|
||||
iex> Keyword.pop([a: 1], :a)
|
||||
{1, []}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b
|
||||
iex> Keyword.pop([a: 1], :b)
|
||||
{nil, [a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b, 3
|
||||
iex> Keyword.pop([a: 1], :b, 3)
|
||||
{3, [a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1, a: 2], :a
|
||||
iex> Keyword.pop([a: 1, a: 2], :a)
|
||||
{1, []}
|
||||
|
||||
"""
|
||||
@@ -674,11 +775,10 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without `key`.
|
||||
Lazily returns and removes all values associated with `key` in the keyword list.
|
||||
|
||||
This is useful if the default value is very expensive to calculate or
|
||||
generally difficult to set-up and tear-down again.
|
||||
generally difficult to setup and teardown again.
|
||||
|
||||
All duplicated keys are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
@@ -688,12 +788,12 @@ defmodule Keyword do
|
||||
iex> keyword = [a: 1]
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> :result
|
||||
...> 13
|
||||
...> end
|
||||
iex> Keyword.pop_lazy(keyword, :a, fun)
|
||||
{1, []}
|
||||
iex> Keyword.pop_lazy(keyword, :b, fun)
|
||||
{:result, [a: 1]}
|
||||
{13, [a: 1]}
|
||||
|
||||
"""
|
||||
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
|
||||
@@ -708,9 +808,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without that particular occurrence
|
||||
of `key`.
|
||||
Returns and removes the first value associated with `key` in the keyword list.
|
||||
|
||||
Duplicated keys are not removed.
|
||||
|
||||
@@ -718,13 +816,10 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :a
|
||||
{1, []}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b
|
||||
{nil, [a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b, 3
|
||||
{3, [a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1, a: 2], :a
|
||||
{1, [a: 2]}
|
||||
|
||||
@@ -737,15 +832,23 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
# Dict callbacks
|
||||
@doc """
|
||||
Returns the keyword list itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.to_list([a: 1])
|
||||
[a: 1]
|
||||
|
||||
"""
|
||||
def to_list(keyword) when is_list(keyword) do
|
||||
keyword
|
||||
end
|
||||
|
||||
# TODO: Deprecate by 1.3
|
||||
# TODO: Remove by 1.4
|
||||
@doc false
|
||||
def size(keyword) do
|
||||
length(keyword)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(keyword) do
|
||||
keyword
|
||||
end
|
||||
end
|
||||
|
||||
+40
-9
@@ -1,19 +1,45 @@
|
||||
defmodule List do
|
||||
@moduledoc """
|
||||
Implements functions that only make sense for lists
|
||||
and cannot be part of the Enum protocol. In general,
|
||||
favor using the Enum API instead of List.
|
||||
Specialized functions that only work on lists.
|
||||
|
||||
Some functions in this module expect an index. Index
|
||||
access for list is linear. Negative indexes are also
|
||||
In general, favor using the `Enum` API instead of `List`.
|
||||
|
||||
Index access for list is linear. Negative indexes are also
|
||||
supported but they imply the list will be iterated twice,
|
||||
one to calculate the proper index and another to the
|
||||
one to calculate the proper index and another to perform the
|
||||
operation.
|
||||
|
||||
A decision was taken to delegate most functions to
|
||||
Erlang's standard library but follow Elixir's convention
|
||||
of receiving the target (in this case, a list) as the
|
||||
of receiving the subject (in this case, a list) as the
|
||||
first argument.
|
||||
|
||||
## Char lists
|
||||
|
||||
If a list is made of non-negative integers, it can also
|
||||
be called as a char list. Elixir uses single quotes to
|
||||
define char lists:
|
||||
|
||||
iex> 'héllo'
|
||||
[104, 233, 108, 108, 111]
|
||||
|
||||
In particular, char lists may be printed back in single
|
||||
quotes if they contain only ASCII-printable codepoints:
|
||||
|
||||
iex> 'abc'
|
||||
'abc'
|
||||
|
||||
The rationale behind this behaviour is to better support
|
||||
Erlang libraries which may return text as char lists
|
||||
instead of Elixir strings. One example of such functions
|
||||
is `Application.loaded_applications`:
|
||||
|
||||
Application.loaded_applications
|
||||
#=> [{:stdlib, 'ERTS CXC 138 10', '2.6'},
|
||||
{:compiler, 'ERTS CXC 138 10', '6.0.1'},
|
||||
{:elixir, 'elixir', '1.0.0'},
|
||||
{:kernel, 'ERTS CXC 138 10', '4.1'},
|
||||
{:logger, 'logger', '1.0.0'}]
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@@ -485,7 +511,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a char list to an existing atom. Raises an `ArguementError`
|
||||
Converts a char list to an existing atom. Raises an `ArgumentError`
|
||||
if the atom does not exist.
|
||||
|
||||
Currently Elixir does not support conversions from char lists
|
||||
@@ -591,7 +617,12 @@ defmodule List do
|
||||
"""
|
||||
@spec to_string(:unicode.charlist) :: String.t
|
||||
def to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
try do
|
||||
:unicode.characters_to_binary(list)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
raise ArgumentError, "cannot convert list to string. The list must contain only integers, strings or nested such lists; got: #{inspect list}"
|
||||
else
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
|
||||
+211
-77
@@ -20,8 +20,6 @@ defmodule Macro do
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST)"
|
||||
@type t :: expr | {t, t} | atom | number | binary | pid | fun | [t]
|
||||
|
||||
@typedoc "Expr node (remaining ones are literals)"
|
||||
@type expr :: {expr | atom, Keyword.t, atom | [t]}
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
@@ -88,13 +86,17 @@ defmodule Macro do
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
bad_pipe(expr, call_args)
|
||||
raise ArgumentError, bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
def pipe(expr, {tuple_or_map, _, _} = call_args, _integer) when tuple_or_map in [:{}, :%{}] do
|
||||
raise ArgumentError, bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
def pipe(expr, {call, _, [_, _]} = call_args, _integer)
|
||||
when call in unquote(@binary_ops) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
|
||||
"the #{to_string call} operator can only take two arguments"
|
||||
"the #{to_string call} operator can only take two arguments"
|
||||
end
|
||||
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
@@ -106,12 +108,12 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
bad_pipe(expr, call_args)
|
||||
raise ArgumentError, bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
defp bad_pipe(expr, call_args) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
|
||||
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
|
||||
"cannot pipe #{to_string expr} into #{to_string call_args}, " <>
|
||||
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -165,6 +167,52 @@ defmodule Macro do
|
||||
{var, [], context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, traversal of quoted expressions
|
||||
using an accumulator.
|
||||
"""
|
||||
@spec traverse(t, any, (t, any -> {t, any}), (t, any -> {t, any})) :: {t, any}
|
||||
def traverse(ast, acc, pre, post) when is_function(pre, 2) and is_function(post, 2) do
|
||||
{ast, acc} = pre.(ast, acc)
|
||||
do_traverse(ast, acc, pre, post)
|
||||
end
|
||||
|
||||
defp do_traverse({form, meta, args}, acc, pre, post) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = pre.(form, acc)
|
||||
{form, acc} = do_traverse(form, acc, pre, post)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
|
||||
{x, acc} = pre.(x, acc)
|
||||
do_traverse(x, acc, pre, post)
|
||||
end)
|
||||
end
|
||||
|
||||
post.({form, meta, args}, acc)
|
||||
end
|
||||
|
||||
defp do_traverse({left, right}, acc, pre, post) do
|
||||
{left, acc} = pre.(left, acc)
|
||||
{left, acc} = do_traverse(left, acc, pre, post)
|
||||
{right, acc} = pre.(right, acc)
|
||||
{right, acc} = do_traverse(right, acc, pre, post)
|
||||
post.({left, right}, acc)
|
||||
end
|
||||
|
||||
defp do_traverse(list, acc, pre, post) when is_list(list) do
|
||||
{list, acc} = Enum.map_reduce(list, acc, fn x, acc ->
|
||||
{x, acc} = pre.(x, acc)
|
||||
do_traverse(x, acc, pre, post)
|
||||
end)
|
||||
post.(list, acc)
|
||||
end
|
||||
|
||||
defp do_traverse(x, acc, _pre, post) do
|
||||
post.(x, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions.
|
||||
"""
|
||||
@@ -179,43 +227,7 @@ defmodule Macro do
|
||||
"""
|
||||
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
|
||||
def prewalk(ast, acc, fun) when is_function(fun, 2) do
|
||||
{ast, acc} = fun.(ast, acc)
|
||||
do_prewalk(ast, acc, fun)
|
||||
end
|
||||
|
||||
defp do_prewalk({form, meta, args}, acc, fun) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = fun.(form, acc)
|
||||
{form, acc} = do_prewalk(form, acc, fun)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
|
||||
{x, acc} = fun.(x, acc)
|
||||
do_prewalk(x, acc, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
{{form, meta, args}, acc}
|
||||
end
|
||||
|
||||
defp do_prewalk({left, right}, acc, fun) do
|
||||
{left, acc} = fun.(left, acc)
|
||||
{left, acc} = do_prewalk(left, acc, fun)
|
||||
{right, acc} = fun.(right, acc)
|
||||
{right, acc} = do_prewalk(right, acc, fun)
|
||||
{{left, right}, acc}
|
||||
end
|
||||
|
||||
defp do_prewalk(list, acc, fun) when is_list(list) do
|
||||
Enum.map_reduce(list, acc, fn x, acc ->
|
||||
{x, acc} = fun.(x, acc)
|
||||
do_prewalk(x, acc, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_prewalk(x, acc, _fun) do
|
||||
{x, acc}
|
||||
traverse(ast, acc, fun, fn x, a -> {x, a} end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -232,34 +244,7 @@ defmodule Macro do
|
||||
"""
|
||||
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
|
||||
def postwalk(ast, acc, fun) when is_function(fun, 2) do
|
||||
do_postwalk(ast, acc, fun)
|
||||
end
|
||||
|
||||
defp do_postwalk({form, meta, args}, acc, fun) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = do_postwalk(form, acc, fun)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, &do_postwalk(&1, &2, fun))
|
||||
end
|
||||
|
||||
fun.({form, meta, args}, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk({left, right}, acc, fun) do
|
||||
{left, acc} = do_postwalk(left, acc, fun)
|
||||
{right, acc} = do_postwalk(right, acc, fun)
|
||||
fun.({left, right}, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk(list, acc, fun) when is_list(list) do
|
||||
{list, acc} = Enum.map_reduce(list, acc, &do_postwalk(&1, &2, fun))
|
||||
fun.(list, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk(x, acc, fun) do
|
||||
fun.(x, acc)
|
||||
traverse(ast, acc, fn x, a -> {x, a} end, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -493,14 +478,19 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_string({:<<>>, _, args} = ast, fun) do
|
||||
def to_string({:<<>>, _, parts} = ast, fun) do
|
||||
if interpolated?(ast) do
|
||||
fun.(ast, interpolate(ast, fun))
|
||||
else
|
||||
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
|
||||
"<" <> rest -> "<< <" <> rest <> " >>"
|
||||
rest -> "<<" <> rest <> ">>"
|
||||
result = Enum.map_join(parts, ", ", fn(part) ->
|
||||
str = bitpart_to_string(part, fun)
|
||||
if :binary.first(str) == ?< or :binary.last(str) == ?> do
|
||||
"(" <> str <> ")"
|
||||
else
|
||||
str
|
||||
end
|
||||
end)
|
||||
fun.(ast, "<<" <> result <> ">>")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -537,6 +527,12 @@ defmodule Macro do
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
|
||||
# Ranges
|
||||
def to_string({:.., _, args} = ast, fun) do
|
||||
range = Enum.map_join(args, "..", &to_string(&1, fun))
|
||||
fun.(ast, range)
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_string([{:->, _, _}|_] = ast, fun) do
|
||||
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
|
||||
@@ -575,6 +571,10 @@ defmodule Macro do
|
||||
fun.(ast, "&" <> to_string(mod, fun) <> "." <> Atom.to_string(name) <> "/" <> to_string(arity, fun))
|
||||
end
|
||||
|
||||
def to_string({:&, _, [arg]} = ast, fun) when not is_integer(arg) do
|
||||
fun.(ast, "&(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
@@ -590,6 +590,11 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({{:., _, [Access, :get]}, _, [{op, _, _} = left, right]} = ast, fun)
|
||||
when op in unquote(@binary_ops) do
|
||||
fun.(ast, "(" <> to_string(left, fun) <> ")" <> to_string([right], fun))
|
||||
end
|
||||
|
||||
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string([right], fun))
|
||||
end
|
||||
@@ -629,6 +634,30 @@ defmodule Macro do
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, []))
|
||||
|
||||
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
|
||||
result =
|
||||
op_to_string(left, fun, :::, :left) <>
|
||||
"::" <>
|
||||
bitmods_to_string(right, fun, :::, :right)
|
||||
fun.(ast, result)
|
||||
end
|
||||
|
||||
defp bitpart_to_string(ast, fun) do
|
||||
to_string(ast, fun)
|
||||
end
|
||||
|
||||
defp bitmods_to_string({:-, _, [left, right]} = ast, fun, _, _) do
|
||||
result =
|
||||
bitmods_to_string(left, fun, :-, :left) <>
|
||||
"-" <>
|
||||
bitmods_to_string(right, fun, :-, :right)
|
||||
fun.(ast, result)
|
||||
end
|
||||
|
||||
defp bitmods_to_string(other, fun, parent_op, side) do
|
||||
op_to_string(other, fun, parent_op, side)
|
||||
end
|
||||
|
||||
# Block keywords
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
@@ -666,7 +695,7 @@ defmodule Macro do
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_list(args) do
|
||||
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_atom(func) and is_list(args) do
|
||||
sigil =
|
||||
case Atom.to_string(func) do
|
||||
<<"sigil_", name>> ->
|
||||
@@ -1017,4 +1046,109 @@ defmodule Macro do
|
||||
defp expand_until({tree, false}, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given atom or binary to underscore format.
|
||||
|
||||
If an atom is given, it is assumed to be an Elixir module,
|
||||
so it is converted to a binary and then processed.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.underscore "FooBar"
|
||||
"foo_bar"
|
||||
|
||||
iex> Macro.underscore "Foo.Bar"
|
||||
"foo/bar"
|
||||
|
||||
iex> Macro.underscore Foo.Bar
|
||||
"foo/bar"
|
||||
|
||||
In general, `underscore` can be thought of as the reverse of
|
||||
`camelize`, however, in some cases formatting may be lost:
|
||||
|
||||
iex> Macro.underscore "SAPExample"
|
||||
"sap_example"
|
||||
|
||||
iex> Macro.camelize "sap_example"
|
||||
"SapExample"
|
||||
|
||||
"""
|
||||
def underscore(atom) when is_atom(atom) do
|
||||
"Elixir." <> rest = Atom.to_string(atom)
|
||||
underscore(rest)
|
||||
end
|
||||
|
||||
def underscore(""), do: ""
|
||||
|
||||
def underscore(<<h, t::binary>>) do
|
||||
<<to_lower_char(h)>> <> do_underscore(t, h)
|
||||
end
|
||||
|
||||
defp do_underscore(<<h, t, rest::binary>>, _)
|
||||
when (h >= ?A and h <= ?Z) and not (t >= ?A and t <= ?Z) and t != ?. do
|
||||
<<?_, to_lower_char(h), t>> <> do_underscore(rest, t)
|
||||
end
|
||||
|
||||
defp do_underscore(<<h, t::binary>>, prev)
|
||||
when (h >= ?A and h <= ?Z) and not (prev >= ?A and prev <= ?Z) do
|
||||
<<?_, to_lower_char(h)>> <> do_underscore(t, h)
|
||||
end
|
||||
|
||||
defp do_underscore(<<?., t::binary>>, _) do
|
||||
<<?/>> <> underscore(t)
|
||||
end
|
||||
|
||||
defp do_underscore(<<h, t::binary>>, _) do
|
||||
<<to_lower_char(h)>> <> do_underscore(t, h)
|
||||
end
|
||||
|
||||
defp do_underscore(<<>>, _) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to CamelCase format.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.camelize "foo_bar"
|
||||
"FooBar"
|
||||
|
||||
"""
|
||||
@spec camelize(String.t) :: String.t
|
||||
def camelize(string)
|
||||
|
||||
def camelize(""),
|
||||
do: ""
|
||||
|
||||
def camelize(<<?_, t::binary>>),
|
||||
do: camelize(t)
|
||||
|
||||
def camelize(<<h, t::binary>>),
|
||||
do: <<to_upper_char(h)>> <> do_camelize(t)
|
||||
|
||||
defp do_camelize(<<?_, ?_, t::binary>>),
|
||||
do: do_camelize(<<?_, t::binary >>)
|
||||
|
||||
defp do_camelize(<<?_, h, t::binary>>) when h >= ?a and h <= ?z,
|
||||
do: <<to_upper_char(h)>> <> do_camelize(t)
|
||||
|
||||
defp do_camelize(<<?_>>),
|
||||
do: <<>>
|
||||
|
||||
defp do_camelize(<<?/, t::binary>>),
|
||||
do: <<?.>> <> camelize(t)
|
||||
|
||||
defp do_camelize(<<h, t::binary>>),
|
||||
do: <<h>> <> do_camelize(t)
|
||||
|
||||
defp do_camelize(<<>>),
|
||||
do: <<>>
|
||||
|
||||
defp to_upper_char(char) when char >= ?a and char <= ?z, do: char - 32
|
||||
defp to_upper_char(char), do: char
|
||||
|
||||
defp to_lower_char(char) when char >= ?A and char <= ?Z, do: char + 32
|
||||
defp to_lower_char(char), do: char
|
||||
end
|
||||
|
||||
@@ -24,12 +24,12 @@ defmodule Macro.Env do
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity; returns
|
||||
* `function` - a tuple as `{atom, integer}`, where the first
|
||||
element is the function name and the second its arity; returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment; it can be `nil`
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
(default context), inside a guard or inside a match
|
||||
* `aliases` - a list of two-item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
|
||||
+510
-17
@@ -1,37 +1,341 @@
|
||||
defmodule Map do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on maps.
|
||||
A set of functions for working with maps.
|
||||
|
||||
Maps are key-value stores where keys are compared using
|
||||
the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form defined in the `Kernel.SpecialForms`
|
||||
module.
|
||||
|
||||
For more information about the functions in this module and
|
||||
their APIs, please consult the `Dict` module.
|
||||
Maps are key-value stores where keys can be any value and
|
||||
are compared using the match operator (`===`). Maps can be
|
||||
created with the `%{}` special form defined in the
|
||||
`Kernel.SpecialForms` module.
|
||||
"""
|
||||
|
||||
use Dict
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
Returns all keys from the map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.keys(%{a: 1, b: 2})
|
||||
[:a, :b]
|
||||
|
||||
"""
|
||||
@spec keys(map) :: [key]
|
||||
defdelegate keys(map), to: :maps
|
||||
|
||||
@doc """
|
||||
Returns all values from the map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.values(%{a: 1, b: 2})
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@spec values(map) :: [value]
|
||||
defdelegate values(map), to: :maps
|
||||
|
||||
@doc """
|
||||
Converts the map to a list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.to_list(%{a: 1})
|
||||
[a: 1]
|
||||
iex> Map.to_list(%{1 => 2})
|
||||
[{1, 2}]
|
||||
|
||||
"""
|
||||
@spec to_list(map) :: [{term, term}]
|
||||
defdelegate to_list(map), to: :maps
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new
|
||||
%{}
|
||||
|
||||
"""
|
||||
@spec new :: map
|
||||
def new, do: %{}
|
||||
|
||||
@doc """
|
||||
Creates a map from an enumerable.
|
||||
|
||||
Duplicated keys are removed; the latest one prevails.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new([{:b, 1}, {:a, 2}])
|
||||
%{a: 2, b: 1}
|
||||
iex> Map.new([a: 1, a: 2, a: 3])
|
||||
%{a: 3}
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: map
|
||||
def new(enumerable) do
|
||||
Enum.reduce(enumerable, %{}, fn {k, v}, acc -> put(acc, k, v) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a map from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed; the latest one prevails.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new([:a, :b], fn x -> {x, x} end)
|
||||
%{a: :a, b: :b}
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, (term -> {key, value})) :: map
|
||||
def new(enumerable, transform) do
|
||||
fun = fn el, acc ->
|
||||
{k, v} = transform.(el)
|
||||
put(acc, k, v)
|
||||
end
|
||||
Enum.reduce(enumerable, %{}, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether a given `key` exists in the given `map`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.has_key?(%{a: 1}, :a)
|
||||
true
|
||||
iex> Map.has_key?(%{a: 1}, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec has_key?(map, key) :: boolean
|
||||
def has_key?(map, key), do: :maps.is_key(key, map)
|
||||
|
||||
@doc """
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
|
||||
If the `key` does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.fetch(%{a: 1}, :a)
|
||||
{:ok, 1}
|
||||
iex> Map.fetch(%{a: 1}, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(map, key) :: {:ok, value} | :error
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific `key`.
|
||||
|
||||
If `key` does not exist, a `KeyError` is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.fetch!(%{a: 1}, :a)
|
||||
1
|
||||
iex> Map.fetch!(%{a: 1}, :b)
|
||||
** (KeyError) key :b not found in: %{a: 1}
|
||||
|
||||
"""
|
||||
@spec fetch!(map, key) :: value | no_return
|
||||
def fetch!(map, key) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key` unless the entry `key`
|
||||
already exists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.put_new(%{a: 1}, :b, 2)
|
||||
%{b: 2, a: 1}
|
||||
iex> Map.put_new(%{a: 1, b: 2}, :a, 3)
|
||||
%{a: 1, b: 2}
|
||||
|
||||
"""
|
||||
@spec put_new(map, key, value) :: map
|
||||
def put_new(map, key, value) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> put(map, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates `fun` and puts the result under `key`
|
||||
in map unless `key` is already present.
|
||||
|
||||
This is useful if the value is very expensive to calculate or
|
||||
generally difficult to setup and teardown again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> map = %{a: 1}
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> 3
|
||||
...> end
|
||||
iex> Map.put_new_lazy(map, :a, fun)
|
||||
%{a: 1}
|
||||
iex> Map.put_new_lazy(map, :b, fun)
|
||||
%{a: 1, b: 3}
|
||||
|
||||
"""
|
||||
@spec put_new_lazy(map, key, (() -> value)) :: map
|
||||
def put_new_lazy(map, key, fun) when is_function(fun, 0) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> put(map, key, fun.())
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes all entries corresponding to the given keys and
|
||||
returns them in a new map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.take(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
|
||||
%{a: 1, c: 3}
|
||||
|
||||
"""
|
||||
@spec take(map, [key]) :: map
|
||||
def take(map, keys) do
|
||||
Enum.reduce(keys, new, fn key, acc ->
|
||||
case fetch(map, key) do
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If `key` does not exist, return the default value
|
||||
(`nil` if no default value).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.get(%{}, :a)
|
||||
nil
|
||||
iex> Map.get(%{a: 1}, :a)
|
||||
1
|
||||
iex> Map.get(%{a: 1}, :b)
|
||||
nil
|
||||
iex> Map.get(%{a: 1}, :b, 3)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec get(map, key) :: value
|
||||
@spec get(map, key, value) :: value
|
||||
def get(map, key, default \\ nil) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If `key` does not exist, lazily evaluates `fun` and returns its result.
|
||||
|
||||
This is useful if the default value is very expensive to calculate or
|
||||
generally difficult to setup and teardown again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> map = %{a: 1}
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> 13
|
||||
...> end
|
||||
iex> Map.get_lazy(map, :a, fun)
|
||||
1
|
||||
iex> Map.get_lazy(map, :b, fun)
|
||||
13
|
||||
|
||||
"""
|
||||
@spec get_lazy(map, key, (() -> value)) :: value
|
||||
def get_lazy(map, key, fun) when is_function(fun, 0) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> fun.()
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.put(%{a: 1}, :b, 2)
|
||||
%{a: 1, b: 2}
|
||||
iex> Map.put(%{a: 1, b: 2}, :a, 3)
|
||||
%{a: 3, b: 2}
|
||||
|
||||
"""
|
||||
@spec put(map, key, value) :: map
|
||||
def put(map, key, val) do
|
||||
:maps.put(key, val, map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the map for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the map unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.delete(%{a: 1, b: 2}, :a)
|
||||
%{b: 2}
|
||||
iex> Map.delete(%{b: 2}, :a)
|
||||
%{b: 2}
|
||||
|
||||
"""
|
||||
@spec delete(map, key) :: map
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
@doc """
|
||||
Merges two maps into one.
|
||||
|
||||
All keys in `map2` will be added to `map1`, overriding any existing one.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
|
||||
%{a: 3, b: 2, d: 4}
|
||||
|
||||
"""
|
||||
@spec merge(map, map) :: map
|
||||
defdelegate merge(map1, map2), to: :maps
|
||||
|
||||
@doc """
|
||||
Merges two maps into one.
|
||||
|
||||
All keys in `map2` will be added to `map1`. The given function will
|
||||
be invoked with the key, value1 and value2 to solve conflicts.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4}, fn _k, v1, v2 ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
%{a: 4, b: 2, d: 4}
|
||||
|
||||
"""
|
||||
@spec merge(map, map, (key, value, value -> value)) :: map
|
||||
def merge(map1, map2, callback) do
|
||||
:maps.fold fn k, v2, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
@@ -39,8 +343,131 @@ defmodule Map do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the value in the map with the given function.
|
||||
Updates the `key` in `map` with the given function.
|
||||
|
||||
If the `key` does not exist, inserts the given `initial` value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.update(%{a: 1}, :a, 13, &(&1 * 2))
|
||||
%{a: 2}
|
||||
iex> Map.update(%{a: 1}, :b, 11, &(&1 * 2))
|
||||
%{a: 1, b: 11}
|
||||
|
||||
"""
|
||||
@spec update(map, key, value, (value -> value)) :: map
|
||||
def update(map, key, initial, fun) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} ->
|
||||
put(map, key, fun.(value))
|
||||
:error ->
|
||||
put(map, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns and removes all values associated with `key` in the `map`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.pop(%{a: 1}, :a)
|
||||
{1, %{}}
|
||||
iex> Map.pop(%{a: 1}, :b)
|
||||
{nil, %{a: 1}}
|
||||
iex> Map.pop(%{a: 1}, :b, 3)
|
||||
{3, %{a: 1}}
|
||||
|
||||
"""
|
||||
@spec pop(map, key, value) :: {value, map}
|
||||
def pop(map, key, default \\ nil) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} -> {value, delete(map, key)}
|
||||
:error -> {default, map}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily returns and removes all values associated with `key` in the `map`.
|
||||
|
||||
This is useful if the default value is very expensive to calculate or
|
||||
generally difficult to setup and teardown again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> map = %{a: 1}
|
||||
iex> fun = fn ->
|
||||
...> # some expensive operation here
|
||||
...> 13
|
||||
...> end
|
||||
iex> Map.pop_lazy(map, :a, fun)
|
||||
{1, %{}}
|
||||
iex> Map.pop_lazy(map, :b, fun)
|
||||
{13, %{a: 1}}
|
||||
|
||||
"""
|
||||
@spec pop_lazy(map, key, (() -> value)) :: {value, map}
|
||||
def pop_lazy(map, key, fun) when is_function(fun, 0) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} -> {value, delete(map, key)}
|
||||
:error -> {fun.(), map}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.drop(%{a: 1, b: 2, c: 3}, [:b, :d])
|
||||
%{a: 1, c: 3}
|
||||
|
||||
"""
|
||||
@spec drop(map, [key]) :: map
|
||||
def drop(map, keys) do
|
||||
Enum.reduce(keys, map, &delete(&2, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes all entries corresponding to the given keys and extracts them into a
|
||||
separate map.
|
||||
|
||||
Returns a tuple with the new map and the old map with removed keys.
|
||||
|
||||
Keys for which there are no entires in the map are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.split(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
|
||||
{%{a: 1, c: 3}, %{b: 2}}
|
||||
|
||||
"""
|
||||
@spec split(map, [key]) :: {map, map}
|
||||
def split(map, keys) do
|
||||
Enum.reduce(keys, {new, map}, fn key, {inc, exc} = acc ->
|
||||
case fetch(exc, key) do
|
||||
{:ok, value} ->
|
||||
{put(inc, key, value), delete(exc, key)}
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.update!(%{a: 1}, :a, &(&1 * 2))
|
||||
%{a: 2}
|
||||
|
||||
iex> Map.update!(%{a: 1}, :b, &(&1 * 2))
|
||||
** (KeyError) key :b not found
|
||||
|
||||
"""
|
||||
@spec update!(map, key, (value -> value)) :: map | no_return
|
||||
def update!(%{} = map, key, fun) do
|
||||
case fetch(map, key) do
|
||||
{:ok, value} ->
|
||||
@@ -53,8 +480,30 @@ defmodule Map do
|
||||
def update!(map, _key, _fun), do: :erlang.error({:badmap, map})
|
||||
|
||||
@doc """
|
||||
Gets a value and updates a map in one operation.
|
||||
Gets the value from `key` and updates it, all in one pass.
|
||||
|
||||
This `fun` argument receives the value of `key` (or `nil` if `key`
|
||||
is not present) and must return a two-elements tuple: the "get" value (the
|
||||
retrieved value, which can be operated on before being returned) and the new
|
||||
value to be stored under `key`.
|
||||
|
||||
The returned value is a tuple with the "get" value returned by `fun` and a
|
||||
new map with the updated value under `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.get_and_update(%{a: 1}, :a, fn current_value ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
{1, %{a: "new value!"}}
|
||||
|
||||
iex> Map.get_and_update(%{a: 1}, :b, fn current_value ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
{nil, %{b: "new value!", a: 1}}
|
||||
|
||||
"""
|
||||
@spec get_and_update(map, key, (value -> {get, value})) :: {get, map} when get: term
|
||||
def get_and_update(%{} = map, key, fun) do
|
||||
current_value = case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
@@ -68,8 +517,30 @@ defmodule Map do
|
||||
def get_and_update(map, _key, _fun), do: :erlang.error({:badmap, map})
|
||||
|
||||
@doc """
|
||||
Gets a value and updates a map only if the key exists in one operation.
|
||||
Gets the value from `key` and updates it. Raises if there is no `key`.
|
||||
|
||||
This `fun` argument receives the value of `key` and must return a
|
||||
two-elements tuple: the "get" value (the retrieved value, which can be
|
||||
operated on before being returned) and the new value to be stored under
|
||||
`key`.
|
||||
|
||||
The returned value is a tuple with the "get" value returned by `fun` and a
|
||||
new map with the updated value under `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.get_and_update!(%{a: 1}, :a, fn(current_value) ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
{1, %{a: "new value!"}}
|
||||
|
||||
iex> Map.get_and_update!(%{a: 1}, :b, fn current_value ->
|
||||
...> {current_value, "new value!"}
|
||||
...> end)
|
||||
** (KeyError) key :b not found
|
||||
|
||||
"""
|
||||
@spec get_and_update!(map, key, (value -> {get, value})) :: {get, map} | no_return when get: term
|
||||
def get_and_update!(%{} = map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} ->
|
||||
@@ -101,6 +572,7 @@ defmodule Map do
|
||||
#=> %{name: "john"}
|
||||
|
||||
"""
|
||||
@spec from_struct(atom | struct) :: map
|
||||
def from_struct(struct) when is_atom(struct) do
|
||||
:maps.remove(:__struct__, struct.__struct__)
|
||||
end
|
||||
@@ -109,6 +581,27 @@ defmodule Map do
|
||||
:maps.remove(:__struct__, struct)
|
||||
end
|
||||
|
||||
def equal?(map1, map2)
|
||||
@doc """
|
||||
Checks if two maps are equal.
|
||||
|
||||
Two maps are considered to be equal if they contain
|
||||
the same keys and those keys contain the same values.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.equal?(%{a: 1, b: 2}, %{b: 2, a: 1})
|
||||
true
|
||||
iex> Map.equal?(%{a: 1, b: 2}, %{b: 1, a: 2})
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(map, map) :: boolean
|
||||
def equal?(%{} = map1, %{} = map2), do: map1 === map2
|
||||
|
||||
# TODO: Deprecate by 1.3
|
||||
# TODO: Remove by 1.4
|
||||
@doc false
|
||||
def size(map) do
|
||||
map_size(map)
|
||||
end
|
||||
end
|
||||
|
||||
+214
-41
@@ -1,97 +1,270 @@
|
||||
defmodule MapSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
A set of functions for working with sets.
|
||||
|
||||
The `MapSet` is represented internally as a struct, therefore
|
||||
`%MapSet{}` can be used whenever there is a need to match
|
||||
on any `MapSet`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Set` module.
|
||||
|
||||
The `MapSet` is implemented using `Map` data type.
|
||||
For more information about the functions
|
||||
and their APIs, please consult the `Set` module.
|
||||
The `MapSet` is represented internally as a struct,
|
||||
therefore `%MapSet{}` can be used whenever there is a
|
||||
need to match on any `MapSet`. Note though the struct
|
||||
fields are private and must not be accessed directly.
|
||||
Instead, use the functions in this module.
|
||||
"""
|
||||
|
||||
@behaviour Set
|
||||
|
||||
@opaque t :: %__MODULE__{map: map}
|
||||
@type value :: term
|
||||
defstruct map: %{}
|
||||
|
||||
@doc """
|
||||
Returns a new set.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.new
|
||||
#MapSet<[]>
|
||||
|
||||
"""
|
||||
@spec new :: t
|
||||
def new(), do: %MapSet{}
|
||||
|
||||
@doc """
|
||||
Creates a set from an enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.new([:b, :a, 3])
|
||||
#MapSet<[3, :a, :b]>
|
||||
iex> MapSet.new([3, 3, 3, 2, 2, 1])
|
||||
#MapSet<[1, 2, 3]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: t
|
||||
def new(enumerable) do
|
||||
Enum.reduce(enumerable, %MapSet{}, &put(&2, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a mapset from an enumerable via the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.new([1, 2, 1], fn x -> 2 * x end)
|
||||
#MapSet<[2, 4]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, (term -> term)) :: t
|
||||
def new(enumerable, transform) do
|
||||
Enum.reduce(enumerable, %MapSet{}, &put(&2, transform.(&1)))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes `value` from `set`.
|
||||
|
||||
Returns a new set which is a copy of `set` but without `value`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> set = MapSet.new([1, 2, 3])
|
||||
iex> MapSet.delete(set, 4)
|
||||
#MapSet<[1, 2, 3]>
|
||||
iex> MapSet.delete(set, 2)
|
||||
#MapSet<[1, 3]>
|
||||
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(%MapSet{map: map} = set, term) do
|
||||
%{set | map: Map.delete(map, term)}
|
||||
end
|
||||
|
||||
def difference(%MapSet{} = set1, %MapSet{} = set2) do
|
||||
reduce(set2, {:cont, set1}, fn value, acc ->
|
||||
{:cont, delete(acc, value)}
|
||||
end) |> elem(1)
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.difference(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
|
||||
#MapSet<[1]>
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
map = :maps.fold(fn value, _, acc ->
|
||||
Map.delete(acc, value)
|
||||
end, map1, map2)
|
||||
%MapSet{map: map}
|
||||
end
|
||||
|
||||
def disjoint?(%MapSet{} = set1, %MapSet{} = set2) do
|
||||
if size(set1) > size(set2), do: {set1, set2} = {set2, set1}
|
||||
reduce(set1, {:cont, true}, fn value, _ ->
|
||||
if member?(set2, value) do
|
||||
{:halt, false}
|
||||
@doc """
|
||||
Checks if `set1` and `set2` have no members in common.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([3, 4]))
|
||||
true
|
||||
iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([2, 3]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
if map_size(map1) > map_size(map2), do: {map1, map2} = {map2, map1}
|
||||
:maps.fold(fn value, _, _ ->
|
||||
if Map.has_key?(map2, value) do
|
||||
throw({:halt, false})
|
||||
else
|
||||
{:cont, true}
|
||||
true
|
||||
end
|
||||
end) |> elem(1)
|
||||
end, true, map1)
|
||||
catch
|
||||
{:halt, false} -> false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two sets are equal.
|
||||
|
||||
The comparison between elements must be done using `===`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([2, 1, 1]))
|
||||
true
|
||||
iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([3, 4]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
Map.equal?(map1, map2)
|
||||
end
|
||||
|
||||
def intersection(%MapSet{} = set1, %MapSet{} = set2) do
|
||||
if size(set1) > size(set2), do: {set1, set2} = {set2, set1}
|
||||
reduce(set1, {:cont, new}, fn value, acc ->
|
||||
if member?(set2, value) do
|
||||
{:cont, put(acc, value)}
|
||||
@doc """
|
||||
Returns a set containing only members that `set1` and `set2` have in common.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
|
||||
#MapSet<[2]>
|
||||
|
||||
iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([3, 4]))
|
||||
#MapSet<[]>
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
if map_size(map1) > map_size(map2), do: {map1, map2} = {map2, map1}
|
||||
map = :maps.fold(fn value, _, acc ->
|
||||
if Map.has_key?(map2, value) do
|
||||
Map.put(acc, value, true)
|
||||
else
|
||||
{:cont, acc}
|
||||
acc
|
||||
end
|
||||
end) |> elem(1)
|
||||
end, %{}, map1)
|
||||
%MapSet{map: map}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set` contains `value`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.member?(MapSet.new([1, 2, 3]), 2)
|
||||
true
|
||||
iex> MapSet.member?(MapSet.new([1, 2, 3]), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec member?(t, value) :: boolean
|
||||
def member?(%MapSet{map: map}, value) do
|
||||
Map.has_key?(map, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts `value` into `set` if `set` doesn't already contain it.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.put(MapSet.new([1, 2, 3]), 3)
|
||||
#MapSet<[1, 2, 3]>
|
||||
iex> MapSet.put(MapSet.new([1, 2, 3]), 4)
|
||||
#MapSet<[1, 2, 3, 4]>
|
||||
|
||||
"""
|
||||
@spec put(t, value) :: t
|
||||
def put(%MapSet{map: map} = set, value) do
|
||||
%{set | map: Map.put(map, value, nil)}
|
||||
%{set | map: Map.put(map, value, true)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `set`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.size(MapSet.new([1, 2, 3]))
|
||||
3
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(%MapSet{map: map}) do
|
||||
map_size(map)
|
||||
end
|
||||
|
||||
def subset?(%MapSet{} = set1, %MapSet{} = set2) do
|
||||
if size(set1) <= size(set2) do
|
||||
reduce(set1, {:cont, true}, fn value, _ ->
|
||||
if member?(set2, value), do: {:cont, true}, else: {:halt, false}
|
||||
end) |> elem(1)
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
This function checks if `set1` is a subset of `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.subset?(MapSet.new([1, 2]), MapSet.new([1, 2, 3]))
|
||||
true
|
||||
iex> MapSet.subset?(MapSet.new([1, 2, 3]), MapSet.new([1, 2]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
if map_size(map1) <= map_size(map2) do
|
||||
:maps.fold(fn value, _, _ ->
|
||||
if Map.has_key?(map2, value) do
|
||||
true
|
||||
else
|
||||
throw({:halt, false})
|
||||
end
|
||||
end, true, map1)
|
||||
else
|
||||
false
|
||||
end
|
||||
catch
|
||||
{:halt, false} -> false
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(%MapSet{} = set, acc, fun) do
|
||||
Enumerable.List.reduce(to_list(set), acc, fun)
|
||||
end
|
||||
@doc """
|
||||
Converts `set` to a list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.to_list(MapSet.new([1, 2, 3]))
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: list
|
||||
def to_list(%MapSet{map: map}) do
|
||||
Map.keys(map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MapSet.union(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
|
||||
#MapSet<[1, 2, 3, 4]>
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(%MapSet{map: map1}, %MapSet{map: map2}) do
|
||||
%MapSet{map: Map.merge(map1, map2)}
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(set, acc, fun), do: MapSet.reduce(set, acc, fun)
|
||||
def reduce(set, acc, fun), do: Enumerable.List.reduce(MapSet.to_list(set), acc, fun)
|
||||
def member?(set, val), do: {:ok, MapSet.member?(set, val)}
|
||||
def count(set), do: {:ok, MapSet.size(set)}
|
||||
end
|
||||
|
||||
+168
-115
@@ -1,8 +1,9 @@
|
||||
defmodule Module do
|
||||
@moduledoc ~S'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
Provides functions to deal with modules during compilation time.
|
||||
|
||||
It allows a developer to dynamically add, delete and register
|
||||
attributes, attach documentation and so forth.
|
||||
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their scope
|
||||
@@ -65,6 +66,30 @@ defmodule Module do
|
||||
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Behaviours can be referenced by modules to ensure they implement
|
||||
required specific function signatures defined by `@callback`.
|
||||
|
||||
For example, you can specify the URI.Parser behaviour as follows:
|
||||
|
||||
defmodule URI.Parser do
|
||||
@doc "Parses the given URL"
|
||||
@callback parse(uri_info :: URI.t) :: URI.t
|
||||
|
||||
@doc "Defines a default port"
|
||||
@callback default_port() :: integer
|
||||
end
|
||||
|
||||
And then a module may use it as:
|
||||
|
||||
defmodule URI.HTTP do
|
||||
@behaviour URI.Parser
|
||||
def default_port(), do: 80
|
||||
def parse(info), do: info
|
||||
end
|
||||
|
||||
If the behaviour changes or URI.HTTP does not implement one of the
|
||||
callbacks, a warning will be raised.
|
||||
|
||||
Specifies an OTP or user-defined behaviour.
|
||||
|
||||
### Example
|
||||
@@ -85,18 +110,18 @@ defmodule Module do
|
||||
For the list of supported options, see Erlang's
|
||||
[`:compile` module](http://www.erlang.org/doc/man/compile.html).
|
||||
|
||||
Several uses of `@compile` will accumulate instead of overriding
|
||||
Multiple uses of `@compile` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@compile {:inline, myfun: 1}
|
||||
defmodule M do
|
||||
@compile {:inline, myfun: 1}
|
||||
|
||||
def myfun(arg) do
|
||||
to_string(arg)
|
||||
end
|
||||
def myfun(arg) do
|
||||
to_string(arg)
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
|
||||
@@ -111,20 +136,20 @@ defmodule Module do
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sums `a` to `b`.
|
||||
"""
|
||||
def sum(a, b) do
|
||||
a + b
|
||||
end
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sums `a` to `b`.
|
||||
"""
|
||||
def sum(a, b) do
|
||||
a + b
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
|
||||
Changes the filename used in stacktraces for the function or macro that
|
||||
@@ -134,13 +159,13 @@ defmodule Module do
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@file "hello.ex"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@file "hello.ex"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
|
||||
@@ -152,11 +177,11 @@ defmodule Module do
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
A very useful module
|
||||
"""
|
||||
end
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
A very useful module
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
* `@on_definition`
|
||||
@@ -191,28 +216,28 @@ defmodule Module do
|
||||
|
||||
### Example
|
||||
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
IO.puts "Defining #{kind} named #{name} with args:"
|
||||
IO.inspect args
|
||||
IO.puts "and guards"
|
||||
IO.inspect guards
|
||||
IO.puts "and body"
|
||||
IO.puts Macro.to_string(body)
|
||||
end
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
IO.puts "Defining #{kind} named #{name} with args:"
|
||||
IO.inspect args
|
||||
IO.puts "and guards"
|
||||
IO.inspect guards
|
||||
IO.puts "and body"
|
||||
IO.puts Macro.to_string(body)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition {H, :on_def}
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_string(arg)
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition {H, :on_def}
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_string(arg)
|
||||
end
|
||||
|
||||
def hello(_) do
|
||||
:ok
|
||||
end
|
||||
def hello(_) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
|
||||
@@ -224,31 +249,31 @@ defmodule Module do
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
|
||||
def load_check do
|
||||
if some_condition() do
|
||||
:ok
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def some_condition do
|
||||
false
|
||||
def load_check do
|
||||
if some_condition() do
|
||||
:ok
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def some_condition do
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
end
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
* `@external_resource`
|
||||
|
||||
@@ -271,18 +296,18 @@ defmodule Module do
|
||||
For the list of supported warnings, see
|
||||
[`:dialyzer` module](http://www.erlang.org/doc/man/dialyzer.html).
|
||||
|
||||
Several uses of `@dialyzer` will accumulate instead of overriding
|
||||
Multiple uses of `@dialyzer` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@dialyzer {:nowarn_function, myfun: 1}
|
||||
defmodule M do
|
||||
@dialyzer {:nowarn_function, myfun: 1}
|
||||
|
||||
def myfun(arg) do
|
||||
M.not_a_function(arg)
|
||||
end
|
||||
def myfun(arg) do
|
||||
M.not_a_function(arg)
|
||||
end
|
||||
end
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
@@ -298,9 +323,9 @@ defmodule Module do
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
`@` and followed by a valid Elixir value:
|
||||
|
||||
defmodule M do
|
||||
@custom_attr [some: "stuff"]
|
||||
end
|
||||
defmodule M do
|
||||
@custom_attr [some: "stuff"]
|
||||
end
|
||||
|
||||
For more advanced options available when defining custom attributes, see
|
||||
`register_attribute/3`.
|
||||
@@ -464,14 +489,15 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates a list of aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
Concatenates a list of aliases and returns a new alias only if the alias
|
||||
was already referenced.
|
||||
|
||||
If the alias was not referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.safe_concat([Unknown, Module])
|
||||
iex> Module.safe_concat([Module, Unknown])
|
||||
** (ArgumentError) argument error
|
||||
|
||||
iex> Module.safe_concat([List, Chars])
|
||||
@@ -484,14 +510,15 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates two aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
Concatenates two aliases and returns a new alias only if the alias was
|
||||
already referenced.
|
||||
|
||||
If the alias was not referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.safe_concat(Unknown, Module)
|
||||
iex> Module.safe_concat(Module, Unknown)
|
||||
** (ArgumentError) argument error
|
||||
|
||||
iex> Module.safe_concat(List, Chars)
|
||||
@@ -504,9 +531,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
integer), the kind (`def` or `defmacro`), a tuple representing
|
||||
Attaches documentation to a given function or type.
|
||||
|
||||
It expects the module the function/type belongs to, the line (a non
|
||||
negative integer), the kind (`def` or `defmacro`), a tuple representing
|
||||
the function and its arity, the function signature (the signature
|
||||
should be omitted for types) and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
@@ -619,12 +647,12 @@ defmodule Module do
|
||||
length(:lists.filter(fn(el) -> el == key end, list))
|
||||
end
|
||||
|
||||
defp camelcase_to_underscore(<<c :: utf8, rest :: binary>>) when c >= ?A and c <= ?Z,
|
||||
do: do_camelcase_to_underscore(rest, <<c + 32 :: utf8>>)
|
||||
defp do_camelcase_to_underscore(<<c :: utf8, rest :: binary>>, acc) when c >= ?A and c <= ?Z,
|
||||
do: do_camelcase_to_underscore(rest, <<acc :: binary, ?_, c + 32 :: utf8>>)
|
||||
defp do_camelcase_to_underscore(<<c :: utf8, rest :: binary>>, acc),
|
||||
do: do_camelcase_to_underscore(rest, <<acc :: binary, c>>)
|
||||
defp camelcase_to_underscore(<<c::utf8, rest::binary>>) when c >= ?A and c <= ?Z,
|
||||
do: do_camelcase_to_underscore(rest, <<c + 32::utf8>>)
|
||||
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc) when c >= ?A and c <= ?Z,
|
||||
do: do_camelcase_to_underscore(rest, <<acc::binary, ?_, c + 32::utf8>>)
|
||||
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc),
|
||||
do: do_camelcase_to_underscore(rest, <<acc::binary, c>>)
|
||||
defp do_camelcase_to_underscore(<<>>, acc),
|
||||
do: acc
|
||||
|
||||
@@ -658,6 +686,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
|
||||
Use `defines?/3` to assert for a specific type.
|
||||
|
||||
## Examples
|
||||
@@ -677,8 +706,9 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Checks if the module defines a function or macro of the
|
||||
given `kind`. `kind` can be any of `:def`, `:defp`,
|
||||
`:defmacro` or `:defmacrop`.
|
||||
given `kind`.
|
||||
|
||||
`kind` can be any of `:def`, `:defp`, `:defmacro` or `:defmacrop`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -736,6 +766,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Makes the given functions in `module` overridable.
|
||||
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable/1` for
|
||||
more information and documentation.
|
||||
@@ -757,11 +788,13 @@ defmodule Module do
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
end
|
||||
|
||||
old = :elixir_def_overridable.overridable(module)
|
||||
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
|
||||
{count + 1, clause, neighbours, false}
|
||||
end, {1, clause, neighbours, false}, old)
|
||||
:elixir_def_overridable.overridable(module, merged)
|
||||
old = :elixir_def_overridable.overridable(module)
|
||||
count = case :maps.find(tuple, old) do
|
||||
{:ok, {count, _, _, _}} -> count + 1
|
||||
:error -> 1
|
||||
end
|
||||
new = :maps.put(tuple, {count, clause, neighbours, false}, old)
|
||||
:elixir_def_overridable.overridable(module, new)
|
||||
end
|
||||
end, tuples)
|
||||
end
|
||||
@@ -770,12 +803,14 @@ defmodule Module do
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
|
||||
:maps.is_key(tuple, :elixir_def_overridable.overridable(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of putting the attribute depends
|
||||
key and value.
|
||||
|
||||
The semantics of putting the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/3`.
|
||||
|
||||
## Examples
|
||||
@@ -785,11 +820,17 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def put_attribute(module, key, value) when is_atom(key) do
|
||||
def put_attribute(module, key, value) do
|
||||
put_attribute(module, key, value, nil)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put_attribute(module, key, value, stack) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = preprocess_attribute(key, value)
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
warn_if_redefining_doc_attribute(stack, table, key)
|
||||
|
||||
new =
|
||||
if :lists.member(key, acc) do
|
||||
@@ -805,9 +846,12 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked with `accumulate` with `Module.register_attribute/3`,
|
||||
a list is always returned.
|
||||
Gets the given attribute from a module.
|
||||
|
||||
If the attribute was marked with `accumulate` with
|
||||
`Module.register_attribute/3`, a list is always returned. `nil` is returned
|
||||
if the attribute has not been marked with `accumulate` and has not been set
|
||||
to any value.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
the following code:
|
||||
@@ -968,7 +1012,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
|
||||
def get_attribute(module, key, stack) when is_atom(key) and (is_list(stack) or is_nil(stack)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
@@ -981,8 +1025,8 @@ defmodule Module do
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
is_list(stack) ->
|
||||
:elixir_errors.warn warn_info(stack), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it before access"
|
||||
nil
|
||||
true ->
|
||||
@@ -1038,13 +1082,9 @@ defmodule Module do
|
||||
defp postprocess_attribute(_, value), do: value
|
||||
|
||||
defp get_doc_info(table, env) do
|
||||
# TODO: Use :ets.take/2 with Erlang 18
|
||||
case :ets.lookup(table, :doc) do
|
||||
[doc: {_, _} = pair] ->
|
||||
:ets.delete(table, :doc)
|
||||
pair
|
||||
[] ->
|
||||
{env.line, nil}
|
||||
case :ets.take(table, :doc) do
|
||||
[doc: {_, _} = pair] -> pair
|
||||
[] -> {env.line, nil}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1061,4 +1101,17 @@ defmodule Module do
|
||||
raise ArgumentError,
|
||||
"could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp warn_if_redefining_doc_attribute(stack, table, key)
|
||||
when is_list(stack) and key in [:doc, :typedoc, :moduledoc] do
|
||||
case :ets.lookup(table, key) do
|
||||
[{_, {line, val}}] when val != false ->
|
||||
:elixir_errors.warn warn_info(stack),
|
||||
"redefining @#{key} attribute previously set at line #{line}"
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_if_redefining_doc_attribute(nil, _table, _key), do: false
|
||||
end
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# This is an Elixir module responsible for tracking
|
||||
# calls in order to extract Elixir modules' behaviour
|
||||
# during compilation time.
|
||||
#
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with one main vertice:
|
||||
# all dependencies. The graph starts with one main vertex:
|
||||
#
|
||||
# * `:local` - points to local functions
|
||||
#
|
||||
# We also have can the following vertices:
|
||||
# We can also have the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{name, arity}` - a local function/arity pair
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule OptionParser do
|
||||
@moduledoc """
|
||||
This module contains functions to parse command line arguments.
|
||||
This module contains functions to parse command line options.
|
||||
"""
|
||||
|
||||
@type argv :: [String.t]
|
||||
@@ -11,8 +11,11 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
It returns the parsed values, remaining arguments and the
|
||||
invalid options.
|
||||
It returns a three-element tuple as follows:
|
||||
|
||||
1. parsed switches,
|
||||
2. remaining arguments,
|
||||
3. invalid options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -25,30 +28,30 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
By default, Elixir will try to automatically parse switches.
|
||||
By default, Elixir will try to automatically parse all switches.
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` will automatically
|
||||
be set to `true`. Switches followed by a value will be assigned
|
||||
to the value, always as strings.
|
||||
be set to `true`.
|
||||
|
||||
Note Elixir also converts the switches to underscore atoms, as
|
||||
Note: Elixir also converts the switches to underscore atoms, so
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions. This means that option names on the command line cannot contain
|
||||
underscores; such options will be reported as `:undefined` (in strict mode)
|
||||
or `:invalid` (in basic mode).
|
||||
underscores; such options will be put in the invalid options list.
|
||||
|
||||
## Switches
|
||||
## Switch Definitions
|
||||
|
||||
Many times though, it is better to explicitly list the available
|
||||
Often it is better to explicitly list the known
|
||||
switches and their formats. The switches can be specified via two
|
||||
different options:
|
||||
alternative options:
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error.
|
||||
* `:switches` - defines some switches. An attempt is still made to parse
|
||||
switches that do not appear in the list.
|
||||
|
||||
* `:switches` - defines some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed.
|
||||
* `:strict` - the switches are strict. Any switch that is not specified
|
||||
in the list is returned in the invalid options list.
|
||||
|
||||
Note only `:strict` or `:switches` may be given at once.
|
||||
Note that you should only supply the `:switches` or `:strict` option. If you
|
||||
supply both, an error will be raised.
|
||||
|
||||
For each switch, the following types are supported:
|
||||
|
||||
@@ -59,13 +62,14 @@ defmodule OptionParser do
|
||||
* `:float` - parses the switch as a float.
|
||||
* `:string` - returns the switch as a string.
|
||||
|
||||
If a switch can't be parsed or is not specified in the strict case,
|
||||
the option is returned in the invalid options list (third element
|
||||
of the returned tuple).
|
||||
If a switch can't be parsed, it is returned in the invalid options list.
|
||||
|
||||
The following extra "types" are supported:
|
||||
|
||||
* `:keep` - keeps duplicated items in the list instead of overriding
|
||||
* `:keep` - keeps duplicated items in the list instead of overriding them.
|
||||
|
||||
Note: if you want to use `:keep` with a non-string type, use a list, e.g.
|
||||
`[foo: [:integer, :keep]]`.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -181,7 +185,7 @@ defmodule OptionParser do
|
||||
command line)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
(returned in strict mode when the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
"""
|
||||
@@ -235,7 +239,7 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a key-value enumerable and convert it to argv.
|
||||
Receives a key-value enumerable and converts it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
@@ -281,23 +285,23 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
# If we have an escaped quote, simply remove the escape
|
||||
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
|
||||
defp do_split(<<?\\, quote, t::binary>>, buffer, acc, quote),
|
||||
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
|
||||
|
||||
# If we have a quote and we were not in a quote, start one
|
||||
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
|
||||
defp do_split(<<quote, t::binary>>, buffer, acc, nil) when quote in [?", ?'],
|
||||
do: do_split(t, buffer, acc, quote)
|
||||
|
||||
# If we have a quote and we were inside it, close it
|
||||
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
|
||||
defp do_split(<<quote, t::binary>>, buffer, acc, quote),
|
||||
do: do_split(t, buffer, acc, nil)
|
||||
|
||||
# If we have an escaped quote/space, simply remove the escape as long as we are not inside a quote
|
||||
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
|
||||
defp do_split(<<?\\, h, t::binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
|
||||
do: do_split(t, <<buffer::binary, h>>, acc, nil)
|
||||
|
||||
# If we have space and we are outside of a quote, start new segment
|
||||
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
|
||||
defp do_split(<<?\s, t::binary>>, buffer, acc, nil),
|
||||
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
|
||||
|
||||
# All other characters are moved to buffer
|
||||
@@ -326,6 +330,8 @@ defmodule OptionParser do
|
||||
aliases = opts[:aliases] || []
|
||||
|
||||
{switches, strict} = cond do
|
||||
opts[:switches] && opts[:strict] ->
|
||||
raise ArgumentError, ":switches and :strict cannot be given together"
|
||||
s = opts[:switches] ->
|
||||
{s, false}
|
||||
s = opts[:strict] ->
|
||||
@@ -377,7 +383,7 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
|
||||
defp tag_option(<<?-, option::binary>>, switches, _aliases) do
|
||||
get_negated(option, switches)
|
||||
end
|
||||
|
||||
|
||||
+31
-35
@@ -73,7 +73,7 @@ defmodule Path do
|
||||
do: absname_join([volume|rest])
|
||||
|
||||
# Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _::binary>>|_], relative),
|
||||
do: absname(absname_join(rest), relative)
|
||||
|
||||
# Relative to current directory on another drive.
|
||||
@@ -96,13 +96,13 @@ defmodule Path do
|
||||
defp absname_join(left, right),
|
||||
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp do_absname_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
defp do_absname_join(<<uc_letter, ?:, rest::binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_absname_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_absname_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
defp do_absname_join(<<?\\, rest::binary>>, relativename, result, :win32), do:
|
||||
do_absname_join(<<?/, rest::binary>>, relativename, result, :win32)
|
||||
defp do_absname_join(<<?/, rest::binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
defp do_absname_join(<<?/, rest::binary>>, relativename, [?/|result], os_type), do:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_absname_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(reverse_maybe_remove_dirsep(result, os_type))
|
||||
@@ -112,7 +112,7 @@ defmodule Path do
|
||||
do_absname_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_absname_join(<<>>, relativename, result, os_type), do:
|
||||
do_absname_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_absname_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
defp do_absname_join(<<char, rest::binary>>, relativename, result, os_type), do:
|
||||
do_absname_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
@@ -225,7 +225,7 @@ defmodule Path do
|
||||
end
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
defp unix_pathtype(<<?/, relative::binary>>), do:
|
||||
{:absolute, relative}
|
||||
defp unix_pathtype([?/|relative]), do:
|
||||
{:absolute, relative}
|
||||
@@ -240,13 +240,13 @@ defmodule Path do
|
||||
win32_pathtype(list++rest)
|
||||
defp win32_pathtype([char, list|rest]) when is_list(list), do:
|
||||
win32_pathtype([char|list++rest])
|
||||
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
|
||||
defp win32_pathtype(<<c1, c2, relative::binary>>) when c1 in @slash and c2 in @slash, do:
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
|
||||
defp win32_pathtype(<<c, relative::binary>>) when c in @slash, do:
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
|
||||
defp win32_pathtype(<<_letter, ?:, c, relative::binary>>) when c in @slash, do:
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
|
||||
defp win32_pathtype(<<_letter, ?:, relative::binary>>), do:
|
||||
{:volumerelative, relative}
|
||||
|
||||
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
|
||||
@@ -628,32 +628,28 @@ defmodule Path do
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_dot(<<"/../", rest::binary>>),
|
||||
do: expand_dot("/" <> rest)
|
||||
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
|
||||
do: expand_dot(<<letter, ":/", rest::binary>>)
|
||||
defp expand_dot("/.."),
|
||||
do: "/"
|
||||
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
|
||||
do: expand_dot(<<letter, ":/">>)
|
||||
defp expand_dot(<<"/", rest::binary>>),
|
||||
do: "/" <> do_expand_dot(rest)
|
||||
defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,
|
||||
do: <<letter, ":/">> <> do_expand_dot(rest)
|
||||
defp expand_dot(path),
|
||||
do: expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
do: do_expand_dot(path)
|
||||
|
||||
defp expand_dot([".."|t], [_, _|acc]) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
defp do_expand_dot(path),
|
||||
do: do_expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
|
||||
defp expand_dot(["."|t], acc) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp expand_dot([h|t], acc) do
|
||||
expand_dot t, ["/", h|acc]
|
||||
end
|
||||
|
||||
defp expand_dot([], ["/"|acc]) do
|
||||
IO.iodata_to_binary(:lists.reverse(acc))
|
||||
end
|
||||
defp do_expand_dot([".."|t], [_, _|acc]),
|
||||
do: do_expand_dot(t, acc)
|
||||
defp do_expand_dot([".."|t], []),
|
||||
do: do_expand_dot(t, [])
|
||||
defp do_expand_dot(["."|t], acc),
|
||||
do: do_expand_dot(t, acc)
|
||||
defp do_expand_dot([h|t], acc),
|
||||
do: do_expand_dot(t, ["/", h|acc])
|
||||
defp do_expand_dot([], []),
|
||||
do: ""
|
||||
defp do_expand_dot([], ["/"|acc]),
|
||||
do: IO.iodata_to_binary(:lists.reverse(acc))
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
|
||||
+54
-9
@@ -3,56 +3,98 @@ defmodule Port do
|
||||
Functions related to Erlang ports.
|
||||
"""
|
||||
|
||||
@type name :: {:spawn, char_list | binary} |
|
||||
{:spawn_driver, char_list | binary} |
|
||||
{:spawn_executable, char_list | atom} |
|
||||
{:fd, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
See [`:erlang.open_port/2`](http://www.erlang.org/doc/man/erlang.html#open_port-2)
|
||||
Opens an Erlang port given a tuple `name` and a list of `settings`.
|
||||
|
||||
## Name
|
||||
|
||||
The supported values for `name` are:
|
||||
|
||||
* `{:spawn, command}` - to run an external program. The first space separated
|
||||
word of `command` will be considered as the name of the program to run, so
|
||||
use `{:spawn_executable, command}` to run a program having spaces in its name.
|
||||
* `{:spawn_driver, command}` - similar to `{:spawn, command}`, but to run a
|
||||
loaded driver.
|
||||
* `{:spawn_executable, filename}` - similar to `{:spawn, filename}`, but to run
|
||||
an external executable. With this option, `filename` in its whole is considered
|
||||
the name of the program to execute.
|
||||
* `{:fd, fd_in, fd_out}` - to access file descriptors used by Erlang, `fd_in`
|
||||
being used for standard input, `fd_out` for standard output.
|
||||
|
||||
For more information, see [`:erlang.open_port/2`](http://www.erlang.org/doc/man/erlang.html#open_port-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec open(name, list) :: port
|
||||
def open(name, settings) do
|
||||
:erlang.open_port(name, settings)
|
||||
end
|
||||
|
||||
@doc """
|
||||
See [`:erlang.port_close/1`](http://www.erlang.org/doc/man/erlang.html#port_close-1)
|
||||
Closes the `port`.
|
||||
|
||||
For more information, see [`:erlang.port_close/1`](http://www.erlang.org/doc/man/erlang.html#port_close-1).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec close(port) :: true
|
||||
def close(port) do
|
||||
:erlang.port_close(port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
See [`:erlang.port_command/2`](http://www.erlang.org/doc/man/erlang.html#port_command-2)
|
||||
Sends `data` to the port driver `port`.
|
||||
|
||||
For more information, see [`:erlang.port_command/2`](http://www.erlang.org/doc/man/erlang.html#port_command-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec command(port, iodata, [:force | :nosuspend]) :: boolean
|
||||
def command(port, data, options \\ []) do
|
||||
:erlang.port_command(port, data, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
See [`:erlang.port_connect/2`](http://www.erlang.org/doc/man/erlang.html#port_connect-2)
|
||||
Associates the `port` identifier with a `pid`.
|
||||
|
||||
For more information, see [`:erlang.port_connect/2`](http://www.erlang.org/doc/man/erlang.html#port_connect-2).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec connect(port, pid) :: true
|
||||
def connect(port, pid) do
|
||||
:erlang.port_connect(port, pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
See [`:erlang.port_control/3`](http://www.erlang.org/doc/man/erlang.html#port_control-3)
|
||||
Sends a synchronous control command to the `port` and returns its reply as a binary.
|
||||
|
||||
Not all port drivers support this feature.
|
||||
|
||||
For more information, see [`:erlang.port_control/3`](http://www.erlang.org/doc/man/erlang.html#port_control-3).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec control(port, integer, iodata) :: iodata | binary
|
||||
def control(port, operation, data) do
|
||||
:erlang.port_control(port, operation, data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
See [`:erlang.port_call/3`](http://www.erlang.org/doc/man/erlang.html#port_call-3)
|
||||
Makes a synchronous call to the `port` and returns its reply as a term.
|
||||
|
||||
Not all port drivers support this control feature.
|
||||
|
||||
For more information, see [`:erlang.port_call/3`](http://www.erlang.org/doc/man/erlang.html#port_call-3).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec call(port, integer, term) :: term
|
||||
def call(port, operation, data) do
|
||||
:erlang.port_call(port, operation, data)
|
||||
end
|
||||
@@ -61,7 +103,7 @@ defmodule Port do
|
||||
Returns information about the `port`
|
||||
or `nil` if the port is closed.
|
||||
|
||||
See [`:erlang.port_info/1`](http://www.erlang.org/doc/man/erlang.html#port_info-1)
|
||||
For more information, see [`:erlang.port_info/1`](http://www.erlang.org/doc/man/erlang.html#port_info-1).
|
||||
"""
|
||||
def info(port) do
|
||||
nillify :erlang.port_info(port)
|
||||
@@ -71,7 +113,7 @@ defmodule Port do
|
||||
Returns information about the `port`
|
||||
or `nil` if the port is closed.
|
||||
|
||||
See [`:erlang.port_info/2`](http://www.erlang.org/doc/man/erlang.html#port_info-2)
|
||||
For more information, see [`:erlang.port_info/2`](http://www.erlang.org/doc/man/erlang.html#port_info-2).
|
||||
"""
|
||||
@spec info(port, atom) :: {atom, term} | nil
|
||||
def info(port, spec)
|
||||
@@ -88,10 +130,13 @@ defmodule Port do
|
||||
end
|
||||
|
||||
@doc """
|
||||
See [`:erlang.ports/0`](http://www.erlang.org/doc/man/erlang.html#ports-0)
|
||||
Returns a list of the ports for the current node.
|
||||
|
||||
For more information, see [`:erlang.ports/0`](http://www.erlang.org/doc/man/erlang.html#ports-0).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec list :: [port]
|
||||
def list do
|
||||
:erlang.ports
|
||||
end
|
||||
|
||||
@@ -19,6 +19,8 @@ defmodule Process do
|
||||
and has not exited yet). Otherwise, returns `false`.
|
||||
|
||||
`pid` must refer to a process at the local node.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec alive?(pid) :: boolean
|
||||
def alive?(pid) do
|
||||
@@ -27,6 +29,8 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Returns all key-values in the dictionary.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec get :: [{term, term}]
|
||||
def get do
|
||||
@@ -47,8 +51,20 @@ defmodule Process do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys in the process dictionary.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec get_keys() :: [term]
|
||||
def get_keys() do
|
||||
:erlang.get_keys()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys that have the given `value`.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec get_keys(term) :: [term]
|
||||
def get_keys(value) do
|
||||
@@ -57,6 +73,9 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Stores the given key-value in the process dictionary.
|
||||
|
||||
The return value is the value that was previously stored under the key `key`
|
||||
(or `nil` in case no value was stored under `key`).
|
||||
"""
|
||||
@spec put(term, term) :: term | nil
|
||||
def put(key, value) do
|
||||
@@ -120,11 +139,10 @@ defmodule Process do
|
||||
:noconnect
|
||||
|
||||
"""
|
||||
@spec send(dest, msg, [option]) :: result when
|
||||
@spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend when
|
||||
dest: pid | port | atom | {atom, node},
|
||||
msg: any,
|
||||
option: :noconnect | :nosuspend,
|
||||
result: :ok | :noconnect | :nosuspend
|
||||
option: :noconnect | :nosuspend
|
||||
def send(dest, msg, options) do
|
||||
:erlang.send(dest, msg, options)
|
||||
end
|
||||
@@ -138,8 +156,7 @@ defmodule Process do
|
||||
not refer to a process.
|
||||
|
||||
This function returns a timer reference, which can be read or canceled with
|
||||
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
|
||||
Note `time` cannot be greater than `4294967295`.
|
||||
`read_timer/1` and `cancel_timer/1`.
|
||||
|
||||
Finally, the timer will be automatically canceled if the given `dest` is a pid
|
||||
which is not alive or when the given pid exits. Note that timers will not be
|
||||
@@ -151,6 +168,46 @@ defmodule Process do
|
||||
:erlang.send_after(time, dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cancels a timer created by `send_after/3`.
|
||||
|
||||
When the result is an integer, it represents the time in milli-seconds
|
||||
left until the timer will expire.
|
||||
|
||||
When the result is `false`, a timer corresponding to `timer_ref` could
|
||||
not be found. This can be either because the timer expired, already has
|
||||
been canceled, or because `timer_ref` never corresponded to a timer.
|
||||
|
||||
If the timer has expired, the timeout message has been sent, but it does
|
||||
not tell you whether or not it has arrived at its destination yet.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec cancel_timer(reference) :: non_neg_integer | false
|
||||
def cancel_timer(timer_ref) do
|
||||
:erlang.cancel_timer(timer_ref)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a timer created by `send_after/3`.
|
||||
|
||||
When the result is an integer, it represents the time in milli-seconds
|
||||
left until the timer will expire.
|
||||
|
||||
When the result is `false`, a timer corresponding to `timer_ref` could
|
||||
not be found. This can be either because the timer expired, already has
|
||||
been canceled, or because `timer_ref` never corresponded to a timer.
|
||||
|
||||
If the timer has expired, the timeout message has been sent, but it does
|
||||
not tell you whether or not it has arrived at its destination yet.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec read_timer(reference) :: non_neg_integer | false
|
||||
def read_timer(timer_ref) do
|
||||
:erlang.read_timer(timer_ref)
|
||||
end
|
||||
|
||||
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
|
||||
{:fullsweep_after, non_neg_integer} |
|
||||
{:min_heap_size, non_neg_integer} |
|
||||
@@ -199,6 +256,8 @@ defmodule Process do
|
||||
The calling process starts monitoring the item given.
|
||||
It returns the monitor reference.
|
||||
|
||||
See [the need for monitoring](http://elixir-lang.org/getting-started/mix-otp/genserver.html#the-need-for-monitoring)
|
||||
for an example.
|
||||
See [`:erlang.monitor/2`](http://www.erlang.org/doc/man/erlang.html#monitor-2) for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
+20
-16
@@ -217,15 +217,6 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the protocol was consolidated.
|
||||
"""
|
||||
@@ -263,9 +254,9 @@ defmodule Protocol do
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def consolidate(protocol, types) when is_atom(protocol) do
|
||||
beam_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
with {:ok, info} <- beam_protocol(protocol),
|
||||
{:ok, code, docs} <- change_debug_info(info, types),
|
||||
do: compile(code, docs)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
@@ -302,7 +293,7 @@ defmodule Protocol do
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
structs = types -- all
|
||||
case change_impl_for(code, protocol, types, structs, false, []) do
|
||||
{:ok, ret} -> {:ok, {ret, docs}}
|
||||
{:ok, ret} -> {:ok, ret, docs}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
@@ -320,7 +311,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
fallback = if Any in types, do: load_impl(protocol, Any)
|
||||
|
||||
clauses = for {guard, mod} <- builtin,
|
||||
mod in types,
|
||||
@@ -334,7 +325,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
fallback = if Any in types, do: load_impl(protocol, Any)
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
@@ -393,7 +384,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({{protocol, code}, docs}) do
|
||||
defp compile({protocol, code}, docs) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
unless docs == :missing_chunk do
|
||||
@@ -549,6 +540,7 @@ defmodule Protocol do
|
||||
"the dict[key] syntax, please implement the Dict behaviour instead"
|
||||
else
|
||||
Protocol.assert_protocol!(protocol)
|
||||
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
|
||||
end
|
||||
|
||||
defmodule name do
|
||||
@@ -589,6 +581,7 @@ defmodule Protocol do
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
__ensure_defimpl__(protocol, for, env)
|
||||
assert_impl!(protocol, Any, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
@@ -617,6 +610,17 @@ defmodule Protocol do
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __ensure_defimpl__(protocol, for, env) do
|
||||
if Protocol.consolidated?(protocol) do
|
||||
message =
|
||||
"the #{inspect protocol} protocol has already been consolidated" <>
|
||||
", an implementation for #{inspect for} has no effect"
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
|
||||
+23
-16
@@ -1,12 +1,12 @@
|
||||
defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
Defines a range.
|
||||
|
||||
A Range represents a discrete number of values where
|
||||
A range represents a discrete number of values where
|
||||
the first and last values are integers.
|
||||
|
||||
Ranges can be either increasing (first <= last) or
|
||||
decresing (first > last). Ranges are also always
|
||||
decreasing (first > last). Ranges are also always
|
||||
inclusive.
|
||||
|
||||
A Range is represented internally as a struct. However,
|
||||
@@ -15,7 +15,7 @@ defmodule Range do
|
||||
|
||||
iex> range = 1..3
|
||||
1..3
|
||||
iex> first .. last = range
|
||||
iex> first..last = range
|
||||
iex> first
|
||||
1
|
||||
iex> last
|
||||
@@ -31,12 +31,21 @@ defmodule Range do
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
def new(first, last) do
|
||||
@spec new(integer, integer) :: t
|
||||
def new(first, last) when is_integer(first) and is_integer(last) do
|
||||
%Range{first: first, last: last}
|
||||
end
|
||||
|
||||
def new(first, last) do
|
||||
raise ArgumentError,
|
||||
"ranges (first..last) expect both sides to be integers, " <>
|
||||
"got: #{inspect first}..#{inspect last}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given argument is a range.
|
||||
Returns `true` if the given `term` is a range.
|
||||
|
||||
It does not check if the range is valid.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -47,13 +56,15 @@ defmodule Range do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec range?(%Range{}) :: true
|
||||
@spec range?(term) :: false
|
||||
def range?(term)
|
||||
def range?(%Range{}), do: true
|
||||
def range?(_), do: false
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(first .. last, acc, fun) do
|
||||
validate_range!(first, last)
|
||||
reduce(first, last, acc, fun, last >= first)
|
||||
end
|
||||
|
||||
@@ -77,8 +88,7 @@ defimpl Enumerable, for: Range do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first .. last, value) do
|
||||
validate_range!(first, last)
|
||||
def member?(first .. last, value) when is_integer(value) do
|
||||
if first <= last do
|
||||
{:ok, first <= value and value <= last}
|
||||
else
|
||||
@@ -86,20 +96,17 @@ defimpl Enumerable, for: Range do
|
||||
end
|
||||
end
|
||||
|
||||
def member?(_ .. _, _value) do
|
||||
{:ok, false}
|
||||
end
|
||||
|
||||
def count(first .. last) do
|
||||
validate_range!(first, last)
|
||||
if first <= last do
|
||||
{:ok, last - first + 1}
|
||||
else
|
||||
{:ok, first - last + 1}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_range!(first, last) when is_integer(first) and is_integer(last), do: :ok
|
||||
defp validate_range!(first, last) do
|
||||
raise ArgumentError,
|
||||
"ranges (left .. right) expect both sides to be integers, got: #{inspect first..last}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Record do
|
||||
@moduledoc """
|
||||
Module to work, define and import records.
|
||||
Module to work with, define and import records.
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule Record do
|
||||
This module provides conveniences for working with records at
|
||||
compilation time, where compile-time field names are used to
|
||||
manipulate the tuples, providing fast operations on top of
|
||||
the tuples compact structure.
|
||||
the tuples' compact structure.
|
||||
|
||||
In Elixir, records are used mostly in two situations:
|
||||
|
||||
|
||||
+27
-24
@@ -1,6 +1,7 @@
|
||||
defmodule Regex do
|
||||
@moduledoc ~S"""
|
||||
Regular expressions for Elixir built on top of Erlang's `:re` module.
|
||||
Provides regular expressions for Elixir. Built on top of Erlang's `:re`
|
||||
module.
|
||||
|
||||
As the `:re` module, Regex is based on PCRE
|
||||
(Perl Compatible Regular Expressions). More information can be
|
||||
@@ -22,7 +23,7 @@ defmodule Regex do
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p` and change
|
||||
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
|
||||
It expects valid unicode strings to be given on match
|
||||
|
||||
@@ -55,7 +56,7 @@ defmodule Regex do
|
||||
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
Many functions in this module handle what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string
|
||||
@@ -379,7 +380,7 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) and is_list(opts) do
|
||||
on = Keyword.get(opts, :on, :first)
|
||||
case :re.run(string, compiled, [:global, capture: on]) do
|
||||
{:match, matches} ->
|
||||
@@ -469,11 +470,13 @@ defmodule Regex do
|
||||
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex, string, replacement, options) when is_binary(replacement) do
|
||||
def replace(regex, string, replacement, options)
|
||||
when is_binary(string) and is_binary(replacement) and is_list(options) do
|
||||
do_replace(regex, string, precompile_replacement(replacement), options)
|
||||
end
|
||||
|
||||
def replace(regex, string, replacement, options) when is_function(replacement) do
|
||||
def replace(regex, string, replacement, options)
|
||||
when is_binary(string) and is_function(replacement) and is_list(options) do
|
||||
{:arity, arity} = :erlang.fun_info(replacement, :arity)
|
||||
do_replace(regex, string, {replacement, arity}, options)
|
||||
end
|
||||
@@ -495,30 +498,30 @@ defmodule Regex do
|
||||
defp precompile_replacement(""),
|
||||
do: []
|
||||
|
||||
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
|
||||
{ns, <<?}, rest :: binary>>} = pick_int(rest)
|
||||
defp precompile_replacement(<<?\\, ?g, ?{, rest::binary>>) when byte_size(rest) > 0 do
|
||||
{ns, <<?}, rest::binary>>} = pick_int(rest)
|
||||
[List.to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<?\\, ?\\, rest :: binary>>) do
|
||||
defp precompile_replacement(<<?\\, ?\\, rest::binary>>) do
|
||||
[<<?\\>> | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x in ?0..?9 do
|
||||
defp precompile_replacement(<<?\\, x, rest::binary>>) when x in ?0..?9 do
|
||||
{ns, rest} = pick_int(rest)
|
||||
[List.to_integer([x|ns]) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<x, rest :: binary>>) do
|
||||
defp precompile_replacement(<<x, rest::binary>>) do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
[<<x, head :: binary>> | t]
|
||||
[<<x, head::binary>> | t]
|
||||
other ->
|
||||
[<<x>> | other]
|
||||
end
|
||||
end
|
||||
|
||||
defp pick_int(<<x, rest :: binary>>) when x in ?0..?9 do
|
||||
defp pick_int(<<x, rest::binary>>) when x in ?0..?9 do
|
||||
{found, rest} = pick_int(rest)
|
||||
{[x|found], rest}
|
||||
end
|
||||
@@ -541,12 +544,12 @@ defmodule Regex do
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||
length = mpos - pos
|
||||
<<untouched :: binary-size(length), rest :: binary>> = string
|
||||
<<untouched::binary-size(length), rest::binary>> = string
|
||||
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{pos, length} | _] = head | tail]) do
|
||||
<<_ :: size(length)-binary, rest :: binary>> = string
|
||||
<<_::size(length)-binary, rest::binary>> = string
|
||||
new_data = apply_replace(whole, replacement, head)
|
||||
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
|
||||
end
|
||||
@@ -579,7 +582,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp get_index(string, {pos, len}) do
|
||||
<<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
|
||||
<<_::size(pos)-binary, res::size(len)-binary, _::binary>> = string
|
||||
res
|
||||
end
|
||||
|
||||
@@ -629,17 +632,17 @@ defmodule Regex do
|
||||
|
||||
# Private Helpers
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
|
||||
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
|
||||
defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
|
||||
defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
|
||||
defp translate_options(<<?U, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
|
||||
defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
|
||||
defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
|
||||
defp translate_options(<<?u, t::binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
|
||||
defp translate_options(<<?i, t::binary>>, acc), do: translate_options(t, [:caseless|acc])
|
||||
defp translate_options(<<?x, t::binary>>, acc), do: translate_options(t, [:extended|acc])
|
||||
defp translate_options(<<?f, t::binary>>, acc), do: translate_options(t, [:firstline|acc])
|
||||
defp translate_options(<<?U, t::binary>>, acc), do: translate_options(t, [:ungreedy|acc])
|
||||
defp translate_options(<<?s, t::binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
|
||||
defp translate_options(<<?m, t::binary>>, acc), do: translate_options(t, [:multiline|acc])
|
||||
|
||||
# TODO: Deprecate by 1.2
|
||||
# TODO: Remove by 2.0
|
||||
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
|
||||
defp translate_options(<<?r, t::binary>>, acc), do: translate_options(t, [:ungreedy|acc])
|
||||
|
||||
defp translate_options(<<>>, acc), do: acc
|
||||
defp translate_options(rest, _acc), do: {:error, rest}
|
||||
|
||||
+10
-189
@@ -1,35 +1,16 @@
|
||||
defmodule Set do
|
||||
@moduledoc ~S"""
|
||||
This module specifies the `Set` behaviour expected to be
|
||||
implemented by different representations of sets.
|
||||
WARNING: this module is deprecated.
|
||||
|
||||
It also provides functions that redirect to the
|
||||
underlying implementation, allowing a developer to work with
|
||||
different `Set` implementations using a common API.
|
||||
|
||||
To create a new set, use the `new` function which each set implementation
|
||||
defines:
|
||||
|
||||
HashSet.new #=> creates an empty HashSet
|
||||
|
||||
In the examples below, `set_impl` means a specific
|
||||
`Set` implementation, for example `HashSet`.
|
||||
|
||||
## Protocols
|
||||
|
||||
Sets are required to implement both the `Enumerable` and `Collectable`
|
||||
protocols.
|
||||
|
||||
## Matching
|
||||
|
||||
Sets are required to implement all equality checks using the match (`===`)
|
||||
operator.
|
||||
Use the `MapSet` module instead.
|
||||
"""
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: map
|
||||
|
||||
# TODO: Remove callbacks on 1.3
|
||||
# TODO: Deprecate every function on 1.3
|
||||
@callback new :: t
|
||||
@callback delete(t, value) :: t
|
||||
@callback difference(t, t) :: t
|
||||
@@ -54,42 +35,10 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes `value` from `set`.
|
||||
|
||||
Returns a new set which is a copy of `set` but without `value`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 4) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 2) |> Enum.sort
|
||||
[1, 3]
|
||||
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(set, value) do
|
||||
target(set).delete(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
Note that this function is polymorphic as it calculates the difference for
|
||||
sets of the same type as well as of sets of different types. Each set
|
||||
implementation also provides a `difference` function which only works with
|
||||
sets of that type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(Enum.into([1, 2], set_impl.new), Enum.into([2, 3, 4], set_impl.new)) |> Enum.sort
|
||||
[1]
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -97,29 +46,12 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.difference(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.delete(acc, v)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` have no members in common.
|
||||
|
||||
Note that this function is polymorphic as it checks for disjoint sets of
|
||||
any type. Each set implementation also provides a `disjoint?` function,
|
||||
but that function can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
true
|
||||
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -127,7 +59,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.disjoint?(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, {:cont, true}, fn member, acc ->
|
||||
Enumerable.reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
@@ -137,28 +69,10 @@ defmodule Set do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two sets are equal using `===`.
|
||||
|
||||
Note that this function is polymorphic as it compares sets of
|
||||
any type. Each set implementation also provides an `equal?`
|
||||
function, but that function can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
|
||||
true
|
||||
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -175,23 +89,7 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing only members that `set1` and `set2` have in common.
|
||||
|
||||
Note that this function is polymorphic as it calculates the intersection of
|
||||
any type. Each set implementation also provides an `intersection` function,
|
||||
but that function can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(Enum.into([1, 2], set_impl.new), Enum.into([2, 3, 4], set_impl.new)) |> Enum.sort
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new)) |> Enum.sort
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -199,79 +97,26 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
Enumerable.reduce(set1, {:cont, target1.new}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set` contains `value`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
|
||||
true
|
||||
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec member?(t, value) :: boolean
|
||||
def member?(set, value) do
|
||||
target(set).member?(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts `value` into `set` if `set` doesn't already contain it.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@spec put(t, value) :: t
|
||||
def put(set, value) do
|
||||
target(set).put(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `set`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
|
||||
3
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(set) do
|
||||
target(set).size(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
This function checks if `set1` is a subset of `set2`.
|
||||
|
||||
Note that this function is polymorphic as it checks the subset for
|
||||
any type. Each set implementation also provides a `subset?` function,
|
||||
but that function can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
|
||||
true
|
||||
|
||||
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -283,34 +128,10 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts `set` to a list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: list
|
||||
def to_list(set) do
|
||||
target(set).to_list(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
Note that this function is polymorphic as it calculates the union of sets of
|
||||
any type. Each set implementation also provides a `union` function,
|
||||
but that function can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(Enum.into([1, 2], set_impl.new), Enum.into([2, 3, 4], set_impl.new)) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(set1, set2) do
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
@@ -318,14 +139,14 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.union(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.put(acc, v)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_subset?(target1, target2, set1, set2) do
|
||||
target1.reduce(set1, {:cont, true}, fn member, acc ->
|
||||
defp do_subset?(_target1, target2, set1, set2) do
|
||||
Enumerable.reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
|
||||
+26
-19
@@ -22,7 +22,7 @@ defmodule Stream do
|
||||
|
||||
Notice we started with a range and then we created a stream that is
|
||||
meant to multiply each item in the range by 2. At this point, no
|
||||
computation was done yet. Just when `Enum.map/2` is called we
|
||||
computation was done. Only when `Enum.map/2` is called we actually
|
||||
enumerate over each item in the range, multiplying it by 2 and adding 1.
|
||||
We say the functions in `Stream` are *lazy* and the functions in `Enum`
|
||||
are *eager*.
|
||||
@@ -33,10 +33,10 @@ defmodule Stream do
|
||||
computations that are executed at a later moment. Let's see another
|
||||
example:
|
||||
|
||||
1..3 |>
|
||||
Enum.map(&IO.inspect(&1)) |>
|
||||
Enum.map(&(&1 * 2)) |>
|
||||
Enum.map(&IO.inspect(&1))
|
||||
1..3
|
||||
|> Enum.map(&IO.inspect(&1))
|
||||
|> Enum.map(&(&1 * 2))
|
||||
|> Enum.map(&IO.inspect(&1))
|
||||
1
|
||||
2
|
||||
3
|
||||
@@ -49,10 +49,10 @@ defmodule Stream do
|
||||
element by 2 and finally printed each new value. In this example, the list
|
||||
was enumerated three times. Let's see an example with streams:
|
||||
|
||||
stream = 1..3 |>
|
||||
Stream.map(&IO.inspect(&1)) |>
|
||||
Stream.map(&(&1 * 2)) |>
|
||||
Stream.map(&IO.inspect(&1))
|
||||
stream = 1..3
|
||||
|> Stream.map(&IO.inspect(&1))
|
||||
|> Stream.map(&(&1 * 2))
|
||||
|> Stream.map(&IO.inspect(&1))
|
||||
Enum.to_list(stream)
|
||||
1
|
||||
2
|
||||
@@ -66,10 +66,10 @@ defmodule Stream do
|
||||
printed changed! With streams, we print the first item and then print
|
||||
its double. In this example, the list was enumerated just once!
|
||||
|
||||
That's what we meant when we first said that streams are composable,
|
||||
That's what we meant when we said earlier that streams are composable,
|
||||
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
|
||||
effectively composing the streams and they are lazy. The computations
|
||||
are performed only when you call a function from the `Enum` module.
|
||||
effectively composing the streams and keeping them lazy. The computations
|
||||
are only performed when you call a function from the `Enum` module.
|
||||
|
||||
## Creating Streams
|
||||
|
||||
@@ -85,7 +85,7 @@ defmodule Stream do
|
||||
Note the functions in this module are guaranteed to return enumerables.
|
||||
Since enumerables can have different shapes (structs, anonymous functions,
|
||||
and so on), the functions in this module may return any of those shapes
|
||||
and that it may change at any time. For example, a function that today
|
||||
and that this may change at any time. For example, a function that today
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
@@ -96,7 +96,7 @@ defmodule Stream do
|
||||
@type element :: any
|
||||
@type index :: non_neg_integer
|
||||
@type default :: any
|
||||
@type t :: %__MODULE__{}
|
||||
@opaque t :: %__MODULE__{}
|
||||
|
||||
# Require Stream.Reducers and its callbacks
|
||||
require Stream.Reducers, as: R
|
||||
@@ -807,7 +807,7 @@ defmodule Stream do
|
||||
@spec uniq(Enumerable.t) :: Enumerable.t
|
||||
@spec uniq(Enumerable.t, (element -> term)) :: Enumerable.t
|
||||
def uniq(enum, fun \\ fn x -> x end) do
|
||||
lazy enum, HashSet.new, fn f1 -> R.uniq(fun, f1) end
|
||||
lazy enum, %{}, fn f1 -> R.uniq(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -820,10 +820,15 @@ defmodule Stream do
|
||||
iex> Enum.to_list(stream)
|
||||
[{1, 0}, {2, 1}, {3, 2}]
|
||||
|
||||
iex> stream = Stream.with_index([1, 2, 3], 3)
|
||||
iex> Enum.to_list(stream)
|
||||
[{1, 3}, {2, 4}, {3, 5}]
|
||||
|
||||
"""
|
||||
@spec with_index(Enumerable.t) :: Enumerable.t
|
||||
def with_index(enum) do
|
||||
lazy enum, 0, fn(f1) -> R.with_index(f1) end
|
||||
@spec with_index(Enumerable.t, integer) :: Enumerable.t
|
||||
def with_index(enum, offset \\ 0) do
|
||||
lazy enum, offset, fn(f1) -> R.with_index(f1) end
|
||||
end
|
||||
|
||||
## Combiners
|
||||
@@ -1023,8 +1028,10 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
|
||||
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
|
||||
# Although not necessary, let's seed the random algorithm
|
||||
iex> :rand.seed(:exsplus, {1, 2, 3})
|
||||
iex> Stream.repeatedly(&:rand.uniform/0) |> Enum.take(3)
|
||||
[0.40502929729990744, 0.45336720247823126, 0.04094511692041057]
|
||||
|
||||
"""
|
||||
@spec repeatedly((() -> element)) :: Enumerable.t
|
||||
|
||||
@@ -187,10 +187,10 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
if HashSet.member?(prev, value) do
|
||||
if Map.has_key?(prev, value) do
|
||||
skip(acc)
|
||||
else
|
||||
next_with_acc(unquote(f), entry, h, HashSet.put(prev, value), t)
|
||||
next_with_acc(unquote(f), entry, h, Map.put(prev, value, true), t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+283
-67
@@ -7,10 +7,12 @@ defmodule String do
|
||||
## Codepoints and graphemes
|
||||
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0. As per the standard, a codepoint is
|
||||
a Unicode Character, which may be represented by one or more
|
||||
bytes. For example, the character "é" is represented with two
|
||||
bytes:
|
||||
Standard, version 6.3.0.
|
||||
|
||||
As per the standard, a codepoint is a single Unicode Character,
|
||||
which may be represented by one or more bytes.
|
||||
|
||||
For example, the codepoint "é" is two bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
2
|
||||
@@ -21,16 +23,21 @@ defmodule String do
|
||||
1
|
||||
|
||||
Furthermore, this module also presents the concept of
|
||||
graphemes, which are multiple characters that may be
|
||||
"perceived as a single character" by readers. For example,
|
||||
the same "é" character written above could be represented
|
||||
by the letter "e" followed by the accent ́:
|
||||
graphemes. A single grapheme can consist of multiple codepoints
|
||||
that may be perceived as a single character by readers. For example,
|
||||
the "é" grapheme can be represented either as a single "e with acute"
|
||||
codepoint (like above), or as the letter "e" followed by a
|
||||
"combining acute accent" (two codepoints):
|
||||
|
||||
iex> string = "\u0065\u0301"
|
||||
iex> byte_size(string)
|
||||
3
|
||||
iex> String.length(string)
|
||||
1
|
||||
iex> String.codepoints(string)
|
||||
["e", "́"]
|
||||
iex> String.graphemes(string)
|
||||
["é"]
|
||||
|
||||
Although the example above is made of two characters, it is
|
||||
perceived by users as one.
|
||||
@@ -46,16 +53,16 @@ defmodule String do
|
||||
|
||||
More information about graphemes can be found in the [Unicode
|
||||
Standard Annex #29](http://www.unicode.org/reports/tr29/).
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
The current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
To act accordingly to the Unicode Standard, many functions
|
||||
in this module runs in linear time, as it needs to traverse
|
||||
To act according to the Unicode Standard, many functions
|
||||
in this module run in linear time, as they need to traverse
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
|
||||
For example, `String.length/1` is going to take longer as
|
||||
For example, `String.length/1` will take longer as
|
||||
the input grows. On the other hand, `Kernel.byte_size/1` always runs
|
||||
in constant time (i.e. regardless of the input size).
|
||||
|
||||
@@ -88,7 +95,7 @@ defmodule String do
|
||||
fully, so we traverse both `prefix` and `full` strings, then
|
||||
slice the `full` one, traversing it again.
|
||||
|
||||
A first attempting at improving it could be with ranges:
|
||||
A first attempt at improving it could be with ranges:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
@@ -114,7 +121,7 @@ defmodule String do
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
...> <<_::binary-size(base), rest::binary>> = full
|
||||
...> rest
|
||||
...> end
|
||||
iex> take_prefix.("Mr. John", "Mr. ")
|
||||
@@ -123,7 +130,7 @@ defmodule String do
|
||||
On the other hand, if you want to dynamically slice a string
|
||||
based on an integer value, then using `String.slice/3` is the
|
||||
best option as it guarantees we won't incorrectly split a valid
|
||||
codepoint in multiple bytes.
|
||||
codepoint into multiple bytes.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
@@ -144,7 +151,7 @@ defmodule String do
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "á"
|
||||
iex> <<eacute::utf8>> = "á"
|
||||
iex> eacute
|
||||
225
|
||||
|
||||
@@ -162,12 +169,12 @@ defmodule String do
|
||||
codepoint needs to be rejected.
|
||||
|
||||
This module relies on this behaviour to ignore such invalid
|
||||
characters. For example, `length/1` is going to return
|
||||
characters. For example, `length/1` will return
|
||||
a correct result even if an invalid codepoint is fed into it.
|
||||
|
||||
In other words, this module expects invalid data to be detected
|
||||
when retrieving data from the external source. For example, a
|
||||
driver that reads strings from a database will be the one
|
||||
driver that reads strings from a database will be
|
||||
responsible to check the validity of the encoding.
|
||||
|
||||
## Patterns
|
||||
@@ -211,7 +218,7 @@ defmodule String do
|
||||
@spec printable?(t) :: boolean
|
||||
def printable?(string)
|
||||
|
||||
def printable?(<< h :: utf8, t :: binary >>)
|
||||
def printable?(<<h::utf8, t::binary >>)
|
||||
when h in 0x20..0x7E
|
||||
when h in 0xA0..0xD7FF
|
||||
when h in 0xE000..0xFFFD
|
||||
@@ -219,15 +226,15 @@ defmodule String do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
def printable?(<<?\n, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\r, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\t, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\v, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\b, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\f, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\e, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\d, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\a, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\n, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\r, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\t, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\v, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\b, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\f, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\e, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\d, t::binary>>), do: printable?(t)
|
||||
def printable?(<<?\a, t::binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(binary) when is_binary(binary), do: false
|
||||
@@ -294,7 +301,7 @@ defmodule String do
|
||||
iex> String.split(" a b c ", ~r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty patterns returns codepoints:
|
||||
Splitting on empty patterns returns graphemes:
|
||||
|
||||
iex> String.split("abc", ~r{})
|
||||
["a", "b", "c", ""]
|
||||
@@ -319,15 +326,15 @@ defmodule String do
|
||||
@spec split(t, pattern | Regex.t, Keyword.t) :: [t]
|
||||
def split(string, pattern, options \\ [])
|
||||
|
||||
def split(string, %Regex{} = pattern, options) do
|
||||
def split(string, %Regex{} = pattern, options) when is_binary(string) do
|
||||
Regex.split(pattern, string, options)
|
||||
end
|
||||
|
||||
def split(string, pattern, []) when pattern != "" do
|
||||
def split(string, pattern, []) when is_binary(string) and pattern != "" do
|
||||
:binary.split(string, pattern, [:global])
|
||||
end
|
||||
|
||||
def split(string, pattern, options) do
|
||||
def split(string, pattern, options) when is_binary(string) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
pattern = maybe_compile_pattern(pattern)
|
||||
@@ -441,6 +448,66 @@ defmodule String do
|
||||
{binary_part(string, 0, byte_size), rest || ""}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Returns `true` if `binary` is canonically equivalent to 'another_binary'.
|
||||
|
||||
It performs Normalization Form Canonical Decomposition (NFD) on the
|
||||
strings before comparing them. This function is equivalent to:
|
||||
|
||||
String.normalize(left, :nfd) == String.normalize(right, :nfd)
|
||||
|
||||
Therefore, if you plan to compare multiple strings, multiple times
|
||||
in a row, you may normalize them upfront and compare them directly
|
||||
to avoid multiple normalization passes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.equivalent?("abc", "abc")
|
||||
true
|
||||
|
||||
iex> String.equivalent?("man\u0303ana", "mañana")
|
||||
true
|
||||
|
||||
iex> String.equivalent?("abc", "ABC")
|
||||
false
|
||||
|
||||
iex> String.equivalent?("nø", "nó")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equivalent?(t, t) :: boolean
|
||||
def equivalent?(left, right) do
|
||||
normalize(left, :nfd) == normalize(right, :nfd)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts all characters in `binary` to Unicode normalization
|
||||
form identified by `form`.
|
||||
|
||||
## Forms
|
||||
|
||||
The supported forms are:
|
||||
|
||||
* `:nfd` - Normalization Form Canonical Decomposition.
|
||||
Characters are decomposed by canonical equivalence, and
|
||||
multiple combining characters are arranged in a specific
|
||||
order.
|
||||
|
||||
* `:nfc` - Normalization Form Canonical Composition.
|
||||
Characters are decomposed and then recomposed by canonical equivalence.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.normalize("yêṩ", :nfd)
|
||||
"yêṩ"
|
||||
|
||||
iex> String.normalize("leña", :nfc)
|
||||
"leña"
|
||||
|
||||
"""
|
||||
@spec normalize(t, atom) :: boolean
|
||||
defdelegate normalize(binary, form), to: String.Normalizer
|
||||
|
||||
@doc """
|
||||
Converts all characters in the given string to uppercase.
|
||||
|
||||
@@ -527,39 +594,159 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec rstrip(t, char) :: t
|
||||
def rstrip(string, char) when is_integer(char) do
|
||||
replace_trailing(string, <<char::utf8>>, "")
|
||||
end
|
||||
|
||||
def rstrip("", _char), do: ""
|
||||
@doc """
|
||||
Replaces all leading occurences of `match` by `replacement` of `match` in `string`.
|
||||
|
||||
# Do a quick check before we traverse the whole
|
||||
# binary. :binary.last is a fast operation (it
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) when char in 0..127 do
|
||||
if :binary.last(string) == char do
|
||||
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
|
||||
else
|
||||
string
|
||||
Returns the string untouched if there are no occurrences.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.replace_leading("hello world", "hello ", "")
|
||||
"world"
|
||||
iex> String.replace_leading("hello hello world", "hello ", "")
|
||||
"world"
|
||||
|
||||
iex> String.replace_leading("hello world", "hello ", "ola ")
|
||||
"ola world"
|
||||
iex> String.replace_leading("hello hello world", "hello ", "ola ")
|
||||
"ola ola world"
|
||||
|
||||
"""
|
||||
def replace_leading(string, match, replacement)
|
||||
when is_binary(string) and is_binary(match) and is_binary(replacement) do
|
||||
prefix_size = byte_size(match)
|
||||
suffix_size = byte_size(string) - prefix_size
|
||||
replace_leading(string, match, replacement, prefix_size, suffix_size, "")
|
||||
end
|
||||
|
||||
defp replace_leading(string, match, replacement, prefix_size, suffix_size, acc) when suffix_size > 0 do
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when prefix == match ->
|
||||
replace_leading(suffix, match, replacement, prefix_size, suffix_size - prefix_size, acc <> replacement)
|
||||
_ ->
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
def rstrip(string, char) when is_integer(char) do
|
||||
do_rstrip(string, "", char)
|
||||
defp replace_leading(string, _match, _replacement, _prefix_size, _suffix_size, prefix) do
|
||||
prefix <> string
|
||||
end
|
||||
|
||||
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, char) do
|
||||
<<do_rstrip(string, <<char :: utf8, buffer :: binary>>, char) :: binary>>
|
||||
@doc """
|
||||
Replaces all trailing occurences of `match` by `replacement` in `string`.
|
||||
|
||||
Returns the string untouched if there are no occurrences.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.replace_trailing("hello world", " world", "")
|
||||
"hello"
|
||||
iex> String.replace_trailing("hello world world", " world", "")
|
||||
"hello"
|
||||
|
||||
iex> String.replace_trailing("hello world", " world", " mundo")
|
||||
"hello mundo"
|
||||
iex> String.replace_trailing("hello world world", " world", " mundo")
|
||||
"hello mundo mundo"
|
||||
|
||||
"""
|
||||
def replace_trailing(string, match, replacement)
|
||||
when is_binary(string) and is_binary(match) and is_binary(replacement) do
|
||||
suffix_size = byte_size(match)
|
||||
prefix_size = byte_size(string) - suffix_size
|
||||
replace_trailing(string, match, replacement, prefix_size, suffix_size, "")
|
||||
end
|
||||
|
||||
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char :: utf8, do_rstrip(string, "", another_char) :: binary>>
|
||||
defp replace_trailing(string, match, replacement, prefix_size, suffix_size, acc) when prefix_size > 0 do
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when suffix == match ->
|
||||
replace_trailing(prefix, match, replacement, prefix_size - suffix_size, suffix_size, acc <> replacement)
|
||||
_ ->
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _, _) do
|
||||
<<>>
|
||||
defp replace_trailing(string, _match, _replacement, _prefix_size, _suffix_size, suffix) do
|
||||
string <> suffix
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replaces prefix in `string` by `replacement` if it matches `match`.
|
||||
|
||||
Returns the string untouched if there is no match.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.replace_prefix("world", "hello ", "")
|
||||
"world"
|
||||
iex> String.replace_prefix("hello world", "hello ", "")
|
||||
"world"
|
||||
iex> String.replace_prefix("hello hello world", "hello ", "")
|
||||
"hello world"
|
||||
|
||||
iex> String.replace_prefix("world", "hello ", "ola ")
|
||||
"world"
|
||||
iex> String.replace_prefix("hello world", "hello ", "ola ")
|
||||
"ola world"
|
||||
iex> String.replace_prefix("hello hello world", "hello ", "ola ")
|
||||
"ola hello world"
|
||||
|
||||
"""
|
||||
def replace_prefix(string, match, replacement)
|
||||
when is_binary(string) and is_binary(match) and is_binary(replacement) do
|
||||
prefix_size = byte_size(match)
|
||||
suffix_size = byte_size(string) - prefix_size
|
||||
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when prefix == match ->
|
||||
replacement <> suffix
|
||||
_ ->
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replaces suffix in `string` by `replacement` if it matches `match`.
|
||||
|
||||
Returns the string untouched if there is no match.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.replace_suffix("hello", " world", "")
|
||||
"hello"
|
||||
iex> String.replace_suffix("hello world", " world", "")
|
||||
"hello"
|
||||
iex> String.replace_suffix("hello world world", " world", "")
|
||||
"hello world"
|
||||
|
||||
iex> String.replace_suffix("hello", " world", " mundo")
|
||||
"hello"
|
||||
iex> String.replace_suffix("hello world", " world", " mundo")
|
||||
"hello mundo"
|
||||
iex> String.replace_suffix("hello world world", " world", " mundo")
|
||||
"hello world mundo"
|
||||
|
||||
"""
|
||||
def replace_suffix(string, match, replacement)
|
||||
when is_binary(string) and is_binary(match) and is_binary(replacement) do
|
||||
suffix_size = byte_size(match)
|
||||
prefix_size = byte_size(string) - suffix_size
|
||||
|
||||
case string do
|
||||
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when suffix == match ->
|
||||
prefix <> replacement
|
||||
_ ->
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where all leading Unicode whitespaces
|
||||
has been removed.
|
||||
have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -578,12 +765,11 @@ defmodule String do
|
||||
" abc _"
|
||||
|
||||
"""
|
||||
|
||||
@spec lstrip(t, char) :: t
|
||||
def lstrip(string, char)
|
||||
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
def lstrip(<<char::utf8, rest::binary>>, char) when is_integer(char) do
|
||||
<<lstrip(rest, char)::binary>>
|
||||
end
|
||||
|
||||
def lstrip(string, char) when is_integer(char) do
|
||||
@@ -592,7 +778,7 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Returns a string where all leading and trailing Unicode whitespaces
|
||||
has been removed.
|
||||
have been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -617,7 +803,6 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec strip(t, char) :: t
|
||||
|
||||
def strip(string, char) do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
@@ -673,7 +858,7 @@ defmodule String do
|
||||
subject_len >= len ->
|
||||
subject
|
||||
subject_len < len ->
|
||||
fill = duplicate(<<padding :: utf8>>, len - subject_len)
|
||||
fill = duplicate(<<padding::utf8>>, len - subject_len)
|
||||
|
||||
case type do
|
||||
:left -> subject <> fill
|
||||
@@ -683,11 +868,11 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Returns a new binary created by replacing occurences of `pattern` in
|
||||
Returns a new string created by replacing occurences of `pattern` in
|
||||
`subject` with `replacement`.
|
||||
|
||||
By default, it replaces all occurences, unless the `global` option is
|
||||
set to `false`.
|
||||
set to `false`, where it will only replace the first one
|
||||
|
||||
The `pattern` may be a string or a regular expression.
|
||||
|
||||
@@ -746,8 +931,8 @@ defmodule String do
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reverses the given string. Works on graphemes.
|
||||
@doc ~S"""
|
||||
Reverses the graphemes in given string.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -760,6 +945,21 @@ defmodule String do
|
||||
iex> String.reverse("hello ∂og")
|
||||
"go∂ olleh"
|
||||
|
||||
Keep in mind reversing the same string twice does
|
||||
not necessarily yield the original string:
|
||||
|
||||
iex> "̀e"
|
||||
"̀e"
|
||||
iex> String.reverse("̀e")
|
||||
"è"
|
||||
iex> String.reverse String.reverse("̀e")
|
||||
"è"
|
||||
|
||||
In the first example the accent is before the vowel, so
|
||||
it is considered two graphemes. However, when you reverse
|
||||
it once, you have the vowel followed by the accent, which
|
||||
becomes one grapheme. Reversing it again will keep it as
|
||||
one single grapheme.
|
||||
"""
|
||||
@spec reverse(t) :: t
|
||||
def reverse(string) do
|
||||
@@ -795,6 +995,8 @@ defmodule String do
|
||||
@doc """
|
||||
Returns all codepoints in the string.
|
||||
|
||||
For details about codepoints and graphemes, see the `String` module documentation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.codepoints("olá")
|
||||
@@ -806,6 +1008,12 @@ defmodule String do
|
||||
iex> String.codepoints("ἅἪῼ")
|
||||
["ἅ", "Ἢ", "ῼ"]
|
||||
|
||||
iex> String.codepoints("\u00e9")
|
||||
["é"]
|
||||
|
||||
iex> String.codepoints("\u0065\u0301")
|
||||
["e", "́"]
|
||||
|
||||
"""
|
||||
@spec codepoints(t) :: [codepoint]
|
||||
defdelegate codepoints(string), to: String.Unicode
|
||||
@@ -860,10 +1068,10 @@ defmodule String do
|
||||
0x9FFFE, 0x9FFFF, 0x10FFFE, 0x10FFFF]
|
||||
|
||||
for noncharacter <- noncharacters do
|
||||
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
|
||||
def valid?(<<unquote(noncharacter)::utf8, _::binary >>), do: false
|
||||
end
|
||||
|
||||
def valid?(<<_ :: utf8, t :: binary>>), do: valid?(t)
|
||||
def valid?(<<_::utf8, t::binary>>), do: valid?(t)
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@@ -890,7 +1098,7 @@ defmodule String do
|
||||
"""
|
||||
@spec valid_character?(t) :: boolean
|
||||
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(<<_::utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc ~S"""
|
||||
@@ -956,11 +1164,19 @@ defmodule String do
|
||||
Cluster algorithm outlined in the [Unicode Standard Annex #29,
|
||||
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
|
||||
|
||||
For details about codepoints and graphemes, see the `String` module documentation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.graphemes("Ńaïve")
|
||||
["Ń", "a", "ï", "v", "e"]
|
||||
|
||||
iex> String.graphemes("\u00e9")
|
||||
["é"]
|
||||
|
||||
iex> String.graphemes("\u0065\u0301")
|
||||
["é"]
|
||||
|
||||
"""
|
||||
@spec graphemes(t) :: [grapheme]
|
||||
defdelegate graphemes(string), to: String.Graphemes
|
||||
@@ -1065,7 +1281,7 @@ defmodule String do
|
||||
defdelegate length(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
Returns the grapheme at the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, then it returns `nil`.
|
||||
|
||||
## Examples
|
||||
@@ -1113,8 +1329,8 @@ defmodule String do
|
||||
|
||||
If the offset is greater than string length, then it returns `""`.
|
||||
|
||||
Remember this function works with Unicode codepoints and considers
|
||||
the slices to represent codepoint offsets. If you want to split
|
||||
Remember this function works with Unicode graphemes and considers
|
||||
the slices to represent grapheme offsets. If you want to split
|
||||
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||
|
||||
## Examples
|
||||
@@ -1536,7 +1752,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
|
||||
@spec jaro_distance(t, t) :: 0..1
|
||||
@spec jaro_distance(t, t) :: float
|
||||
def jaro_distance(string1, string2)
|
||||
|
||||
def jaro_distance(string, string), do: 1.0
|
||||
|
||||
@@ -2,14 +2,14 @@ import Kernel, except: [to_string: 1]
|
||||
|
||||
defprotocol String.Chars do
|
||||
@moduledoc ~S"""
|
||||
The String.Chars protocol is responsible for
|
||||
The `String.Chars` protocol is responsible for
|
||||
converting a structure to a Binary (only if applicable).
|
||||
The only function required to be implemented is
|
||||
`to_string` which does the conversion.
|
||||
|
||||
The `to_string` function automatically imported
|
||||
by Kernel invokes this protocol. String
|
||||
interpolation also invokes to_string in its
|
||||
interpolation also invokes `to_string` in its
|
||||
arguments. For example, `"foo#{bar}"` is the same
|
||||
as `"foo" <> to_string(bar)`.
|
||||
"""
|
||||
|
||||
@@ -128,12 +128,12 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, chars}, %{output: output} = s) do
|
||||
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
|
||||
{:ok, %{s | output: <<output::binary, IO.chardata_to_string(chars)::binary>>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
|
||||
chars = apply(m, f, as)
|
||||
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
|
||||
{:ok, %{s | output: <<output::binary, IO.chardata_to_string(chars)::binary>>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, _encoding, chars}, s) do
|
||||
@@ -205,7 +205,7 @@ defmodule StringIO do
|
||||
{error, s}
|
||||
{result, input} ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
|
||||
output = <<output::binary, IO.chardata_to_string(prompt)::binary>>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
@@ -221,7 +221,7 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<<chars :: binary-size(n), rest :: binary>> = input
|
||||
<<chars::binary-size(n), rest::binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
@@ -231,7 +231,7 @@ defmodule StringIO do
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
{_buf_count, split_pos} ->
|
||||
<<chars :: binary-size(split_pos), rest :: binary>> = input
|
||||
<<chars::binary-size(split_pos), rest::binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
catch
|
||||
@@ -253,7 +253,7 @@ defmodule StringIO do
|
||||
{result, input} = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
|
||||
output = <<output::binary, IO.chardata_to_string(prompt)::binary>>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
@@ -283,7 +283,7 @@ defmodule StringIO do
|
||||
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(IO.chardata_to_string(prompt), count) :: binary >>
|
||||
output = <<output::binary, :binary.copy(IO.chardata_to_string(prompt), count)::binary>>
|
||||
end
|
||||
|
||||
input =
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
defmodule Supervisor do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
A behaviour module for implementing supervision functionality.
|
||||
|
||||
A supervisor is a process which supervises other processes called
|
||||
A supervisor is a process which supervises other processes, called
|
||||
child processes. Supervisors are used to build a hierarchical process
|
||||
structure called a supervision tree, a nice way to structure fault-tolerant
|
||||
applications.
|
||||
@@ -80,7 +80,7 @@ defmodule Supervisor do
|
||||
configuration, shutdown values, and restart strategies.
|
||||
|
||||
Continue reading this moduledoc to learn more about supervision strategies
|
||||
and then follow to the `Supervisor.Spec` module documentation to learn
|
||||
and then proceed to the `Supervisor.Spec` module documentation to learn
|
||||
about the specification for workers and supervisors.
|
||||
|
||||
## Module-based supervisors
|
||||
@@ -107,13 +107,13 @@ defmodule Supervisor do
|
||||
|
||||
You may want to use a module-based supervisor if:
|
||||
|
||||
* You need to do some particular action on supervisor
|
||||
* You need to perform some particular action on supervisor
|
||||
initialization, like setting up an ETS table.
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
new children directly, while dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps.
|
||||
|
||||
@@ -161,7 +161,7 @@ defmodule Supervisor do
|
||||
* The simple one for one specification can define only one child which
|
||||
works as a template for when we call `start_child/2`
|
||||
|
||||
* We have define the child to have restart strategy of transient. This
|
||||
* We have defined the child to have a restart strategy of transient. This
|
||||
means that, if the child process exits due to a `:normal`, `:shutdown`
|
||||
or `{:shutdown, term}` reason, it won't be restarted. This is useful
|
||||
as it allows our workers to politely shutdown and be removed from the
|
||||
@@ -187,18 +187,18 @@ defmodule Supervisor do
|
||||
strategy for the worker does not restart the child in case it crashes with
|
||||
reason `:normal`, `:shutdown` or `{:shutdown, term}`.
|
||||
|
||||
So one may ask: which exit reason should I choose when existing my worker?
|
||||
So one may ask: which exit reason should I choose when exiting my worker?
|
||||
There are three options:
|
||||
|
||||
* `:normal` - on such cases, the exit won't be logged, there is no restart
|
||||
on transient mode and linked processes do not exit
|
||||
* `:normal` - in such cases, the exit won't be logged, there is no restart
|
||||
in transient mode and linked processes do not exit
|
||||
|
||||
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
|
||||
logged, there is no restart on transient mode and linked processes exit
|
||||
* `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be
|
||||
logged, there is no restart in transient mode and linked processes exit
|
||||
with the same reason unless trapping exits
|
||||
|
||||
* any other term - on such cases, the exit will be logged, there are
|
||||
restarts on transient mode and linked processes exit with the same reason
|
||||
* any other term - in such cases, the exit will be logged, there are
|
||||
restarts in transient mode and linked processes exit with the same reason
|
||||
unless trapping exits
|
||||
|
||||
## Name Registration
|
||||
@@ -463,6 +463,22 @@ defmodule Supervisor do
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the supervisor with the given `reason`.
|
||||
|
||||
It returns `:ok` if the supervisor terminates with the given
|
||||
reason, if it terminates with another reason, the call will
|
||||
exit.
|
||||
|
||||
This function keeps OTP semantics regarding error reporting.
|
||||
If the reason is any other than `:normal`, `:shutdown` or
|
||||
`{:shutdown, _}`, an error report will be logged.
|
||||
"""
|
||||
@spec stop(supervisor, reason :: term, timeout) :: :ok
|
||||
def stop(supervisor, reason \\ :normal, timeout \\ :infinity) do
|
||||
:gen.stop(supervisor, reason, timeout)
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
|
||||
@@ -52,7 +52,7 @@ defmodule Supervisor.Spec do
|
||||
|
||||
## Supervisor and worker options
|
||||
|
||||
In the example above, we have defined workers and supervisors
|
||||
In the example above, we defined workers and supervisors
|
||||
and each accepts the following options:
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
@@ -61,7 +61,7 @@ defmodule Supervisor.Spec do
|
||||
|
||||
* `:function` - the function to invoke on the child to start it
|
||||
|
||||
* `:restart` - defines when the child process should restart
|
||||
* `:restart` - defines when a terminated child process should be restarted
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated
|
||||
|
||||
@@ -90,11 +90,11 @@ defmodule Supervisor.Spec do
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`.
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
* `:infinity` - if the child process is a supervisor, this is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care.
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
* Finally, the value can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in milliseconds), the child process is
|
||||
|
||||
+171
-13
@@ -25,15 +25,15 @@ defmodule System do
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to run "git describe --always --tags". In the case of success returns
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# Tries to run "git rev-parse --short HEAD". In the case of success returns
|
||||
# the short revision hash. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
defmacrop get_revision do
|
||||
dirpath = :filename.join(__DIR__, "../../../.git")
|
||||
case :file.read_file_info(dirpath) do
|
||||
{:ok, _} ->
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
data = :os.cmd('git rev-parse --short HEAD')
|
||||
strip_re(data, "\n")
|
||||
else
|
||||
read_stripped(:filename.join(".git", "HEAD"))
|
||||
@@ -47,6 +47,21 @@ defmodule System do
|
||||
IO.iodata_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the endianness.
|
||||
"""
|
||||
def endianness do
|
||||
:erlang.system_info(:endian)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the endianness the system was compiled with.
|
||||
"""
|
||||
@endianness :erlang.system_info(:endian)
|
||||
def compiled_endianness do
|
||||
@endianness
|
||||
end
|
||||
|
||||
@doc """
|
||||
Elixir version information.
|
||||
|
||||
@@ -58,11 +73,11 @@ defmodule System do
|
||||
@doc """
|
||||
Elixir build information.
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
Returns a keyword list with Elixir version, git short revision hash and compilation date.
|
||||
"""
|
||||
@spec build_info() :: map
|
||||
def build_info do
|
||||
%{version: version, tag: get_describe, date: get_date}
|
||||
%{version: version, date: get_date, revision: get_revision}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -284,9 +299,9 @@ defmodule System do
|
||||
Sets a new value for each environment variable corresponding
|
||||
to each key in `dict`.
|
||||
"""
|
||||
@spec put_env(Dict.t) :: :ok
|
||||
def put_env(dict) do
|
||||
Enum.each dict, fn {key, val} -> put_env key, val end
|
||||
@spec put_env(Enumerable.t) :: :ok
|
||||
def put_env(enum) do
|
||||
Enum.each enum, fn {key, val} -> put_env key, val end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -367,13 +382,13 @@ defmodule System do
|
||||
and the command exit status.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
iex> System.cmd "echo", ["hello"]
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd "echo", ["hello"], env: [{"MIX_ENV", "test"}]
|
||||
{"hello\n", 0}
|
||||
|
||||
|
||||
iex> System.cmd "echo", ["hello"], into: IO.stream(:stdio, :line)
|
||||
hello
|
||||
{%IO.Stream{}, 0}
|
||||
@@ -437,7 +452,16 @@ defmodule System do
|
||||
|
||||
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
|
||||
{initial, fun} = Collectable.into(into)
|
||||
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
|
||||
try do
|
||||
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
fun.(initial, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{acc, status} -> {fun.(acc, :done), status}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_cmd(port, acc, fun) do
|
||||
@@ -445,7 +469,7 @@ defmodule System do
|
||||
{^port, {:data, data}} ->
|
||||
do_cmd(port, fun.(acc, {:cont, data}), fun)
|
||||
{^port, {:exit_status, status}} ->
|
||||
{fun.(acc, :done), status}
|
||||
{acc, status}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -478,10 +502,144 @@ defmodule System do
|
||||
|
||||
defp validate_env(enum) do
|
||||
Enum.map enum, fn
|
||||
{k, nil} ->
|
||||
{String.to_char_list(k), false}
|
||||
{k, v} ->
|
||||
{String.to_char_list(k), String.to_char_list(v)}
|
||||
other ->
|
||||
raise ArgumentError, "invalid environment key-value #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current monotonic time in the `:native` time unit.
|
||||
|
||||
This time is monotonically increasing and starts in an unspecified point in
|
||||
time.
|
||||
|
||||
For more information, see the [chapter on time and time
|
||||
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
|
||||
Erlang docs.
|
||||
|
||||
Inlined by the compiler into `:erlang.monotonic_time/0`.
|
||||
"""
|
||||
@spec monotonic_time() :: integer
|
||||
def monotonic_time do
|
||||
:erlang.monotonic_time()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current monotonic time in the given time unit.
|
||||
|
||||
For more information, see the [chapter on time and time
|
||||
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
|
||||
Erlang docs.
|
||||
|
||||
Inlined by the compiler into `:erlang.monotonic_time/1`.
|
||||
"""
|
||||
@spec monotonic_time(:erlang.time_unit) :: integer
|
||||
def monotonic_time(unit) do
|
||||
:erlang.monotonic_time(unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current system time in the `:native` time unit.
|
||||
|
||||
For more information, see the [chapter on time and time
|
||||
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
|
||||
Erlang docs.
|
||||
|
||||
Inlined by the compiler into `:erlang.system_time/0`.
|
||||
"""
|
||||
@spec system_time() :: integer
|
||||
def system_time do
|
||||
:erlang.system_time()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current system time in the given time unit.
|
||||
|
||||
For more information, see the [chapter on time and time
|
||||
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
|
||||
Erlang docs.
|
||||
|
||||
Inlined by the compiler into `:erlang.system_time/1`.
|
||||
"""
|
||||
@spec system_time(:erlang.time_unit) :: integer
|
||||
def system_time(unit) do
|
||||
:erlang.system_time(unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts `time` from time unit `from_unit` to time unit `to_unit`. The result
|
||||
is rounded via the floor function.
|
||||
|
||||
Inlined by the compiler into `:erlang.convert_time_unit/3`.
|
||||
"""
|
||||
@spec convert_time_unit(integer, :erlang.time_unit, :erlang.time_unit) :: integer
|
||||
def convert_time_unit(time, from_unit, to_unit) do
|
||||
:erlang.convert_time_unit(time, from_unit, to_unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current time offset between the Erlang monotonic time and the
|
||||
Erlang system time.
|
||||
|
||||
The result is returned in the `:native` time unit.
|
||||
|
||||
See `time_offset/1` for more information.
|
||||
|
||||
Inlined by the compiler into `:erlang.time_offset/0`.
|
||||
"""
|
||||
@spec time_offset() :: integer
|
||||
def time_offset do
|
||||
:erlang.time_offset()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current time offset between the Erlang monotonic time and the
|
||||
Erlang system time.
|
||||
|
||||
The result is returned in the given time unit `unit`. The returned offset,
|
||||
added to an Erlang monotonic time (e.g., obtained with `monotonic_time/1`),
|
||||
gives the Erlang system time that corresponds to that monotonic time.
|
||||
|
||||
For more information, see the [chapter on time and time
|
||||
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
|
||||
Erlang docs.
|
||||
|
||||
Inlined by the compiler into `:erlang.time_offset/1`.
|
||||
"""
|
||||
@spec time_offset(:erlang.time_unit) :: integer
|
||||
def time_offset(unit) do
|
||||
:erlang.time_offset(unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates and returns an integer that is unique in the current runtime
|
||||
instance.
|
||||
|
||||
"Unique" means that this function, called with the same list of `modifiers`,
|
||||
will never return the same integer more than once on the current runtime
|
||||
instance.
|
||||
|
||||
If `modifiers` is `[]`, then an unique integer (that can be positive or negative) is returned.
|
||||
Other modifiers can be passed to change the properties of the returned integer:
|
||||
|
||||
* `:positive` - the returned integer is guaranteed to be positive.
|
||||
* `:monotonic` - the returned integer is monotonically increasing. This
|
||||
means that, on the same runtime instance (but even on different
|
||||
processes), integers returned using the `:monotonic` modifier will always
|
||||
be strictly less than integers returned by successive calls with the
|
||||
`:monotonic` modifier.
|
||||
|
||||
All modifiers listed above can be combined; repeated modifiers in `modifiers`
|
||||
will be ignored.
|
||||
|
||||
Inlined by the compiler into `:erlang.unique_integer/1`.
|
||||
"""
|
||||
@spec unique_integer([:positive | :monotonic]) :: integer
|
||||
def unique_integer(modifiers \\ []) do
|
||||
:erlang.unique_integer(modifiers)
|
||||
end
|
||||
end
|
||||
|
||||
+252
-92
@@ -1,31 +1,33 @@
|
||||
defmodule Task do
|
||||
@moduledoc """
|
||||
Conveniences for spawning and awaiting for tasks.
|
||||
Conveniences for spawning and awaiting tasks.
|
||||
|
||||
Tasks are processes meant to execute one particular
|
||||
action throughout their life-cycle, often with little or no
|
||||
communication with other processes. The most common use case
|
||||
for tasks is to compute a value asynchronously:
|
||||
for tasks is to convert sequential code into concurrent code
|
||||
by computing a value asynchronously:
|
||||
|
||||
task = Task.async(fn -> do_some_work() end)
|
||||
res = do_some_other_work()
|
||||
res + Task.await(task)
|
||||
|
||||
Tasks spawned with `async` can be awaited on by its caller
|
||||
process (and only its caller) as shown in the example above.
|
||||
Tasks spawned with `async` can be waited on by their caller
|
||||
process (and only their caller) as shown in the example above.
|
||||
They are implemented by spawning a process that sends a message
|
||||
to the caller once the given computation is performed.
|
||||
|
||||
Besides `async/1` and `await/2`, tasks can also be
|
||||
started as part of supervision trees and dynamically spawned
|
||||
started as part of supervision tree and dynamically spawned
|
||||
in remote nodes. We will explore all three scenarios next.
|
||||
|
||||
## async and await
|
||||
|
||||
The most common way to spawn a task is with `Task.async/1`. A new
|
||||
process will be created, linked and monitored by the caller. Once
|
||||
the task action finishes, a message will be sent to the caller
|
||||
with the result.
|
||||
One of the common use of tasks is to convert sequential code
|
||||
into concurrent code with `Task.async/1` while keeping its semantics.
|
||||
When invoked, a new process will be created, linked and monitored
|
||||
by the caller. Once the task action finishes, a message will be sent
|
||||
to the caller with the result.
|
||||
|
||||
`Task.await/2` is used to read the message sent by the task.
|
||||
`await` will check the monitor setup by the call to `async/1` to
|
||||
@@ -34,7 +36,7 @@ defmodule Task do
|
||||
|
||||
There are two important things to consider when using async:
|
||||
|
||||
1. If you are using async tasks, you must await for a reply
|
||||
1. If you are using async tasks, you must await a reply
|
||||
as they are *always* sent. If you are not expecting a reply,
|
||||
consider using `Task.start_link/1` detailed below
|
||||
|
||||
@@ -42,15 +44,15 @@ defmodule Task do
|
||||
means that, if the caller crashes, the task will crash
|
||||
too and vice-versa. This is on purpose, if the process
|
||||
meant to receive the result no longer exists, there is
|
||||
no purpose in computing the result until the end. If this
|
||||
no purpose in completing computation of the result. If this
|
||||
is not desired, consider using `Task.start_link/1` as well
|
||||
|
||||
`Task.yield/2` is an alternative to `await/2` where the caller will
|
||||
temporarily block waiting for a task's result. If the result does not
|
||||
arrive within the timeout it can be called again at later moment. This
|
||||
allows checking for the result of a task multiple times or to handle
|
||||
a timeout. If a reply does not arrive within the desired time, and the
|
||||
caller is not going exit, `Task.shutdown/2` can be used to stop the task.
|
||||
temporarily block, waiting until the task replies or crashes. If the
|
||||
result does not arrive within the timeout it can be called again at a
|
||||
later moment. This allows checking for the result of a task multiple
|
||||
times or to handle a timeout. If a reply does not arrive within the
|
||||
desired time, `Task.shutdown/2` can be used to stop the task.
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
@@ -68,7 +70,7 @@ defmodule Task do
|
||||
]
|
||||
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
the caller, they cannot be waited on. Note `start_link/1`,
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is
|
||||
the result expected by supervision trees).
|
||||
|
||||
@@ -144,8 +146,10 @@ defmodule Task do
|
||||
|
||||
* `:ref` - the task monitor reference
|
||||
|
||||
* `:owner` - the PID of the process that started the task
|
||||
|
||||
"""
|
||||
defstruct pid: nil, ref: nil
|
||||
defstruct pid: nil, ref: nil, owner: nil
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@@ -169,7 +173,7 @@ defmodule Task do
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
(i.e. no interest in the returned result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(fun) :: {:ok, pid}
|
||||
@@ -181,7 +185,7 @@ defmodule Task do
|
||||
Starts a task.
|
||||
|
||||
This is only used when the task is used for side-effects
|
||||
(i.e. no interest in its return result) and it should not
|
||||
(i.e. no interest in the returned result) and it should not
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(module, atom, [term]) :: {:ok, pid}
|
||||
@@ -210,16 +214,59 @@ defmodule Task do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
Starts a task that must be awaited on.
|
||||
|
||||
This function spawns a process that is linked to and monitored
|
||||
by the caller process. A `Task` struct is returned containing
|
||||
the relevant information.
|
||||
A `Task` struct is returned containing the relevant information.
|
||||
Developers must eventually call `Task.await/2` or `Task.yield/2`
|
||||
followed by `Task.shutdown/2` on the returned task.
|
||||
|
||||
Read the `Task` module documentation for more info on general
|
||||
usage of `async/1` and `async/3`.
|
||||
|
||||
## Task's message format
|
||||
## Linking
|
||||
|
||||
This function spawns a process that is linked to and monitored
|
||||
by the caller process. The linking part is important because it
|
||||
aborts the task if the parent process dies. It also guarantees
|
||||
the code before async/await has the same properties after you
|
||||
add the async call. For example, imagine you have this:
|
||||
|
||||
x = heavy_fun()
|
||||
y = some_fun()
|
||||
x + y
|
||||
|
||||
Now you want to make the `heavy_fun()` async:
|
||||
|
||||
x = Task.async(&heavy_fun/0)
|
||||
y = some_fun()
|
||||
Task.await(x) + y
|
||||
|
||||
As before, if `heavy_fun/0` fails, the whole computation will
|
||||
fail, including the parent process. If you don't want the task
|
||||
to fail then you must change the `heavy_fun/0` code in the
|
||||
same way you would if you didn't have the async call. For
|
||||
example to either return `{:ok, val} | :error` results or,
|
||||
in more extreme cases, by using `try/rescue`. In other words,
|
||||
an asynchronous task should be considered an extension of a
|
||||
process rather than a mechanism to isolate it from all errors.
|
||||
|
||||
If you don't want to link the caller to the task, then you
|
||||
must use a supervised task with `Task.Supervisor` and call
|
||||
`Task.Supervisor.async_nolink/2`.
|
||||
|
||||
In any case, avoid any of the following:
|
||||
|
||||
* Setting `:trap_exit` to true - trapping exists should be
|
||||
used only in special circumstances as it would make your
|
||||
process immune to not only exits from the task but from
|
||||
any other processes.
|
||||
|
||||
* Unlinking the task process started with `async`/`await`.
|
||||
If you unlink the processes and the task does not belong
|
||||
to any supervisor, you may leave dangling tasks in case
|
||||
the parent dies.
|
||||
|
||||
## Message format
|
||||
|
||||
The reply sent by the task will be in the format `{ref, msg}`,
|
||||
where `ref` is the monitoring reference held by the task.
|
||||
@@ -227,10 +274,11 @@ defmodule Task do
|
||||
@spec async(module, atom, [term]) :: t
|
||||
def async(mod, fun, args) do
|
||||
mfa = {mod, fun, args}
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
|
||||
owner = self()
|
||||
pid = Task.Supervised.spawn_link(owner, get_info(owner), mfa)
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, {self(), ref})
|
||||
%Task{pid: pid, ref: ref}
|
||||
send(pid, {owner, ref})
|
||||
%Task{pid: pid, ref: ref, owner: owner}
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
@@ -250,18 +298,32 @@ defmodule Task do
|
||||
|
||||
If the timeout is exceeded, `await` will exit, however,
|
||||
the task will continue to run. When the calling process exits, its
|
||||
exit signal will close the task if it is not trapping exits.
|
||||
exit signal will terminate the task if it is not trapping exits.
|
||||
|
||||
This function assumes the task's monitor is still active or the monitor's
|
||||
`:DOWN` message is in the message queue. If it has been demonitored, or the
|
||||
message already received, this function may wait for the duration of the
|
||||
timeout awaiting the message.
|
||||
|
||||
This function will always demonitor the task and so the task can not be used
|
||||
again. To await the task's reply multiple times use `yield/2` instead.
|
||||
This function will always exit and demonitor if the task crashes or if
|
||||
it times out, so the task can not be used again. To explicitly handle
|
||||
the timeout or the crash, use `yield/2` instead.
|
||||
"""
|
||||
@spec await(t, timeout) :: term | no_return
|
||||
def await(%Task{ref: ref}=task, timeout \\ 5000) do
|
||||
def await(task, timeout \\ 5000)
|
||||
|
||||
# TODO: Remove nil check in Elixir 1.3
|
||||
def await(%Task{owner: owner}=task, _) when owner != nil and owner != self() do
|
||||
raise ArgumentError, invalid_owner_error(task)
|
||||
end
|
||||
|
||||
def await(%Task{ref: ref, owner: owner}=task, timeout) do
|
||||
if is_nil(owner) do
|
||||
IO.write :stderr, "warning: a Task was created with the :owner field no set, " <>
|
||||
"please ensure the owner field is correctly set to self()\n" <>
|
||||
Exception.format_stacktrace
|
||||
end
|
||||
|
||||
receive do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
@@ -281,62 +343,32 @@ defmodule Task do
|
||||
|
||||
This function returns a tuple with the returned value
|
||||
in case the message matches a task that exited with
|
||||
success alongside the matching task. It raises in case
|
||||
the found task failed or `nil` if no task was found.
|
||||
success alongside the matching task. It returns `nil`
|
||||
if no task was found. It exits if the task has failed.
|
||||
|
||||
This function is useful in situations where multiple
|
||||
tasks are spawned and their results are collected
|
||||
later on. For example, a `GenServer` can spawn tasks,
|
||||
store the tasks in a list and later use `Task.find/2`
|
||||
to see if incoming messages are from any of the tasks.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule TaskFinder do
|
||||
def run do
|
||||
task1 = Task.async fn -> :timer.sleep(1000); 1 end
|
||||
task2 = Task.async fn -> :timer.sleep(5000); 2 end
|
||||
await [task1, task2]
|
||||
end
|
||||
|
||||
# Be careful, this will receive all messages sent
|
||||
# to this process. It will return the first task
|
||||
# reply and the list of tasks that came second.
|
||||
def await(tasks) do
|
||||
receive do
|
||||
message ->
|
||||
case Task.find(tasks, message) do
|
||||
{reply, task} ->
|
||||
{reply, List.delete(tasks, task)}
|
||||
nil ->
|
||||
await(tasks)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TaskFinder.run
|
||||
"""
|
||||
@spec find([t], any) :: {term, t} | nil | no_return
|
||||
def find(tasks, msg)
|
||||
|
||||
def find(tasks, {ref, reply}) when is_reference(ref) do
|
||||
Enum.find_value tasks, fn
|
||||
%Task{ref: task_ref} = t when ref == task_ref ->
|
||||
%Task{ref: ^ref} = task ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
{reply, t}
|
||||
{reply, task}
|
||||
%Task{} ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def find(tasks, {:DOWN, ref, _, proc, reason} = msg) when is_reference(ref) do
|
||||
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
|
||||
case Enum.find(tasks, find) do
|
||||
%Task{} ->
|
||||
exit({reason(reason, proc), {__MODULE__, :find, [tasks, msg]}})
|
||||
nil ->
|
||||
nil
|
||||
find = fn %Task{ref: task_ref} -> task_ref == ref end
|
||||
if Enum.find(tasks, find) do
|
||||
exit({reason(reason, proc), {__MODULE__, :find, [tasks, msg]}})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -345,9 +377,10 @@ defmodule Task do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Yields, temporarily, for a task reply.
|
||||
Yields for a task reply in the given time interval.
|
||||
|
||||
Returns `{:ok, reply}` if the reply is received.
|
||||
Returns `{:ok, reply}` if the reply is received, `{:exit, reason}`
|
||||
if the task exited or `nil` if no reply arrived.
|
||||
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case of the timeout, this function will return `nil`
|
||||
@@ -357,37 +390,144 @@ defmodule Task do
|
||||
In case the task process dies, this function will exit with the
|
||||
same reason as the task.
|
||||
|
||||
This function assumes the task's monitor is still active or the monitor's
|
||||
`:DOWN` message is in the message queue. If it has been demonitored, or the
|
||||
message already received, this function wait for the duration of the timeout
|
||||
awaiting the message.
|
||||
This function assumes the task's monitor is still active or the
|
||||
monitor's `:DOWN` message is in the message queue. If it has been
|
||||
demonitored, or the message already received, this function waits
|
||||
for the duration of the timeout awaiting the message.
|
||||
"""
|
||||
@spec yield(t, timeout) :: {:ok, term} | nil
|
||||
def yield(%Task{ref: ref} = task, timeout \\ 5_000) do
|
||||
@spec yield(t, timeout) :: {:ok, term} | {:exit, term} | nil
|
||||
def yield(task, timeout \\ 5_000)
|
||||
|
||||
# TODO: Remove nil check in Elixir 1.3
|
||||
def yield(%Task{owner: owner} = task, _) when owner != nil and owner != self() do
|
||||
raise ArgumentError, invalid_owner_error(task)
|
||||
end
|
||||
|
||||
def yield(%Task{ref: ref, owner: owner} = task, timeout) do
|
||||
if is_nil(owner) do
|
||||
IO.write :stderr, "warning: a Task was created with the :owner field no set, " <>
|
||||
"please ensure the owner field is correctly set to self()\n" <>
|
||||
Exception.format_stacktrace
|
||||
end
|
||||
|
||||
receive do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
{:ok, reply}
|
||||
{:DOWN, ^ref, _, proc, reason} ->
|
||||
exit({reason(reason, proc), {__MODULE__, :yield, [task, timeout]}})
|
||||
{:DOWN, ^ref, _, proc, :noconnection} ->
|
||||
exit({reason(:noconnection, proc), {__MODULE__, :yield, [task, timeout]}})
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
{:exit, reason}
|
||||
after
|
||||
timeout ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Yields to multiple tasks in the given time interval.
|
||||
|
||||
This function receives a list of tasks and await for their
|
||||
replies at once in the given time interval. It returns a list
|
||||
of tuples of two elements, with tasks as the first element and
|
||||
the `yield` result as the second.
|
||||
|
||||
Similar to `yield/2`, if the task replied in the given interval,
|
||||
it will return `{:ok, term}`, `{:exit, reason}`if it crashed or
|
||||
`nil` if it timed out. Check `yield/2` for more information.
|
||||
|
||||
## Example
|
||||
|
||||
`Task.yield_many/2` allows developers to spawn multiple tasks
|
||||
and retrieve the results received in a given timeframe.
|
||||
If we combine it with `Task.shutdown/2`, it allows us to gather
|
||||
those results and cancel the tasks that have not replied in time.
|
||||
Let's see an example.
|
||||
|
||||
tasks =
|
||||
for i <- 1..10 do
|
||||
Task.async(fn ->
|
||||
:timer.sleep(i * 1000)
|
||||
i
|
||||
end)
|
||||
end
|
||||
|
||||
tasks_with_results = Task.yield_many(tasks, 5000)
|
||||
|
||||
results = Enum.map(tasks_with_results, fn {task, res} ->
|
||||
# Shutdown the tasks that did not reply nor exit
|
||||
res || Task.shutdown(task, :brutal_kill)
|
||||
end)
|
||||
|
||||
# Here we are matching only on {:ok, value} and
|
||||
# ignoring {:exit, _} (crashed tasks) and `nil` (no replies)
|
||||
for {:ok, value} <- results do
|
||||
IO.inspect value
|
||||
end
|
||||
|
||||
In the example above, we create tasks that sleep from 1
|
||||
up to 10 seconds and return the amount of seconds they slept.
|
||||
If you execute the code all at once, you should see 1 up to 5
|
||||
printed, as those were the tasks that have replied in the
|
||||
given time. All other tasks will have been shutdown, according
|
||||
to the `Task.shutdown/2` call.
|
||||
"""
|
||||
@spec yield_many([t], timeout) :: [{t, {:ok, term} | {:exit, term} | nil}]
|
||||
def yield_many(tasks, timeout \\ 5000) do
|
||||
timeout_ref = make_ref()
|
||||
timer_ref = Process.send_after(self(), timeout_ref, timeout)
|
||||
try do
|
||||
yield_many(tasks, timeout_ref, :infinity)
|
||||
catch
|
||||
{:noconnection, reason} ->
|
||||
exit({reason, {__MODULE__, :yield_many, [tasks, timeout]}})
|
||||
after
|
||||
Process.cancel_timer(timer_ref)
|
||||
receive do: (^timeout_ref -> :ok), after: (0 -> :ok)
|
||||
end
|
||||
end
|
||||
|
||||
defp yield_many([%Task{ref: ref, owner: owner}=task|rest], timeout_ref, timeout) do
|
||||
if owner != self() do
|
||||
raise ArgumentError, invalid_owner_error(task)
|
||||
end
|
||||
|
||||
receive do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
[{task, {:ok, reply}}|yield_many(rest, timeout_ref, timeout)]
|
||||
|
||||
{:DOWN, ^ref, _, proc, :noconnection} ->
|
||||
throw({:noconnection, reason(:noconnection, proc)})
|
||||
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
[{task, {:exit, reason}}|yield_many(rest, timeout_ref, timeout)]
|
||||
|
||||
^timeout_ref ->
|
||||
[{task, nil}|yield_many(rest, timeout_ref, 0)]
|
||||
|
||||
after
|
||||
timeout ->
|
||||
[{task, nil}|yield_many(rest, timeout_ref, 0)]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
defp yield_many([], _timeout_ref, _timeout) do
|
||||
[]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unlinks and shutdowns the task, and then checks for a reply.
|
||||
|
||||
Returns `{:ok, reply}` if the reply is received while shutting down the task,
|
||||
otherwise `nil`.
|
||||
`{:exit, reason}` if the task exited abornormally, otherwise `nil`.
|
||||
|
||||
The shutdown method is either a timeout or `:brutal_kill`. In the case
|
||||
The shutdown method is either a timeout or `:brutal_kill`. In case
|
||||
of a `timeout`, a `:shutdown` exit signal is sent to the task process
|
||||
and if it does not exit within the timeout it is killed. With `:brutal_kill`
|
||||
the task is killed straight away. In the case that the task exits abnormal,
|
||||
or a timeout shutdown kills the task, this function will exit with the same
|
||||
reason.
|
||||
the task is killed straight away. In case the task exits abnormally, or a
|
||||
timeout shutdown kills the task, this function will exit with the same reason.
|
||||
|
||||
It is not required to call this function when terminating the caller, unless
|
||||
exiting with reason `:normal` or the task is trapping exits. If the caller is
|
||||
@@ -400,18 +540,32 @@ defmodule Task do
|
||||
`:DOWN` message is in the message queue. If it has been demonitored, or the
|
||||
message already received, this function will block forever awaiting the message.
|
||||
"""
|
||||
@spec shutdown(t, timeout | :brutal_kill) :: {:ok, term} | nil
|
||||
@spec shutdown(t, timeout | :brutal_kill) :: {:ok, term} | {:exit, term} | nil
|
||||
def shutdown(task, shutdown \\ 5_000)
|
||||
|
||||
def shutdown(%Task{pid: nil} = task, _) do
|
||||
raise ArgumentError, "task #{inspect task} does not have an associated task process."
|
||||
raise ArgumentError, "task #{inspect task} does not have an associated task process"
|
||||
end
|
||||
|
||||
def shutdown(%Task{pid: pid} = task, :brutal_kill) do
|
||||
# TODO: Remove nil check in Elixir 1.3
|
||||
def shutdown(%Task{owner: owner} = task, _) when owner != nil and owner != self() do
|
||||
raise ArgumentError, invalid_owner_error(task)
|
||||
end
|
||||
|
||||
def shutdown(%Task{pid: pid, owner: owner} = task, :brutal_kill) do
|
||||
if is_nil(owner) do
|
||||
IO.write :stderr, "warning: a Task was created with the :owner field no set, " <>
|
||||
"please ensure the owner field is correctly set to self()\n" <>
|
||||
Exception.format_stacktrace
|
||||
end
|
||||
|
||||
exit(pid, :kill)
|
||||
|
||||
case shutdown_receive(task, :brutal_kill, :infinity) do
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :shutdown, [task, :brutal_kill]}})
|
||||
{:down, proc, :noconnection} ->
|
||||
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, :brutal_kill]}})
|
||||
{:down, _, reason} ->
|
||||
{:exit, reason}
|
||||
result ->
|
||||
result
|
||||
end
|
||||
@@ -420,8 +574,10 @@ defmodule Task do
|
||||
def shutdown(%Task{pid: pid} = task, timeout) do
|
||||
exit(pid, :shutdown)
|
||||
case shutdown_receive(task, :shutdown, timeout) do
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :shutdown, [task, timeout]}})
|
||||
{:down, proc, :noconnection} ->
|
||||
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, timeout]}})
|
||||
{:down, _, reason} ->
|
||||
{:exit, reason}
|
||||
result ->
|
||||
result
|
||||
end
|
||||
@@ -462,7 +618,7 @@ defmodule Task do
|
||||
{:DOWN, ^ref, _, _, :killed} when type == :brutal_kill ->
|
||||
flush_reply(ref)
|
||||
{:DOWN, ^ref, _, proc, reason} ->
|
||||
flush_reply(ref) || {:error, reason(reason, proc)}
|
||||
flush_reply(ref) || {:down, proc, reason}
|
||||
after
|
||||
timeout ->
|
||||
Process.exit(task.pid, :kill)
|
||||
@@ -477,4 +633,8 @@ defmodule Task do
|
||||
0 -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp invalid_owner_error(task) do
|
||||
"task #{inspect task} must be queried from the owner but was queried from #{inspect self()}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
@ref_timeout 5_000
|
||||
|
||||
def start(info, fun) do
|
||||
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
@@ -9,24 +11,38 @@ defmodule Task.Supervised do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(caller, info, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
|
||||
def start_link(caller, link, info, fun) do
|
||||
{:ok, spawn_link(caller, link, info, fun)}
|
||||
end
|
||||
|
||||
def async(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
ref = receive do: ({^caller, ref} -> ref)
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
def spawn_link(caller, link \\ :nolink, info, fun) do
|
||||
:proc_lib.spawn_link(__MODULE__, :reply, [caller, link, info, fun])
|
||||
end
|
||||
|
||||
def reply(caller, info, mfa) do
|
||||
def reply(caller, link, info, mfa) do
|
||||
initial_call(mfa)
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
case link do
|
||||
:link ->
|
||||
Process.link(caller)
|
||||
reply(caller, nil, @ref_timeout, info, mfa)
|
||||
:monitor ->
|
||||
mref = Process.monitor(caller)
|
||||
reply(caller, mref, @ref_timeout, info, mfa)
|
||||
:nolink ->
|
||||
reply(caller, nil, :infinity, info, mfa)
|
||||
end
|
||||
end
|
||||
|
||||
ref =
|
||||
defp reply(caller, mref, timeout, info, mfa) do
|
||||
receive do
|
||||
{^caller, ref} ->
|
||||
_ = if mref, do: Process.demonitor(mref, [:flush])
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
{:DOWN, ^mref, _, _, reason} when is_reference(mref) ->
|
||||
exit(reason)
|
||||
after
|
||||
# There is a race condition on this operation when working across
|
||||
# node that manifests if a "Task.Supervisor.async/1" call is made
|
||||
# node that manifests if a "Task.Supervisor.async/2" call is made
|
||||
# while the supervisor is busy spawning previous tasks.
|
||||
#
|
||||
# Imagine the following workflow:
|
||||
@@ -41,13 +57,12 @@ defmodule Task.Supervised do
|
||||
# Given no work is done in the client between the task start and
|
||||
# sending the reference, 5000 should be enough to not raise false
|
||||
# negatives unless the nodes are indeed not available.
|
||||
receive do
|
||||
{^caller, ref} -> ref
|
||||
after
|
||||
5000 -> exit(:timeout)
|
||||
end
|
||||
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
#
|
||||
# The same situation could occur with "Task.Supervisor.async_nolink/2",
|
||||
# except a monitor is used instead of a link.
|
||||
timeout ->
|
||||
exit(:timeout)
|
||||
end
|
||||
end
|
||||
|
||||
def noreply(info, mfa) do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Task.Supervisor do
|
||||
@moduledoc """
|
||||
A tasks supervisor.
|
||||
A task supervisor.
|
||||
|
||||
This module defines a supervisor which can be used to dynamically
|
||||
supervise tasks. Behind the scenes, this module is implemented as a
|
||||
@@ -12,7 +12,7 @@ defmodule Task.Supervisor do
|
||||
## Name Registration
|
||||
|
||||
A `Task.Supervisor` is bound to the same name registration rules as a
|
||||
`GenServer`. Read more about it in the `GenServer` docs.
|
||||
`GenServer`. Read more about them in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -26,7 +26,7 @@ defmodule Task.Supervisor do
|
||||
|
||||
* `:restart` - the restart strategy, may be `:temporary` (the default),
|
||||
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
|
||||
Defaults to temporary as most tasks can't be effectively restarted after
|
||||
Defaults to `:temporary` as most tasks can't be effectively restarted after
|
||||
a crash;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
@@ -48,7 +48,8 @@ defmodule Task.Supervisor do
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
For more information on tasks, check the `Task` module.
|
||||
The task will still be linked to the caller, see `Task.async/3` for
|
||||
more information and `async_nolink/2` for a non-linked variant.
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, fun) :: Task.t
|
||||
def async(supervisor, fun) do
|
||||
@@ -59,15 +60,47 @@ defmodule Task.Supervisor do
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
For more information on tasks, check the `Task` module.
|
||||
The task will still be linked to the caller, see `Task.async/3` for
|
||||
more information and `async_nolink/2` for a non-linked variant.
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async(supervisor, module, fun, args) do
|
||||
args = [self, get_info(self), {module, fun, args}]
|
||||
owner = self()
|
||||
args = [owner, :link, get_info(owner), {module, fun, args}]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
Process.link(pid)
|
||||
ref = Process.monitor(pid)
|
||||
send pid, {owner, ref}
|
||||
%Task{pid: pid, ref: ref, owner: owner}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
The task won't be linked to the caller, see `Task.async/3` for
|
||||
more information.
|
||||
"""
|
||||
@spec async_nolink(Supervisor.supervisor, fun) :: Task.t
|
||||
def async_nolink(supervisor, fun) do
|
||||
async_nolink(supervisor, :erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
The task won't be linked to the caller, see `Task.async/3` for
|
||||
more information.
|
||||
"""
|
||||
@spec async_nolink(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async_nolink(supervisor, module, fun, args) do
|
||||
owner = self()
|
||||
args = [owner, :monitor, get_info(owner), {module, fun, args}]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
ref = Process.monitor(pid)
|
||||
send pid, {self(), ref}
|
||||
%Task{pid: pid, ref: ref}
|
||||
send pid, {owner, ref}
|
||||
%Task{pid: pid, ref: ref, owner: owner}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -75,7 +108,7 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
:supervisor.terminate_child(supervisor, pid)
|
||||
Supervisor.terminate_child(supervisor, pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -83,7 +116,7 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec children(Supervisor.supervisor) :: [pid]
|
||||
def children(supervisor) do
|
||||
:supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
|
||||
Supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+11
-9
@@ -2,14 +2,14 @@ defmodule Tuple do
|
||||
@moduledoc """
|
||||
Functions for working with tuples.
|
||||
|
||||
See also `Kernel.elem/2`, `Kernel.is_tuple/1`, `Kernel.put_elem/3`, and
|
||||
`Kernel.tuple_size/1`.
|
||||
See also `Kernel.elem/2`, `Kernel.is_tuple/1`,
|
||||
`Kernel.put_elem/3`, and `Kernel.tuple_size/1`.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Creates a new tuple.
|
||||
|
||||
Creates a tuple of size `size` containing the
|
||||
Creates a tuple of `size` containing the
|
||||
given `data` at every position.
|
||||
|
||||
Inlined by the compiler.
|
||||
@@ -28,9 +28,9 @@ defmodule Tuple do
|
||||
@doc """
|
||||
Inserts an element into a tuple.
|
||||
|
||||
Inserts `value` into `tuple` at the given zero-based `index`.
|
||||
Inserts `value` into `tuple` at the given `index`.
|
||||
Raises an `ArgumentError` if `index` is negative or greater than the
|
||||
length of `tuple`.
|
||||
length of `tuple`. Index is zero-based.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -49,9 +49,9 @@ defmodule Tuple do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts an element into the end of a tuple.
|
||||
Inserts an element at the end of a tuple.
|
||||
|
||||
Returns a new tuple which has one element more than `tuple`, and contains
|
||||
Returns a new tuple with the element appended at the end, and contains
|
||||
the elements in `tuple` followed by `value` as the last element.
|
||||
|
||||
Inlined by the compiler.
|
||||
@@ -70,9 +70,9 @@ defmodule Tuple do
|
||||
@doc """
|
||||
Removes an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Deletes the element at the given `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is negative or greater than
|
||||
or equal to the length of `tuple`.
|
||||
or equal to the length of `tuple`. Index is zero-based.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -91,6 +91,8 @@ defmodule Tuple do
|
||||
@doc """
|
||||
Converts a tuple to a list.
|
||||
|
||||
Returns a new list with all the tuple elements.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
+15
-13
@@ -75,6 +75,7 @@ defmodule URI do
|
||||
%{"bar" => "2", "foo" => "1"}
|
||||
|
||||
"""
|
||||
# TODO: Deprecate giving not a map on 1.3
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
case do_decode_query(q) do
|
||||
nil -> dict
|
||||
@@ -177,7 +178,7 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a string as "x-www-urlencoded".
|
||||
Encodes a string as "x-www-form-urlencoded".
|
||||
|
||||
## Example
|
||||
|
||||
@@ -222,7 +223,7 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a string as "x-www-urlencoded".
|
||||
Decodes a string as "x-www-form-urlencoded".
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -339,7 +340,7 @@ defmodule URI do
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
for s <- l do
|
||||
if byte_size(s) > 0, do: s, else: nil
|
||||
if byte_size(s) > 0, do: s
|
||||
end
|
||||
end
|
||||
|
||||
@@ -361,23 +362,24 @@ defimpl String.Chars, for: URI do
|
||||
end
|
||||
|
||||
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
|
||||
|
||||
if uri.host do
|
||||
authority = uri.host
|
||||
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
|
||||
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
|
||||
else
|
||||
authority = uri.authority
|
||||
end
|
||||
authority = extract_authority(uri)
|
||||
|
||||
result = ""
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> ":"
|
||||
if authority, do: result = result <> "//" <> authority
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
defp extract_authority(%{host: nil, authority: authority}) do
|
||||
authority
|
||||
end
|
||||
defp extract_authority(%{host: host, userinfo: userinfo, port: port}) do
|
||||
authority = host
|
||||
if userinfo, do: authority = userinfo <> "@" <> authority
|
||||
if port, do: authority = authority <> ":" <> Integer.to_string(port)
|
||||
authority
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule Version do
|
||||
|
||||
## Versions
|
||||
|
||||
In a nutshell, a version is given by three numbers:
|
||||
In a nutshell, a version is represented by three numbers:
|
||||
|
||||
MAJOR.MINOR.PATCH
|
||||
|
||||
@@ -24,8 +24,8 @@ defmodule Version do
|
||||
|
||||
## Struct
|
||||
|
||||
The version is represented by the Version struct and it has its
|
||||
fields named according to Semver: `:major`, `:minor`, `:patch`,
|
||||
The version is represented by the Version struct and fields
|
||||
are named according to Semver: `:major`, `:minor`, `:patch`,
|
||||
`:pre` and `:build`.
|
||||
|
||||
## Requirements
|
||||
@@ -105,7 +105,7 @@ defmodule Version do
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirementError` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
|
||||
parsable, or `Version.InvalidVersionError` if `version` is not parsable.
|
||||
If given an already parsed version and requirement this function won't
|
||||
raise.
|
||||
|
||||
@@ -144,7 +144,7 @@ defmodule Version do
|
||||
the second and `:lt` for vice versa. If the two versions are equal `:eq`
|
||||
is returned
|
||||
|
||||
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
|
||||
Raises a `Version.InvalidVersionError` exception if `version` is not parsable.
|
||||
If given an already parsed version this function won't raise.
|
||||
|
||||
## Examples
|
||||
@@ -266,8 +266,8 @@ defmodule Version do
|
||||
|
||||
defmacro deflexer(char, acc, do: body) do
|
||||
quote do
|
||||
def lexer(<< unquote(char) :: utf8, rest :: binary >>, unquote(acc)) do
|
||||
unquote(char) = << unquote(char) :: utf8 >>
|
||||
def lexer(<<unquote(char)::utf8, rest::binary>>, unquote(acc)) do
|
||||
unquote(char) = <<unquote(char)::utf8>>
|
||||
|
||||
lexer(rest, unquote(body))
|
||||
end
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# Typespecs
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
dynamically typed, and as such, typespecs are never used by the compiler to
|
||||
optimize or modify code. Still, using typespecs is useful as documentation and
|
||||
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
|
||||
analyze code with typespecs to find bugs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec`, `@callback` and
|
||||
`@macrocallback` available in modules are handled by the equivalent macros
|
||||
defined by this module. See sub-sections "Defining a type" and "Defining a
|
||||
specification" below.
|
||||
|
||||
## Types and their syntax
|
||||
|
||||
The type syntax provided by Elixir is fairly similar to [the one in
|
||||
Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`) are
|
||||
expressed the same way: `pid()` or simply `pid`. Parameterized types are also
|
||||
supported (`list(integer)`) and so are remote types (`Enum.t`).
|
||||
|
||||
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
|
||||
`false`). All other types are built of unions of predefined types. Certain
|
||||
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
|
||||
|
||||
### Basic types
|
||||
|
||||
type :: any() # the top type, the set of all terms
|
||||
| none() # the bottom type, contains no terms
|
||||
| pid()
|
||||
| port()
|
||||
| reference()
|
||||
| tuple()
|
||||
| atom()
|
||||
| integer()
|
||||
| non_neg_integer() # 0, 1, 2, 3, ...
|
||||
| pos_integer() # 1, 2, 3, ...
|
||||
| neg_integer() # ..., -3, -2, -1
|
||||
| float()
|
||||
| map()
|
||||
| struct()
|
||||
| list(type)
|
||||
| nonempty_list(type)
|
||||
| improper_list(type1, type2)
|
||||
| maybe_improper_list(type1, type2)
|
||||
| Literals # Described in section "Literals"
|
||||
| Builtin # Described in section "Builtin-types"
|
||||
| Remotes # Described in section "Remotes"
|
||||
|
||||
### Literals
|
||||
|
||||
The following literals are also supported in typespecs:
|
||||
|
||||
type :: :atom ## Atoms
|
||||
| 1 ## Integers
|
||||
| 1..10 ## Integers from 1 to 10
|
||||
| 1.0 ## Floats
|
||||
|
||||
| <<>> ## Bitstrings
|
||||
| <<_::size>> # size is 0 or a positive integer
|
||||
| <<_::_ * unit>> # unit is an integer from 1 to 256
|
||||
| <<_::size * unit>>
|
||||
|
||||
| [type] ## Lists
|
||||
| [] # empty list
|
||||
| [...] # shorthand for nonempty_list(any())
|
||||
| [type, ...] # shorthand for nonempty_list(type)
|
||||
| [key: type] # keyword lists
|
||||
|
||||
| (... -> type) ## Functions
|
||||
| (... -> type) # any arity, returns type
|
||||
| (() -> type) # 0-arity, returns type
|
||||
| (type1, type2 -> type) # 2-arity, returns type
|
||||
|
||||
| %{} ## Maps
|
||||
| %{key: type} # map with key :key with value of type
|
||||
| %{type1 => type2} # map with keys of type1 with values of type2
|
||||
| %SomeStruct{}
|
||||
| %SomeStruct{key: type}
|
||||
|
||||
| {} ## Tuples
|
||||
| {:ok, type} # two element tuple with an atom and any type
|
||||
|
||||
### Built-in types
|
||||
|
||||
These types are also provided by Elixir as shortcuts on top of the
|
||||
basic and literal types.
|
||||
|
||||
Built-in type | Defined as
|
||||
:---------------------- | :---------
|
||||
`term()` | `any()`
|
||||
`binary()` | `<<_::_ * 8>>`
|
||||
`bitstring()` | `<<_::_ * 1>>`
|
||||
`boolean()` | `false` \| `true`
|
||||
`byte()` | `0..255`
|
||||
`char()` | `0..0x10ffff`
|
||||
`number()` | `integer()` \| `float()`
|
||||
`char_list()` | `[char()]`
|
||||
`list()` | `[any()]`
|
||||
`maybe_improper_list()` | `maybe_improper_list(any(), any())`
|
||||
`nonempty_list()` | `nonempty_list(any())`
|
||||
`iodata()` | `iolist()` \| `binary()`
|
||||
`iolist()` | `maybe_improper_list(byte()` \| `binary()` \| `iolist(), binary()` \| `[])`
|
||||
`module()` | `atom()` \| `tuple()`
|
||||
`arity()` | `0..255`
|
||||
`mfa()` | `{atom(), atom(), arity()}`
|
||||
`node()` | `atom()`
|
||||
`timeout()` | `:infinity` \| `non_neg_integer()`
|
||||
`no_return()` | `none()`
|
||||
`fun()` | `(... -> any)`
|
||||
`struct()` | `%{__struct__: atom()}`
|
||||
`as_boolean(t)` | `t`
|
||||
|
||||
### Remote types
|
||||
|
||||
Any module is also able to define its own type and the modules in
|
||||
Elixir are no exception. For example, a string is `String.t`, a
|
||||
range is `Range.t`, any enumerable can be `Enum.t` and so on.
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
A type defined with `@typep` is private. An opaque type, defined with
|
||||
`@opaque` is a type where the internal structure of the type will not be
|
||||
visible, but the type is still public.
|
||||
|
||||
Types can be parameterized by defining variables as parameters, these variables
|
||||
can then be used to define the type.
|
||||
|
||||
@type dict(key, value) :: [{key, value}]
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@callback function_name(type1, type2) :: return_type
|
||||
@macrocallback macro_name(type1, type2) :: Macro.t
|
||||
|
||||
Callbacks are used to define the callbacks functions of behaviours (see
|
||||
`Behaviour`).
|
||||
|
||||
Guards can be used to restrict type variables given as arguments to the
|
||||
function.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: atom
|
||||
|
||||
Type variables with no restriction can also be defined.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: var
|
||||
|
||||
Specifications can be overloaded just like ordinary functions.
|
||||
|
||||
@spec function(integer) :: atom
|
||||
@spec function(atom) :: integer
|
||||
|
||||
## Notes
|
||||
|
||||
Elixir discourages the use of type `string` as it might be confused with
|
||||
binaries which are referred to as "strings" in Elixir (as opposed to character
|
||||
lists). In order to use the type that is called `string` in Erlang, one has to
|
||||
use the `char_list` type which is a synonym for `string`. If you use `string`,
|
||||
you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated on by functions in
|
||||
the `String` module), use `String.t` type instead.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Writing Documentation
|
||||
|
||||
Elixir treats documentation as a first-class citizen. This means documentation should be easy to write and easy to read. In this document you will learn how to write documentation in Elixir, covering constructs like module attributes, style practices and doctests.
|
||||
|
||||
## Markdown
|
||||
|
||||
Elixir documentation is written using Markdown. There are plenty of guides on Markdown online, we recommend the ones available at GitHub as a getting started point:
|
||||
|
||||
* https://help.github.com/articles/markdown-basics/
|
||||
* https://help.github.com/articles/github-flavored-markdown/
|
||||
|
||||
## Module Attributes
|
||||
|
||||
Documentation in Elixir is usually attached to module attributes. Let's see an example:
|
||||
|
||||
defmodule MyApp.Hello do
|
||||
@moduledoc """
|
||||
This is the Hello module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Says hello to the given `name`.
|
||||
|
||||
Returns `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MyApp.Hello.world(:john)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def world(name) do
|
||||
IO.puts "hello #{name}"
|
||||
end
|
||||
end
|
||||
|
||||
The `@moduledoc` attribute is used to attach documentation to the module. `@doc` is used before a function to attach documentation to it. Besides the attributes above, `@typedoc` can also be used to attach documentation to types defined as part of typespecs.
|
||||
|
||||
## Function Arguments
|
||||
|
||||
When documenting a function, argument names are inferred by the compiler. For example:
|
||||
|
||||
def size(%HashDict{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
The compiler will infer this argument as `hash_dict`. Sometimes the inference will be suboptimal, specially if the function contains multiple clauses with the argument matching on different values each time. You can specify the proper names for documentation by using a bodyless clause:
|
||||
|
||||
def size(dict)
|
||||
|
||||
## Recommendations
|
||||
|
||||
There are a couple tips we recommend developers to follow when writing documentation:
|
||||
|
||||
* Keep the first paragraph of the documentation concise and simple. Tools like ExDoc uses the first line to generate a summary.
|
||||
|
||||
* Markdown uses backticks (`` ` ``) to quote code. Elixir builds on top of that to automatically generate links when modules or function names are referenced. For this reason, always use full module names. If you have a module called `MyApp.Hello`, always reference it as `` `MyApp.Hello` `` and never as `` `Hello` ``. Function names must be referenced by name and arity if they are local, as in `` `world/1` ``, or by module, name and arity if pointing to an external module: `` `MyApp.Hello.world/1` ``. Referencing a `@callback` can be done by prepending `c:`, as in `` `c:world/1` ``.
|
||||
|
||||
* If using headings, always start from the second heading by using `##`. The first heading is reserved to the module or function name itself.
|
||||
|
||||
## Doctests
|
||||
|
||||
We recommend developers to include examples in their documentation, often under its own `## Examples` heading. To ensure examples do not get out of date, Elixir's test framework (ExUnit) provides a feature called doctests that allows developers to test the examples in their documentation. Doctests work by parsing out code samples starting with `iex>` from the documentation. You can read more about it at `ExUnit.DocTest`.
|
||||
|
||||
Notice doctests have limitations. When you cannot doctest a function, because it relies on state or side-effects, we recommend developers to include examples directly without the `iex>` prompt.
|
||||
|
||||
## Privacy
|
||||
|
||||
Elixir allows developers to hide both modules and functions from the documentation by setting the doc attribute to false:
|
||||
|
||||
defmodule MyApp.Hidden do
|
||||
@moduledoc false
|
||||
|
||||
def this_will_be_ignored_by_tools do
|
||||
# ...
|
||||
end
|
||||
end
|
||||
|
||||
Notice that, although developers can add `@doc false` to functions, it does not make the function private:
|
||||
|
||||
defmodule MyApp.Sample do
|
||||
@doc false
|
||||
def add(a, b), do: a + b
|
||||
end
|
||||
|
||||
The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if the `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be wary when adding `@doc false` to functions, instead prefer one of:
|
||||
|
||||
* Move the private function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. In fact, you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
|
||||
|
||||
* Start the function name with underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with underscore and the underscore will tell users to be wary of using it.
|
||||
|
||||
## Documentation != Comments
|
||||
|
||||
Elixir makes the difference between documentation and code comments. Documentation are for users of your API, be it your co-worker or your future self. Modules and functions must always be documented if they are part of your application public interface (API).
|
||||
|
||||
Code comments are for developers reading the code. They are useful to mark improvements, leave notes for developers reading the code (for example, you decided to not call a function due to a bug in a library) and so forth.
|
||||
|
||||
In other words, documentation is required, code comments are optional.
|
||||
|
||||
## Code.get_docs/2
|
||||
|
||||
Elixir stores documentation inside pre-defined chunks in the bytecode. It can be accessed from Elixir by using the `Code.get_docs/2` function. This also means documentation is only accessed when required and not when modules are loaded by the Virtual Machine. The only downside is that modules defined in-memory, like the ones defined via IEx, cannot have their documentation accessed.
|
||||
+33
-11
@@ -7,6 +7,7 @@
|
||||
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
|
||||
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
|
||||
-include("elixir.hrl").
|
||||
-define(system, 'Elixir.System').
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, struct/0, as_boolean/1]).
|
||||
@@ -29,14 +30,32 @@ start(_Type, _Args) ->
|
||||
error -> [binary]
|
||||
end,
|
||||
|
||||
ok = io:setopts(standard_io, Opts),
|
||||
|
||||
%% TODO: Remove this once we support only OTP >18
|
||||
ok = case io:setopts(standard_error, [{encoding, utf8}]) of
|
||||
ok -> ok;
|
||||
{error, _} -> io:setopts(standard_error, [{unicode, true}]) %% OTP 17.3 and earlier
|
||||
case string:to_integer(erlang:system_info(otp_release)) of
|
||||
{Num, _} when Num >= 18 ->
|
||||
ok;
|
||||
_ ->
|
||||
io:format(standard_error, "unsupported Erlang version, expected Erlang 18+~n", []),
|
||||
erlang:halt(1)
|
||||
end,
|
||||
|
||||
case code:ensure_loaded(?system) of
|
||||
{module, ?system} ->
|
||||
Endianness = ?system:endianness(),
|
||||
case ?system:compiled_endianness() of
|
||||
Endianness -> ok;
|
||||
_ ->
|
||||
io:format(standard_error,
|
||||
"warning: Elixir is running in a system with a different endianness than the one its "
|
||||
"source code was compiled in. Please make sure Elixir and all source files were compiled "
|
||||
"in a machine with the same endianness as the current one: ~ts~n", [Endianness])
|
||||
end;
|
||||
{error, _} ->
|
||||
ok
|
||||
end,
|
||||
|
||||
ok = io:setopts(standard_io, Opts),
|
||||
ok = io:setopts(standard_error, [{encoding, utf8}]),
|
||||
|
||||
Encoding = file:native_name_encoding(),
|
||||
case Encoding of
|
||||
latin1 ->
|
||||
@@ -55,11 +74,12 @@ start(_Type, _Args) ->
|
||||
{<<"https">>, 443},
|
||||
{<<"ldap">>, 389}],
|
||||
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
|
||||
CompilerOpts = [{docs, true}, {debug_info, true}, {warnings_as_errors, false}],
|
||||
CompilerOpts = #{docs => true, ignore_module_conflict => false,
|
||||
debug_info => true, warnings_as_errors => false},
|
||||
{ok, [[Home] | _]} = init:get_argument(home),
|
||||
Config = [{at_exit, []},
|
||||
{home, unicode:characters_to_binary(Home, Encoding, Encoding)},
|
||||
{compiler_options, orddict:from_list(CompilerOpts)}
|
||||
{compiler_options, CompilerOpts}
|
||||
| URIConfig],
|
||||
Tab = elixir_config:new(Config),
|
||||
case elixir_sup:start_link() of
|
||||
@@ -73,7 +93,6 @@ start(_Type, _Args) ->
|
||||
stop(Tab) ->
|
||||
elixir_config:delete(Tab).
|
||||
|
||||
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
@@ -173,8 +192,8 @@ eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E)).
|
||||
eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{ParsedBinding, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#{vars := [K || {K, _} <- ParsedScope#elixir_scope.vars]},
|
||||
{ParsedBinding, ParsedVars, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#{vars := ParsedVars},
|
||||
{Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
|
||||
case Erl of
|
||||
@@ -233,6 +252,7 @@ quoted_to_erl(Quoted, Env, Scope) ->
|
||||
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{file, File}|Opts]) of
|
||||
{ok, _Line, _Column, Tokens} ->
|
||||
put(elixir_parser_file, File),
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ok, Forms} -> {ok, Forms};
|
||||
{error, {{Line, _, _}, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}};
|
||||
@@ -240,6 +260,8 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
|
||||
catch
|
||||
{error, {{Line, _, _}, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}};
|
||||
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
after
|
||||
erase(elixir_parser_file)
|
||||
end;
|
||||
{error, {Line, {ErrorPrefix, ErrorSuffix}, Token}, _Rest, _SoFar} ->
|
||||
{error, {Line, {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}};
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
-define(m(M, K), maps:get(K, M)).
|
||||
-define(ann(Opts), elixir_utils:get_ann(Opts)).
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
-define(generated, [{generated, true}, {location, 0}]).
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and map_key
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
extra=nil, %% extra information about the context, like pin_guard and map_key
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
vars=#{}, %% a map of defined variables and their alias
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
match_vars=nil, %% a set of all variables defined in a particular match
|
||||
export_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a dict counting the variables defined
|
||||
file=(<<"nofile">>) %% the current scope filename
|
||||
counter=#{}, %% a map counting the variables defined
|
||||
file=(<<"nofile">>), %% the current scope filename
|
||||
safe_by_default=false %% if case/cond/receive variables should be marked as safe by default
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
|
||||
@@ -81,7 +81,11 @@ ensure_loaded(Meta, Ref, E) ->
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, ?m(E, context_modules)) of
|
||||
true -> scheduled_module;
|
||||
true ->
|
||||
case ?m(E, module) of
|
||||
Ref -> circular_module;
|
||||
_ -> scheduled_module
|
||||
end;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, {Kind, Ref})
|
||||
@@ -137,8 +141,49 @@ lookup(Else, Dict, Counter) ->
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[inspect(Module)]).
|
||||
io_lib:format(
|
||||
"module ~ts is not loaded but was defined. This happens when you depend on "
|
||||
"a module in the same context it is defined. For example:\n"
|
||||
"\n"
|
||||
" defmodule MyApp do\n"
|
||||
" defmodule Mod do\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
" use Mod\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"Try defining the module outside the context that uses it:\n"
|
||||
"\n"
|
||||
" defmodule MyApp.Mod do\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
" defmodule MyApp do\n"
|
||||
" use MyApp.Mod\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"If the module is defined at the top-level and you are trying to "
|
||||
"use it at the top-level, such is not supported by Elixir",
|
||||
[inspect(Module)]);
|
||||
|
||||
format_error({circular_module, Module}) ->
|
||||
io_lib:format(
|
||||
"you are trying to use the module ~ts which is currently being defined.\n"
|
||||
"\n"
|
||||
"This may happen if you accidentally override the module you want to use. For example:\n"
|
||||
"\n"
|
||||
" defmodule MyApp do\n"
|
||||
" defmodule Supervisor do\n"
|
||||
" use Supervisor\n"
|
||||
" end\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"In the example above, the new Supervisor conflicts with Elixir's. "
|
||||
"This may be fixed by using the fully qualified name on definition:\n"
|
||||
"\n"
|
||||
" defmodule MyApp.Supervisor do\n"
|
||||
" use Supervisor\n"
|
||||
" end\n",
|
||||
[inspect(Module)]).
|
||||
|
||||
@@ -147,7 +147,7 @@ translate(Meta, Args, S) ->
|
||||
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{Final, FinalS} = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{{bin, ?line(Meta), lists:reverse(Final)}, FinalS}.
|
||||
{{bin, ?ann(Meta), lists:reverse(Final)}, FinalS}.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{Acc, S};
|
||||
@@ -165,11 +165,11 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
|
||||
true ->
|
||||
%% See explanation in elixir_utils:elixir_to_erl/1 to know
|
||||
%% why we can simply convert the binary to a list.
|
||||
{bin_element, ?line(Meta), {string, 0, binary_to_list(H)}, default, default};
|
||||
{bin_element, ?ann(Meta), {string, 0, binary_to_list(H)}, default, default};
|
||||
false ->
|
||||
case types_require_conversion(Types) of
|
||||
true ->
|
||||
{bin_element, ?line(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
|
||||
{bin_element, ?ann(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
|
||||
false ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
|
||||
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
|
||||
@@ -192,10 +192,10 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{bin, _, Elements} ->
|
||||
case (Size == default) andalso types_allow_splice(Types, Elements) of
|
||||
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements, Acc));
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?ann(Meta), Expr, Size, Types}|Acc])
|
||||
end;
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?ann(Meta), Expr, Size, Types}|Acc])
|
||||
end.
|
||||
|
||||
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
|
||||
@@ -224,6 +224,7 @@ elem_size({bin_element, _, _, {integer, _, Size}, Types}) -> {Size, unit_size(Ty
|
||||
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
|
||||
|
||||
unit_size([binary|T], _) -> unit_size(T, 8);
|
||||
unit_size([bytes|T], _) -> unit_size(T, 8);
|
||||
unit_size([{unit, Size}|_], _) -> Size;
|
||||
unit_size([_|T], Guess) -> unit_size(T, Guess);
|
||||
unit_size([], Guess) -> Guess.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/6, clauses/3, guards/4, get_pairs/3, get_pairs/4,
|
||||
-export([match/3, clause/6, clauses/3, guards/3, get_pairs/3, get_pairs/4,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -24,33 +24,34 @@ get_pairs(Key, Clauses, As, AllowNil) ->
|
||||
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{Result, NewS} = match(Fun, Args, S#elixir_scope{context=match,
|
||||
match_vars=ordsets:new(), backup_vars=Vars}),
|
||||
match_vars=#{}, backup_vars=Vars}),
|
||||
{Result, NewS#elixir_scope{context=Context,
|
||||
match_vars=MatchVars, backup_vars=BackupVars}};
|
||||
match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
clause(Meta, Fun, Args, Expr, Guards, S) when is_list(Meta) ->
|
||||
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{TExpr, SE} = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
|
||||
{TExpr, SE} = elixir_translator:translate(Expr,
|
||||
SA#elixir_scope{extra_guards=nil, export_vars=S#elixir_scope.export_vars}),
|
||||
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
TGuards = guards(Line, Guards, Extra, SA),
|
||||
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
|
||||
TGuards = guards(Guards, Extra, SA),
|
||||
{{clause, ?ann(Meta), TArgs, TGuards, unblock(TExpr)}, SE}.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
guards(Line, Guards, Extra, S) ->
|
||||
guards(Guards, Extra, S) ->
|
||||
SG = S#elixir_scope{context=guard, extra_guards=nil},
|
||||
|
||||
case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
_ -> [translate_guard(Guard, Extra, SG) || Guard <- Guards]
|
||||
end.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
translate_guard(Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate(Guard, S))|Extra].
|
||||
|
||||
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
|
||||
extract_guards(Else) -> {Else, []}.
|
||||
@@ -69,7 +70,7 @@ extract_splat_guards(Else) ->
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
|
||||
{TC, TS} = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
|
||||
{TC, TS} = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=#{}}),
|
||||
{TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)}}.
|
||||
|
||||
do_clauses(_Meta, [], S) ->
|
||||
@@ -79,7 +80,7 @@ do_clauses(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{TX, TS} = each_clause(Meta, X, SAcc),
|
||||
{TX, TS} = each_clause(X, SAcc),
|
||||
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
@@ -88,7 +89,7 @@ do_clauses(Meta, DecoupledClauses, S) ->
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(ReverseCV),
|
||||
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, [], CV),
|
||||
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, #{}, CV),
|
||||
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
@@ -97,18 +98,18 @@ do_clauses(Meta, DecoupledClauses, S) ->
|
||||
% is the old pointer.
|
||||
{FinalVars, FS} = lists:mapfoldl(fun({Key, Val}, Acc) ->
|
||||
normalize_vars(Key, Val, Acc)
|
||||
end, TS, AllVars),
|
||||
end, TS, maps:to_list(AllVars)),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that defines variables missing in one clause to the others.
|
||||
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
expand_clauses(?ann(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
expand_clauses(Ann, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
case generate_match_vars(FinalVars, ClauseVars, [], []) of
|
||||
{[], []} ->
|
||||
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
|
||||
expand_clauses(Ann, T, V, FinalVars, [Clause|Acc], S);
|
||||
{Left, Right} ->
|
||||
MatchExpr = generate_match(Line, Left, Right),
|
||||
MatchExpr = generate_match(Ann, Left, Right),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
@@ -122,8 +123,8 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
{[UserVar, MatchExpr, Final|RawClauseExprs], S};
|
||||
_ ->
|
||||
{VarName, _, SS} = elixir_scope:build_var('_', S),
|
||||
StorageVar = {var, Line, VarName},
|
||||
StorageExpr = {match, Line, StorageVar, Final},
|
||||
StorageVar = {var, Ann, VarName},
|
||||
StorageExpr = {match, Ann, StorageVar, Final},
|
||||
{[StorageVar, MatchExpr, StorageExpr|RawClauseExprs], SS}
|
||||
end;
|
||||
false ->
|
||||
@@ -131,34 +132,22 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
|
||||
expand_clauses(Ann, T, V, FinalVars, [FinalClause|Acc], FS)
|
||||
end;
|
||||
|
||||
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
expand_clauses(_Ann, [], [], _FinalVars, Acc, S) ->
|
||||
{lists:reverse(Acc), S}.
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause(Export, {match, Meta, [Condition], Expr}, S) ->
|
||||
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
|
||||
each_clause({match, Meta, [Condition], Expr}, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), Fun, [Arg], Expr, Guards, S);
|
||||
clause(Meta, fun elixir_translator:translate_args/2, [Arg], Expr, Guards, S);
|
||||
|
||||
each_clause(Export, {expr, Meta, [Condition], Expr}, S) ->
|
||||
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate(Expr, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
wrap_export_fun(Meta, Fun) ->
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, S) ->
|
||||
{TArgs, TS} = Fun(Args, S),
|
||||
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
|
||||
end
|
||||
end.
|
||||
each_clause({expr, Meta, [Condition], Expr}, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate(Expr, SC#elixir_scope{export_vars = S#elixir_scope.export_vars}),
|
||||
{{clause, ?ann(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -184,16 +173,22 @@ has_match_tuple(_) -> false.
|
||||
% by picking one value as reference and retrieving
|
||||
% its previous value.
|
||||
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars, export_vars=ClauseVars} = S) ->
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
normalize_vars(Key, {Ref, Counter, _Safe},
|
||||
#elixir_scope{vars=Vars, export_vars=ClauseVars, safe_by_default=SafeDefault} = S) ->
|
||||
{Expr, Safe} =
|
||||
case maps:find(Key, Vars) of
|
||||
{ok, {PrevRef, _, _}} ->
|
||||
{{var, 0, PrevRef}, true};
|
||||
error ->
|
||||
{{atom, 0, nil}, SafeDefault}
|
||||
end,
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
error -> {atom, 0, nil}
|
||||
end,
|
||||
Value = {Ref, Counter, Safe},
|
||||
|
||||
VS = S#elixir_scope{
|
||||
vars=maps:put(Key, Value, Vars),
|
||||
export_vars=maps:put(Key, Value, ClauseVars)
|
||||
},
|
||||
|
||||
{{Key, Value, Expr}, VS}.
|
||||
|
||||
@@ -201,7 +196,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars, export_vars=ClauseVars} = S)
|
||||
% or not and assigning the previous value.
|
||||
|
||||
generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
case maps:find(Key, ClauseVars) of
|
||||
{ok, Value} ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ok, Clause} ->
|
||||
@@ -216,11 +211,11 @@ generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
{Left, Right}.
|
||||
|
||||
generate_match(Line, [Left], [Right]) ->
|
||||
{match, Line, Left, Right};
|
||||
generate_match(Ann, [Left], [Right]) ->
|
||||
{match, Ann, Left, Right};
|
||||
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{match, Line, {tuple, Line, LeftVars}, {tuple, Line, RightVars}}.
|
||||
generate_match(Ann, LeftVars, RightVars) ->
|
||||
{match, Ann, {tuple, Ann, LeftVars}, {tuple, Ann, RightVars}}.
|
||||
|
||||
unblock({'block', _, Exprs}) -> Exprs;
|
||||
unblock(Exprs) -> [Exprs].
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
loaded=[],
|
||||
loaded=#{},
|
||||
mod_pool={[], 0},
|
||||
mod_ets=dict:new(),
|
||||
compilation_status=[]
|
||||
mod_ets=#{},
|
||||
compilation_status=#{}
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
@@ -42,24 +42,24 @@ handle_call({undefmodule, Ref}, _From, Config) ->
|
||||
|
||||
handle_call({acquire, Path}, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
case maps:find(Path, Current) of
|
||||
{ok, true} ->
|
||||
{reply, loaded, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
|
||||
Queued = maps:put(Path, {Ref, [From|List]}, Current),
|
||||
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
|
||||
error ->
|
||||
Queued = orddict:store(Path, {make_ref(), []}, Current),
|
||||
Queued = maps:put(Path, {make_ref(), []}, Current),
|
||||
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
|
||||
end;
|
||||
|
||||
handle_call(loaded, _From, Config) ->
|
||||
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
|
||||
{reply, [F || {F, true} <- maps:to_list(Config#elixir_code_server.loaded)], Config};
|
||||
|
||||
handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = maps:remove(CompilerPid, CompilationStatusList),
|
||||
CompilationStatus = maps:get(CompilerPid, CompilationStatusList),
|
||||
{reply, CompilationStatus,
|
||||
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
|
||||
@@ -76,35 +76,35 @@ handle_call(Request, _From, Config) ->
|
||||
|
||||
handle_cast({register_warning, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
|
||||
CompilerOptions = elixir_config:get(compiler_options),
|
||||
case orddict:find(warnings_as_errors, CompilerOptions) of
|
||||
CompilationStatusNew = maps:put(CompilerPid, error, CompilationStatusCurrent),
|
||||
CompilerOptions = elixir_config:get(compiler_options),
|
||||
case maps:find(warnings_as_errors, CompilerOptions) of
|
||||
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
_ -> {noreply, Config}
|
||||
end;
|
||||
|
||||
handle_cast({reset_warnings, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
|
||||
CompilationStatusNew = maps:put(CompilerPid, ok, CompilationStatusCurrent),
|
||||
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
|
||||
handle_cast({loaded, Path}, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
case maps:find(Path, Current) of
|
||||
{ok, true} ->
|
||||
{noreply, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
Done = maps:put(Path, true, Current),
|
||||
{noreply, Config#elixir_code_server{loaded=Done}};
|
||||
error ->
|
||||
Done = orddict:store(Path, true, Current),
|
||||
Done = maps:put(Path, true, Current),
|
||||
{noreply, Config#elixir_code_server{loaded=Done}}
|
||||
end;
|
||||
|
||||
handle_cast({unload_files, Files}, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
Unloaded = maps:without(Files, Current),
|
||||
{noreply, Config#elixir_code_server{loaded=Unloaded}};
|
||||
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T, Counter}} = Config) ->
|
||||
@@ -132,13 +132,13 @@ defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
|
||||
ets:insert(elixir_modules, Tuple),
|
||||
Ref = erlang:monitor(process, Pid),
|
||||
Mod = erlang:element(1, Tuple),
|
||||
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
|
||||
{Ref, Config#elixir_code_server{mod_ets=maps:put(Ref, Mod, ModEts)}}.
|
||||
|
||||
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
|
||||
case dict:find(Ref, ModEts) of
|
||||
case maps:find(Ref, ModEts) of
|
||||
{ok, Mod} ->
|
||||
ets:delete(elixir_modules, Mod),
|
||||
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
|
||||
Config#elixir_code_server{mod_ets=maps:remove(Ref, ModEts)};
|
||||
error ->
|
||||
Config
|
||||
end.
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
%% Public API
|
||||
|
||||
get_opt(Key) ->
|
||||
Dict = elixir_config:get(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{Key, Value} -> Value
|
||||
Map = elixir_config:get(compiler_options),
|
||||
case maps:find(Key, Map) of
|
||||
{ok, Value} -> Value;
|
||||
error -> false
|
||||
end.
|
||||
|
||||
%% Compilation entry points.
|
||||
@@ -67,7 +67,7 @@ eval_compilation(Forms, Vars, E) ->
|
||||
{Result, EE}.
|
||||
|
||||
code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
|
||||
Dict = [{{Name, Kind}, {Value, 0, true}} || {Name, Kind, Value, _} <- Vars],
|
||||
S = elixir_env:env_to_scope_with_vars(E, Dict),
|
||||
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
|
||||
|
||||
@@ -207,9 +207,7 @@ no_auto_import() ->
|
||||
|
||||
core() ->
|
||||
{ok, _} = application:ensure_all_started(elixir),
|
||||
New = orddict:from_list([{docs, false}, {internal, true}]),
|
||||
Merge = fun(_, _, Value) -> Value end,
|
||||
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
|
||||
Update = fun(Old) -> maps:merge(Old, #{docs => false, internal => true}) end,
|
||||
_ = elixir_config:update(compiler_options, Update),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
@@ -233,8 +231,8 @@ core_main() ->
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/kernel/def.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/utils.ex">>,
|
||||
<<"lib/elixir/lib/behaviour.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
|
||||
@@ -34,7 +34,7 @@ delete_definition(Module, Tuple) ->
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) when is_integer(Line) ->
|
||||
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
|
||||
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
|
||||
|
||||
@@ -52,6 +52,10 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
%% Check if there is a file information in the definition.
|
||||
%% If so, we assume this come from another source and
|
||||
%% we need to linify taking into account keep line numbers.
|
||||
%%
|
||||
%% Line and File will always point to the caller. __ENV__.line
|
||||
%% will always point to the quoted one and __ENV__.file will
|
||||
%% always point to the one at @file or the quoted one.
|
||||
{Location, Key} =
|
||||
case elixir_utils:meta_location(Meta) of
|
||||
{_, _} = KeepLocation -> {KeepLocation, keep};
|
||||
@@ -70,20 +74,26 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
|
||||
Arity = length(Args),
|
||||
Tuple = {Name, Arity},
|
||||
E = ER#{function := Tuple},
|
||||
Location = retrieve_location(KeepLocation, Module),
|
||||
|
||||
E = case Location of
|
||||
{F, _} -> ER#{function := Tuple, file := elixir_utils:characters_to_binary(F)};
|
||||
nil -> ER#{function := Tuple}
|
||||
end,
|
||||
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_location(KeepLocation, Module),
|
||||
{Function, Defaults, Super} = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = ?m(E, file),
|
||||
compile_super(Module, Super, E),
|
||||
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
|
||||
|
||||
%% Retrieve the file before we changed it based on @file
|
||||
File = ?m(ER, file),
|
||||
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Module, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
@@ -94,15 +104,10 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocatio
|
||||
%% @on_definition
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_env:linify({Line, E}),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||
ok
|
||||
end.
|
||||
Env = elixir_env:linify({Line, E}),
|
||||
Callbacks = elixir_module:get_attribute(Module, on_definition),
|
||||
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||
ok.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
@@ -133,10 +138,7 @@ retrieve_location(Location, Module) ->
|
||||
end.
|
||||
|
||||
get_location_attribute(Module) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> nil;
|
||||
false -> 'Elixir.Module':get_attribute(Module, file)
|
||||
end.
|
||||
elixir_module:get_attribute(Module, file).
|
||||
|
||||
normalize_location(File) ->
|
||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
|
||||
@@ -156,7 +158,10 @@ translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Li
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
case Body of
|
||||
nil -> check_args_for_bodyless_clause(Line, EArgs, E);
|
||||
_ -> ok
|
||||
end,
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
@@ -170,8 +175,8 @@ translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{TClause, TS} = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, S),
|
||||
{TClause, TS} = elixir_clauses:clause([{line, Line}],
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, S),
|
||||
|
||||
FClause = case is_macro(Kind) of
|
||||
true ->
|
||||
|
||||
@@ -29,14 +29,14 @@ unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S
|
||||
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Line = ?line(DefMeta),
|
||||
Ann = ?ann(DefMeta),
|
||||
|
||||
Call = {call, Line,
|
||||
{atom, Line, name_for_kind(Kind, Name)},
|
||||
Call = {call, Ann,
|
||||
{atom, Ann, name_for_kind(Kind, Name)},
|
||||
DefInvoke
|
||||
},
|
||||
|
||||
Clause = {clause, Line, DefArgs, [], [Call]},
|
||||
Clause = {clause, Ann, DefArgs, [], [Call]},
|
||||
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
|
||||
|
||||
unpack_each(Kind, Name, [H|T], Acc, Clauses, S) ->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
-define(attr, {elixir, overridable}).
|
||||
|
||||
setup(Module) ->
|
||||
overridable(Module, []).
|
||||
overridable(Module, #{}).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
|
||||
@@ -18,7 +18,7 @@ overridable(Module, Value) ->
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
case maps:find(Tuple, Overridable) of
|
||||
{ok, {_, _, _, _}} -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, {no_super, Module, Tuple})
|
||||
end.
|
||||
@@ -29,18 +29,20 @@ name(Module, Function) ->
|
||||
name(Module, Function, overridable(Module)).
|
||||
|
||||
name(_Module, {Name, _} = Function, Overridable) ->
|
||||
{Count, _, _, _} = orddict:fetch(Function, Overridable),
|
||||
{Count, _, _, _} = maps:get(Function, Overridable),
|
||||
elixir_utils:atom_concat([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
|
||||
store(Module, Function, GenerateName) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:fetch(Function, Overridable) of
|
||||
{_Count, _Clause, _Neighbours, true} -> ok;
|
||||
case maps:get(Function, Overridable) of
|
||||
{_Count, _Clause, _Neighbours, true} ->
|
||||
ok;
|
||||
{Count, Clause, Neighbours, false} ->
|
||||
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
|
||||
{{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
|
||||
overridable(Module, maps:put(Function, {Count, Clause, Neighbours, true}, Overridable)),
|
||||
{{{Name, Arity}, Kind, Line, File, _Check,
|
||||
Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
|
||||
|
||||
{FinalKind, FinalName} = case GenerateName of
|
||||
true -> {defp, name(Module, Function, Overridable)};
|
||||
@@ -62,14 +64,14 @@ super(Module, Function) ->
|
||||
store(Module, Function, true).
|
||||
|
||||
store_pending(Module) ->
|
||||
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
||||
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- maps:to_list(overridable(Module)),
|
||||
not 'Elixir.Module':'defines?'(Module, X)],
|
||||
ok.
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({no_super, Module, {Name, Arity}}) ->
|
||||
Bins = [format_fa(X) || {X, {_, _, _, _}} <- overridable(Module)],
|
||||
Bins = [format_fa(X) || {X, {_, _, _, _}} <- maps:to_list(overridable(Module))],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
|
||||
|
||||
@@ -9,9 +9,9 @@ new() ->
|
||||
file => <<"nofile">>, %% the current filename
|
||||
line => 1, %% the current line
|
||||
function => nil, %% the current function
|
||||
context => nil, %% can be match_vars, guards or nil
|
||||
context => nil, %% can be match, guard or nil
|
||||
requires => [], %% a set with modules required
|
||||
aliases => [], %% an orddict with aliases by new -> old names
|
||||
aliases => [], %% a list of aliases by new -> old names
|
||||
functions => [], %% a list with functions imported from module
|
||||
macros => [], %% a list with macros imported from module
|
||||
macro_aliases => [], %% keep aliases defined inside a macro
|
||||
@@ -27,10 +27,10 @@ env_to_scope(#{module := Module, file := File, function := Function, context :=
|
||||
#elixir_scope{module=Module, file=File, function=Function, context=Context}.
|
||||
|
||||
env_to_scope_with_vars(Env, Vars) ->
|
||||
Map = maps:from_list(Vars),
|
||||
(env_to_scope(Env))#elixir_scope{
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'_', length(Vars)}]
|
||||
}.
|
||||
vars=Map, counter=#{'_' => map_size(Map)}
|
||||
}.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
@@ -40,12 +40,12 @@ mergev(E1, E2) when is_list(E1) ->
|
||||
E2#{
|
||||
vars := merge_vars(E1, ?m(E2, vars)),
|
||||
export_vars := merge_opt_vars(E1, ?m(E2, export_vars))
|
||||
};
|
||||
};
|
||||
mergev(E1, E2) ->
|
||||
E2#{
|
||||
vars := merge_vars(?m(E1, vars), ?m(E2, vars)),
|
||||
export_vars := merge_opt_vars(?m(E1, export_vars), ?m(E2, export_vars))
|
||||
}.
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (imports
|
||||
|
||||
@@ -99,7 +99,6 @@ parse_erl_term(Term) ->
|
||||
{ok, Parsed} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
|
||||
Parsed.
|
||||
|
||||
|
||||
%% Handle warnings and errors from Erlang land (called during module compilation)
|
||||
|
||||
%% Ignore on bootstrap
|
||||
@@ -193,6 +192,10 @@ handle_file_error(File, {Line, erl_lint, {unsafe_var, Var, {In, _Where}}}) ->
|
||||
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
|
||||
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line, erl_lint, {undefined_function, {F, A}}}) ->
|
||||
Message = io_lib:format("undefined function ~ts/~B", [F, A]),
|
||||
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line, erl_lint, {spec_fun_undefined, {M, F, A}}}) ->
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
|
||||
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
|
||||
@@ -216,6 +219,11 @@ file_format(Line, File) ->
|
||||
format_var(Var) ->
|
||||
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
|
||||
|
||||
%% TODO: Remove this clause when we depend only on Erlang 19.
|
||||
format_error(erl_lint, {bittype_mismatch, Val1, Val2, Kind}) ->
|
||||
Desc = "conflict in ~s specification for bit field: '~p' and '~p'",
|
||||
io_lib:format(Desc, [Kind, Val1, Val2]);
|
||||
|
||||
format_error([], Desc) ->
|
||||
io_lib:format("~p", [Desc]);
|
||||
|
||||
|
||||
@@ -46,6 +46,20 @@ expand({'__aliases__', _, _} = Alias, E) ->
|
||||
|
||||
%% alias
|
||||
|
||||
expand({Kind, Meta, [{{'.', _, [Base, '{}']}, _, Refs} | Rest]}, E)
|
||||
when Kind == alias; Kind == require; Kind == import ->
|
||||
case Rest of
|
||||
[] ->
|
||||
expand_multi_alias_call(Kind, Meta, Base, Refs, [], E);
|
||||
[Opts] ->
|
||||
case lists:keymember(as, 1, Opts) of
|
||||
true ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
":as option is not supported by multi-alias call");
|
||||
false ->
|
||||
expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, E)
|
||||
end
|
||||
end;
|
||||
expand({alias, Meta, [Ref]}, E) ->
|
||||
expand({alias, Meta, [Ref, []]}, E);
|
||||
expand({alias, Meta, [Ref, KV]}, E) ->
|
||||
@@ -243,6 +257,11 @@ expand({'try', Meta, [KV]}, E) ->
|
||||
expand({for, Meta, [_|_] = Args}, E) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
%% With
|
||||
|
||||
expand({with, Meta, [_|_] = Args}, E) ->
|
||||
elixir_with:expand(Meta, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({super, Meta, Args}, E) when is_list(Args) ->
|
||||
@@ -257,8 +276,8 @@ expand({'^', Meta, [Arg]}, #{context := match} = E) ->
|
||||
{{Name, _, Kind} = EArg, EA} when is_atom(Name), is_atom(Kind) ->
|
||||
{{'^', Meta, [EArg]}, EA};
|
||||
_ ->
|
||||
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
||||
compile_error(Meta, ?m(E, file), Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
||||
compile_error(Meta, ?m(E, file), Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
expand({'^', Meta, [Arg]}, E) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
@@ -362,6 +381,20 @@ expand(Other, E) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, E) ->
|
||||
{BaseRef, EB} = expand_without_aliases_report(Base, E),
|
||||
Fun = fun
|
||||
({'__aliases__', _, Ref}, ER) ->
|
||||
expand({Kind, Meta, [elixir_aliases:concat([BaseRef | Ref]), Opts]}, ER);
|
||||
(Ref, ER) when is_atom(Ref) ->
|
||||
expand({Kind, Meta, [elixir_aliases:concat([BaseRef, Ref]), Opts]}, ER);
|
||||
(Other, _ER) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid argument for ~ts, expected a compile time atom or alias, got: ~ts",
|
||||
[atom_to_list(Kind), 'Elixir.Macro':to_string(Other)])
|
||||
end,
|
||||
lists:mapfoldl(Fun, EB, Refs).
|
||||
|
||||
expand_list([{'|', Meta, [_, _] = Args}], Fun, Acc, List) ->
|
||||
{EArgs, EAcc} = lists:mapfoldl(Fun, Acc, Args),
|
||||
expand_list([], Fun, EAcc, [{'|', Meta, EArgs}|List]);
|
||||
@@ -483,7 +516,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
||||
ok
|
||||
end,
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
|
||||
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs, EA),
|
||||
elixir_env:mergev(EL, EA)}.
|
||||
|
||||
%% Lexical helpers
|
||||
@@ -586,9 +619,11 @@ expand_aliases({'__aliases__', Meta, _} = Alias, E, Report) ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
|
||||
{Receiver, EA};
|
||||
false ->
|
||||
compile_error(Meta, ?m(E, file), "an alias must expand to an atom "
|
||||
"at compilation time, but did not in \"~ts\". Use Module.concat/2 "
|
||||
"if you want to dynamically generate aliases", ['Elixir.Macro':to_string(Alias)])
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid alias: \"~ts\". If you wanted to define an alias, an alias must expand "
|
||||
"to an atom at compile time but it did not, you may use Module.concat/2 to build "
|
||||
"it at runtime. If instead you wanted to invoke a function or access a field, "
|
||||
"wrap the function or field name in double quotes", ['Elixir.Macro':to_string(Alias)])
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -625,7 +660,7 @@ assert_no_guard_scope(Meta, _Kind, #{context := guard, file := File}) ->
|
||||
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
|
||||
|
||||
format_error({useless_literal, Term}) ->
|
||||
io_lib:format("code block starting at line contains unused literal ~ts "
|
||||
io_lib:format("code block contains unused literal ~ts "
|
||||
"(remove the literal or assign it to _ to avoid warnings)",
|
||||
['Elixir.Macro':to_string(Term)]);
|
||||
format_error({useless_var, Var}) ->
|
||||
|
||||
@@ -18,9 +18,9 @@ def(Fun, Args, Guards, Body, E) ->
|
||||
|
||||
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
|
||||
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, #{export_vars := ExportVars} = E) ->
|
||||
{ELeft, EL} = Fun(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL#{export_vars := ExportVars}),
|
||||
{{'->', Meta, [ELeft, ERight]}, ER};
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
|
||||
@@ -205,18 +205,8 @@ expand_one(Meta, Kind, Key, Fun) ->
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, #{export_vars := ExportVars} = EE) ->
|
||||
{FArgs, FE} = Fun(Args, EE),
|
||||
{FArgs, FE#{export_vars := ExportVars}}
|
||||
end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
|
||||
{EClause, EC} = clause(Meta, Kind, Fun, Clause, E),
|
||||
{EClause, elixir_env:merge_vars(Vars, ?m(EC, export_vars))}
|
||||
end,
|
||||
{EClauses, EVars} = lists:mapfoldl(Transformer, Acc, Clauses),
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
translate(Meta, Clauses, S) ->
|
||||
Transformer = fun({'->', CMeta, [ArgsWithGuards, Expr]}, Acc) ->
|
||||
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
{TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
|
||||
Args, Expr, Guards, Acc),
|
||||
{TClause, TS } = elixir_clauses:clause(CMeta, fun translate_fn_match/2,
|
||||
Args, Expr, Guards, Acc),
|
||||
{TClause, elixir_scope:mergef(S, TS)}
|
||||
end,
|
||||
|
||||
@@ -16,15 +16,15 @@ translate(Meta, Clauses, S) ->
|
||||
|
||||
case lists:usort(Arities) of
|
||||
[_] ->
|
||||
{{'fun', ?line(Meta), {clauses, TClauses}}, NS};
|
||||
{{'fun', ?ann(Meta), {clauses, TClauses}}, NS};
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{TArg, TS} = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
|
||||
{TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars}}.
|
||||
{TArg, TS} = elixir_translator:translate_args(Arg, S#elixir_scope{extra=pin_guard}),
|
||||
{TArg, TS#elixir_scope{extra=S#elixir_scope.extra}}.
|
||||
|
||||
%% Expansion
|
||||
|
||||
|
||||
+102
-95
@@ -53,30 +53,35 @@ translate(Meta, Args, Return, S) ->
|
||||
{AccName, _, SA} = elixir_scope:build_var('_', S),
|
||||
{VarName, _, SV} = elixir_scope:build_var('_', SA),
|
||||
|
||||
Line = ?line(Meta),
|
||||
Acc = {var, Line, AccName},
|
||||
Var = {var, Line, VarName},
|
||||
Ann = ?ann(Meta),
|
||||
Acc = {var, Ann, AccName},
|
||||
Var = {var, Ann, VarName},
|
||||
|
||||
{Cases, [{do, Expr}|Opts]} = elixir_utils:split_last(Args),
|
||||
|
||||
{TInto, SI} =
|
||||
case lists:keyfind(into, 1, Opts) of
|
||||
{into, Into} -> elixir_translator:translate(Into, SV);
|
||||
false when Return -> {{nil, Line}, SV};
|
||||
false when Return -> {{nil, Ann}, SV};
|
||||
false -> {false, SV}
|
||||
end,
|
||||
|
||||
{TCases, SC} = translate_gen(Meta, Cases, [], SI),
|
||||
{TExpr, SE} = elixir_translator:translate(Expr, SC),
|
||||
{TExpr, SE} = elixir_translator:translate(wrap_expr(Expr, TInto), SC),
|
||||
SF = elixir_scope:mergec(SI, SE),
|
||||
|
||||
case comprehension_expr(TInto, TExpr) of
|
||||
{inline, TIntoExpr} ->
|
||||
{build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
|
||||
{build_inline(Ann, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
|
||||
{into, TIntoExpr} ->
|
||||
build_into(Line, TCases, TIntoExpr, TInto, Var, Acc, SF)
|
||||
build_into(Ann, TCases, TIntoExpr, TInto, Var, Acc, SF)
|
||||
end.
|
||||
|
||||
%% In case we have no return, we wrap the expression
|
||||
%% in a block that returns nil.
|
||||
wrap_expr(Expr, false) -> {'__block__', [], [Expr, nil]};
|
||||
wrap_expr(Expr, _) -> Expr.
|
||||
|
||||
translate_gen(ForMeta, [{'<-', Meta, [Left, Right]}|T], Acc, S) ->
|
||||
{TLeft, TRight, TFilters, TT, TS} = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{enum, Meta, TLeft, TRight, TFilters}|Acc],
|
||||
@@ -98,9 +103,14 @@ translate_gen(ForMeta, _, _, S) ->
|
||||
|
||||
translate_gen(_Meta, Left, Right, T, S) ->
|
||||
{TRight, SR} = elixir_translator:translate(Right, S),
|
||||
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{TT, {TFilters, TS}} = translate_filters(T, SL),
|
||||
{TLeft, TRight, TFilters, TT, TS}.
|
||||
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left,
|
||||
SR#elixir_scope{extra=pin_guard, extra_guards=[]}),
|
||||
|
||||
ExtraGuards = [{nil, X} || X <- SL#elixir_scope.extra_guards],
|
||||
SF = SL#elixir_scope{extra=S#elixir_scope.extra, extra_guards=nil},
|
||||
|
||||
{TT, {TFilters, TS}} = translate_filters(T, SF),
|
||||
{TLeft, TRight, ExtraGuards ++ TFilters, TT, TS}.
|
||||
|
||||
translate_filters(T, S) ->
|
||||
{Filters, Rest} = collect_filters(T, []),
|
||||
@@ -125,56 +135,56 @@ collect_filters([H|T], Acc) ->
|
||||
collect_filters([], Acc) ->
|
||||
{Acc, []}.
|
||||
|
||||
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
|
||||
build_inline(Ann, Clauses, Expr, Into, _Var, Acc, S) ->
|
||||
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
|
||||
true -> build_comprehension(Line, Clauses, Expr, Into);
|
||||
true -> build_comprehension(Ann, Clauses, Expr, Into);
|
||||
false -> build_reduce(Clauses, Expr, Into, Acc, S)
|
||||
end.
|
||||
|
||||
build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
|
||||
{Kind, SK} = build_var(Line, S),
|
||||
{Reason, SR} = build_var(Line, SK),
|
||||
{Stack, ST} = build_var(Line, SR),
|
||||
{Done, SD} = build_var(Line, ST),
|
||||
build_into(Ann, Clauses, Expr, Into, Fun, Acc, S) ->
|
||||
{Kind, SK} = build_var(Ann, S),
|
||||
{Reason, SR} = build_var(Ann, SK),
|
||||
{Stack, ST} = build_var(Ann, SR),
|
||||
{Done, SD} = build_var(Ann, ST),
|
||||
|
||||
IntoExpr = {call, Line, Fun, [Acc, pair(Line, cont, Expr)]},
|
||||
MatchExpr = {match, Line,
|
||||
{tuple, Line, [Acc, Fun]},
|
||||
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
|
||||
IntoExpr = {call, Ann, Fun, [Acc, pair(Ann, cont, Expr)]},
|
||||
MatchExpr = {match, Ann,
|
||||
{tuple, Ann, [Acc, Fun]},
|
||||
elixir_utils:erl_call(Ann, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
|
||||
TryExpr =
|
||||
{'try', Line,
|
||||
{'try', Ann,
|
||||
[build_reduce_clause(Clauses, IntoExpr, Acc, Acc, SD)],
|
||||
[{clause, Line,
|
||||
[{clause, Ann,
|
||||
[Done],
|
||||
[],
|
||||
[{call, Line, Fun, [Done, {atom, Line, done}]}]}],
|
||||
[{clause, Line,
|
||||
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
|
||||
[{call, Ann, Fun, [Done, {atom, Ann, done}]}]}],
|
||||
[{clause, Ann,
|
||||
[{tuple, Ann, [Kind, Reason, {var, Ann, '_'}]}],
|
||||
[],
|
||||
[{match, Line, Stack, elixir_utils:erl_call(Line, erlang, get_stacktrace, [])},
|
||||
{call, Line, Fun, [Acc, {atom, Line, halt}]},
|
||||
elixir_utils:erl_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
|
||||
[{match, Ann, Stack, elixir_utils:erl_call(Ann, erlang, get_stacktrace, [])},
|
||||
{call, Ann, Fun, [Acc, {atom, Ann, halt}]},
|
||||
elixir_utils:erl_call(Ann, erlang, raise, [Kind, Reason, Stack])]}],
|
||||
[]},
|
||||
|
||||
{{block, Line, [MatchExpr, TryExpr]}, SD}.
|
||||
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
|
||||
|
||||
%% Helpers
|
||||
|
||||
build_reduce(Clauses, Expr, false, Acc, S) ->
|
||||
build_reduce_clause(Clauses, Expr, {nil, 0}, Acc, S);
|
||||
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
|
||||
ListExpr = {cons, Line, Expr, Acc},
|
||||
elixir_utils:erl_call(Line, lists, reverse,
|
||||
build_reduce(Clauses, Expr, {nil, Ann} = Into, Acc, S) ->
|
||||
ListExpr = {cons, Ann, Expr, Acc},
|
||||
elixir_utils:erl_call(Ann, lists, reverse,
|
||||
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
|
||||
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
|
||||
{bin, Line, Elements} = Expr,
|
||||
BinExpr = {bin, Line, [{bin_element, Line, Acc, default, [bitstring]}|Elements]},
|
||||
{bin, Ann, Elements} = Expr,
|
||||
BinExpr = {bin, Ann, [{bin_element, Ann, Acc, default, [bitstring]}|Elements]},
|
||||
build_reduce_clause(Clauses, BinExpr, Into, Acc, S).
|
||||
|
||||
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
Ann = ?ann(Meta),
|
||||
True = build_reduce_clause(T, Expr, Acc, Acc, S),
|
||||
False = Acc,
|
||||
|
||||
@@ -182,23 +192,23 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
|
||||
case is_var(Left) of
|
||||
true -> [];
|
||||
false ->
|
||||
[{clause, -1,
|
||||
[{var, Line, '_'}, Acc], [],
|
||||
[{clause, ?generated,
|
||||
[{var, Ann, '_'}, Acc], [],
|
||||
[False]}]
|
||||
end,
|
||||
|
||||
Clauses1 =
|
||||
[{clause, Line,
|
||||
[{clause, Ann,
|
||||
[Left, Acc], [],
|
||||
[join_filters(Line, Filters, True, False)]}|Clauses0],
|
||||
[join_filters(Ann, Filters, True, False)]}|Clauses0],
|
||||
|
||||
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
|
||||
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
|
||||
Args = [Right, Arg, {'fun', Ann, {clauses, Clauses1}}],
|
||||
elixir_utils:erl_call(Ann, 'Elixir.Enum', reduce, Args);
|
||||
|
||||
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
{Tail, ST} = build_var(Line, S),
|
||||
{Fun, SF} = build_var(Line, ST),
|
||||
Ann = ?ann(Meta),
|
||||
{Tail, ST} = build_var(Ann, S),
|
||||
{Fun, SF} = build_var(Ann, ST),
|
||||
|
||||
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
|
||||
False = Acc,
|
||||
@@ -206,26 +216,26 @@ build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
{bin, _, Elements} = Left,
|
||||
|
||||
BinMatch =
|
||||
{bin, Line, Elements ++ [{bin_element, Line, Tail, default, [bitstring]}]},
|
||||
{bin, Ann, Elements ++ [{bin_element, Ann, Tail, default, [bitstring]}]},
|
||||
NoVarMatch =
|
||||
{bin, Line, no_var(Elements) ++ [{bin_element, Line, Tail, default, [bitstring]}]},
|
||||
{bin, Ann, no_var(Elements) ++ [{bin_element, Ann, Tail, default, [bitstring]}]},
|
||||
|
||||
Clauses =
|
||||
[{clause, Line,
|
||||
[{clause, Ann,
|
||||
[BinMatch, Acc], [],
|
||||
[{call, Line, Fun, [Tail, join_filters(Line, Filters, True, False)]}]},
|
||||
{clause, -1,
|
||||
[{call, Ann, Fun, [Tail, join_filters(Ann, Filters, True, False)]}]},
|
||||
{clause, ?generated,
|
||||
[NoVarMatch, Acc], [],
|
||||
[{call, Line, Fun, [Tail, False]}]},
|
||||
{clause, -1,
|
||||
[{bin, Line, []}, Acc], [],
|
||||
[{call, Ann, Fun, [Tail, False]}]},
|
||||
{clause, ?generated,
|
||||
[{bin, Ann, []}, Acc], [],
|
||||
[Acc]},
|
||||
{clause, -1,
|
||||
[Tail, {var, Line, '_'}], [],
|
||||
[elixir_utils:erl_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
|
||||
{clause, ?generated,
|
||||
[Tail, {var, Ann, '_'}], [],
|
||||
[elixir_utils:erl_call(Ann, erlang, error, [pair(Ann, badarg, Tail)])]}],
|
||||
|
||||
{call, Line,
|
||||
{named_fun, Line, element(3, Fun), Clauses},
|
||||
{call, Ann,
|
||||
{named_fun, Ann, element(3, Fun), Clauses},
|
||||
[Right, Arg]};
|
||||
|
||||
build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
|
||||
@@ -234,31 +244,28 @@ build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
|
||||
is_var({var, _, _}) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
pair(Line, Atom, Arg) ->
|
||||
{tuple, Line, [{atom, Line, Atom}, Arg]}.
|
||||
pair(Ann, Atom, Arg) ->
|
||||
{tuple, Ann, [{atom, Ann, Atom}, Arg]}.
|
||||
|
||||
build_var(Line, S) ->
|
||||
build_var(Ann, S) ->
|
||||
{Name, _, ST} = elixir_scope:build_var('_', S),
|
||||
{{var, Line, Name}, ST}.
|
||||
{{var, Ann, Name}, ST}.
|
||||
|
||||
no_var(Elements) ->
|
||||
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
|
||||
{bin_element, Line, Expr, Size, Types} <- Elements].
|
||||
no_var_expr({var, Line, _}) ->
|
||||
{var, Line, '_'}.
|
||||
[{bin_element, Ann, no_var_expr(Expr), Size, Types} ||
|
||||
{bin_element, Ann, Expr, Size, Types} <- Elements].
|
||||
no_var_expr({var, Ann, _}) ->
|
||||
{var, Ann, '_'}.
|
||||
|
||||
build_comprehension(Line, Clauses, Expr, false) ->
|
||||
{block, Line, [
|
||||
build_comprehension(Line, Clauses, Expr, {nil, Line}),
|
||||
{nil, Line}
|
||||
]};
|
||||
build_comprehension(Line, Clauses, Expr, Into) ->
|
||||
{comprehension_kind(Into), Line, Expr, comprehension_clause(Clauses)}.
|
||||
build_comprehension(Ann, Clauses, Expr, false) ->
|
||||
{lc, Ann, Expr, comprehension_clause(Clauses)};
|
||||
build_comprehension(Ann, Clauses, Expr, Into) ->
|
||||
{comprehension_kind(Into), Ann, Expr, comprehension_clause(Clauses)}.
|
||||
|
||||
comprehension_clause([{Kind, Meta, Left, Right, Filters}|T]) ->
|
||||
Line = ?line(Meta),
|
||||
[{comprehension_generator(Kind), Line, Left, Right}] ++
|
||||
comprehension_filter(Line, Filters) ++
|
||||
Ann = ?ann(Meta),
|
||||
[{comprehension_generator(Kind), Ann, Left, Right}] ++
|
||||
comprehension_filter(Ann, Filters) ++
|
||||
comprehension_clause(T);
|
||||
comprehension_clause([]) ->
|
||||
[].
|
||||
@@ -271,8 +278,8 @@ comprehension_generator(bin) -> b_generate.
|
||||
|
||||
comprehension_expr({bin, _, []}, {bin, _, _} = Expr) ->
|
||||
{inline, Expr};
|
||||
comprehension_expr({bin, Line, []}, Expr) ->
|
||||
BinExpr = {bin, Line, [{bin_element, Line, Expr, default, [bitstring]}]},
|
||||
comprehension_expr({bin, Ann, []}, Expr) ->
|
||||
BinExpr = {bin, Ann, [{bin_element, Ann, Expr, default, [bitstring]}]},
|
||||
{inline, BinExpr};
|
||||
comprehension_expr({nil, _}, Expr) ->
|
||||
{inline, Expr};
|
||||
@@ -281,29 +288,29 @@ comprehension_expr(false, Expr) ->
|
||||
comprehension_expr(_, Expr) ->
|
||||
{into, Expr}.
|
||||
|
||||
comprehension_filter(Line, Filters) ->
|
||||
[join_filter(Line, Filter, {atom, Line, true}, {atom, Line, false}) ||
|
||||
comprehension_filter(Ann, Filters) ->
|
||||
[join_filter(Ann, Filter, {atom, Ann, true}, {atom, Ann, false}) ||
|
||||
Filter <- lists:reverse(Filters)].
|
||||
|
||||
join_filters(_Line, [], True, _False) ->
|
||||
join_filters(_Ann, [], True, _False) ->
|
||||
True;
|
||||
join_filters(Line, [H|T], True, False) ->
|
||||
join_filters(Ann, [H|T], True, False) ->
|
||||
lists:foldl(fun(Filter, Acc) ->
|
||||
join_filter(Line, Filter, Acc, False)
|
||||
end, join_filter(Line, H, True, False), T).
|
||||
join_filter(Ann, Filter, Acc, False)
|
||||
end, join_filter(Ann, H, True, False), T).
|
||||
|
||||
join_filter(Line, {nil, Filter}, True, False) ->
|
||||
{'case', Line, Filter, [
|
||||
{clause, Line, [{atom, Line, true}], [], [True]},
|
||||
{clause, Line, [{atom, Line, false}], [], [False]}
|
||||
join_filter(Ann, {nil, Filter}, True, False) ->
|
||||
{'case', Ann, Filter, [
|
||||
{clause, Ann, [{atom, Ann, true}], [], [True]},
|
||||
{clause, Ann, [{atom, Ann, false}], [], [False]}
|
||||
]};
|
||||
join_filter(Line, {Var, Filter}, True, False) ->
|
||||
join_filter(Ann, {Var, Filter}, True, False) ->
|
||||
Guard =
|
||||
{op, Line, 'orelse',
|
||||
{op, Line, '==', Var, {atom, Line, false}},
|
||||
{op, Line, '==', Var, {atom, Line, nil}}},
|
||||
{op, Ann, 'orelse',
|
||||
{op, Ann, '==', Var, {atom, Ann, false}},
|
||||
{op, Ann, '==', Var, {atom, Ann, nil}}},
|
||||
|
||||
{'case', Line, Filter, [
|
||||
{clause, Line, [Var], [[Guard]], [False]},
|
||||
{clause, Line, [{var, Line, '_'}], [], [True]}
|
||||
{'case', Ann, Filter, [
|
||||
{clause, Ann, [Var], [[Guard]], [False]},
|
||||
{clause, Ann, [{var, Ann, '_'}], [], [True]}
|
||||
]}.
|
||||
|
||||
@@ -28,10 +28,12 @@ import(Meta, Ref, Opts, E) ->
|
||||
{Functions, Macros}.
|
||||
|
||||
import_functions(Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, ?m(E, functions), E, fun() -> get_functions(Ref) end).
|
||||
calculate(Meta, Ref, Opts, ?m(E, functions), ?m(E, file), fun() ->
|
||||
get_functions(Ref)
|
||||
end).
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, ?m(E, macros), E, fun() ->
|
||||
calculate(Meta, Ref, Opts, ?m(E, macros), ?m(E, file), fun() ->
|
||||
case Force of
|
||||
true -> get_macros(Meta, Ref, E);
|
||||
false -> get_optional_macros(Ref)
|
||||
@@ -49,13 +51,21 @@ record_warn(Meta, Ref, Opts, Added, E) ->
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
calculate(Meta, Key, Opts, Old, File, Existing) ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{only, Only} when is_list(Only) ->
|
||||
ok = ensure_keyword_list(Meta, File, Only, only),
|
||||
case keyfind(except, Opts) of
|
||||
false -> ok;
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, File,
|
||||
":only and :except can only be given together to import"
|
||||
" when :only is either :functions or :macros")
|
||||
end,
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name, Arity}|_] ->
|
||||
Tuple = {invalid_import, {Key, Name, Arity}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
_ ->
|
||||
intersection(Only, Existing())
|
||||
end;
|
||||
@@ -63,6 +73,7 @@ calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> remove_underscored(Existing());
|
||||
{except, Except} when is_list(Except) ->
|
||||
ok = ensure_keyword_list(Meta, File, Except, except),
|
||||
case keyfind(Key, Old) of
|
||||
false -> remove_underscored(Existing()) -- Except;
|
||||
{Key, OldImports} -> OldImports -- Except
|
||||
@@ -78,7 +89,7 @@ calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
[] ->
|
||||
{false, keydelete(Key, Old)};
|
||||
_ ->
|
||||
ensure_no_special_form_conflict(Meta, ?m(E, file), Key, Final),
|
||||
ensure_no_special_form_conflict(Meta, File, Key, Final),
|
||||
{true, [{Key, Final}|keydelete(Key, Old)]}
|
||||
end.
|
||||
|
||||
@@ -131,6 +142,14 @@ ensure_no_special_form_conflict(Meta, File, Key, [{Name, Arity}|T]) ->
|
||||
|
||||
ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
|
||||
|
||||
ensure_keyword_list(_Meta, _File, [], _Kind) -> ok;
|
||||
|
||||
ensure_keyword_list(Meta, File, [{Key, _} | Rest], Kind) when is_atom(Key) ->
|
||||
ensure_keyword_list(Meta, File, Rest, Kind);
|
||||
|
||||
ensure_keyword_list(Meta, File, _Other, Kind) ->
|
||||
elixir_errors:compile_error(Meta, File, "invalid :~s option for import, expected a keyword list", [Kind]).
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({invalid_import, {Receiver, Name, Arity}}) ->
|
||||
@@ -204,6 +223,7 @@ special_form('unquote_splicing', 1) -> true;
|
||||
special_form('fn', _) -> true;
|
||||
special_form('super', _) -> true;
|
||||
special_form('for', _) -> true;
|
||||
special_form('with', _) -> true;
|
||||
special_form('cond', 1) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
|
||||
@@ -67,20 +67,20 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
|
||||
|
||||
if
|
||||
TUpdate /= nil ->
|
||||
Line = ?line(Meta),
|
||||
Ann = ?ann(Meta),
|
||||
{VarName, _, VS} = elixir_scope:build_var('_', US),
|
||||
|
||||
Var = {var, Line, VarName},
|
||||
Map = {map, Line, [{map_field_exact, Line, {atom, Line, '__struct__'}, {atom, Line, Name}}]},
|
||||
Var = {var, Ann, VarName},
|
||||
Map = {map, Ann, [{map_field_exact, Ann, {atom, Ann, '__struct__'}, {atom, Ann, Name}}]},
|
||||
|
||||
Match = {match, Line, Var, Map},
|
||||
Error = {tuple, Line, [{atom, Line, badstruct}, {atom, Line, Name}, Var]},
|
||||
Match = {match, Ann, Var, Map},
|
||||
Error = {tuple, Ann, [{atom, Ann, badstruct}, {atom, Ann, Name}, Var]},
|
||||
|
||||
{TMap, TS} = translate_map(MapMeta, Assocs, Var, VS),
|
||||
|
||||
{{'case', Line, TUpdate, [
|
||||
{clause, Line, [Match], [], [TMap]},
|
||||
{clause, Line, [Var], [], [elixir_utils:erl_call(Line, erlang, error, [Error])]}
|
||||
{{'case', Ann, TUpdate, [
|
||||
{clause, Ann, [Match], [], [TMap]},
|
||||
{clause, Ann, [Var], [], [elixir_utils:erl_call(Ann, erlang, error, [Error])]}
|
||||
]}, TS};
|
||||
S#elixir_scope.context == match ->
|
||||
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
|
||||
@@ -151,15 +151,15 @@ wait_for_struct(Module) ->
|
||||
translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
|
||||
{Op, KeyFun, ValFun} = extract_key_val_op(TUpdate, S),
|
||||
|
||||
Line = ?line(Meta),
|
||||
Ann = ?ann(Meta),
|
||||
|
||||
{TArgs, SA} = lists:mapfoldl(fun({Key, Value}, Acc) ->
|
||||
{TKey, Acc1} = KeyFun(Key, Acc),
|
||||
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
|
||||
{{Op, ?line(Meta), TKey, TValue}, Acc2}
|
||||
{{Op, ?ann(Meta), TKey, TValue}, Acc2}
|
||||
end, S, Assocs),
|
||||
|
||||
build_map(Line, TUpdate, TArgs, SA).
|
||||
build_map(Ann, TUpdate, TArgs, SA).
|
||||
|
||||
extract_assoc_update([{'|', _Meta, [Update, Args]}], S) ->
|
||||
{TArg, SA} = elixir_translator:translate_arg(Update, S, S),
|
||||
@@ -177,8 +177,8 @@ extract_key_val_op(TUpdate, S) ->
|
||||
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, KS) end,
|
||||
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end}.
|
||||
|
||||
build_map(Line, nil, TArgs, SA) -> {{map, Line, TArgs}, SA};
|
||||
build_map(Line, TUpdate, TArgs, SA) -> {{map, Line, TUpdate, TArgs}, SA}.
|
||||
build_map(Ann, nil, TArgs, SA) -> {{map, Ann, TArgs}, SA};
|
||||
build_map(Ann, TUpdate, TArgs, SA) -> {{map, Ann, TUpdate, TArgs}, SA}.
|
||||
|
||||
assert_struct_keys(Meta, Name, Struct, Assocs, S) ->
|
||||
[begin
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user