Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9033b217d9 | ||
|
|
1d9548fd28 | ||
|
|
9a6c195880 | ||
|
|
3eb938a0ba | ||
|
|
66f1e74e0c | ||
|
|
7c382b94af | ||
|
|
9297a9167e | ||
|
|
0410a1cec9 | ||
|
|
b545a1e491 | ||
|
|
c08b5b11c6 | ||
|
|
8939b39fc2 | ||
|
|
e12a0c561e | ||
|
|
61c3e82123 | ||
|
|
d43b41695d | ||
|
|
fdca8b2657 | ||
|
|
dec06d8c82 | ||
|
|
11c84cbd2e | ||
|
|
67501bcddc | ||
|
|
1f05e7ce73 | ||
|
|
02c279af12 | ||
|
|
2c2fb820ab | ||
|
|
1ad1d6d6b0 | ||
|
|
83d8161959 | ||
|
|
b87ea61713 | ||
|
|
c355ab6482 | ||
|
|
f0ed2d48ce | ||
|
|
d23a46fa9f | ||
|
|
6c0cbbd73e | ||
|
|
d58ef57e13 | ||
|
|
1a9493534e | ||
|
|
45fe1cb084 | ||
|
|
3f0e7c5613 | ||
|
|
49e76833d6 | ||
|
|
7963e44a5e | ||
|
|
3ba5e44dae | ||
|
|
1d74ddac78 | ||
|
|
c1e3d4f976 | ||
|
|
b4fc2b35fc | ||
|
|
18a5d8affb | ||
|
|
cd80994a70 | ||
|
|
e02d7bf913 | ||
|
|
fe5ff41514 | ||
|
|
e9887a760e | ||
|
|
0559fe5927 | ||
|
|
1a05bff45d | ||
|
|
4d6670342b | ||
|
|
193e85882e | ||
|
|
a58c007e0c | ||
|
|
ce6f4e6d15 | ||
|
|
321096665a | ||
|
|
b93aae9f86 | ||
|
|
a503069479 | ||
|
|
120ea0fc8a | ||
|
|
d725c66d93 | ||
|
|
fab917dba3 | ||
|
|
23fb92f444 | ||
|
|
db61109495 | ||
|
|
c4e8ad0d65 | ||
|
|
ce77fa54b9 | ||
|
|
eee921bf7b | ||
|
|
73c6a0be8b | ||
|
|
7644bd87a5 | ||
|
|
9e2c5a29a4 | ||
|
|
09498422d0 | ||
|
|
a697bab7f2 | ||
|
|
36e81d587e | ||
|
|
17a0abd57a | ||
|
|
e6cfc20556 | ||
|
|
906f03102b | ||
|
|
6a594e2bba | ||
|
|
10dd8f843b | ||
|
|
53ecea8b03 | ||
|
|
efba8d8bfa | ||
|
|
5a201065dc | ||
|
|
307675284c | ||
|
|
38a9513111 | ||
|
|
578f798cc1 | ||
|
|
efbdf4b9b0 | ||
|
|
9679bed4d7 | ||
|
|
b6c0045fc2 | ||
|
|
10612ddf22 | ||
|
|
cbcbbcf876 | ||
|
|
95b81c2a13 | ||
|
|
a525f71e13 | ||
|
|
750d2a3f7a | ||
|
|
61e9fafb73 | ||
|
|
aa48f6a881 | ||
|
|
b5e2d45b7d | ||
|
|
29f2eb7dab | ||
|
|
a3754e3841 | ||
|
|
337c040da8 | ||
|
|
b4d465e2ed | ||
|
|
0bb3d1c6bd | ||
|
|
86476615ef | ||
|
|
860b3cc5a7 | ||
|
|
bb5e6f65f5 | ||
|
|
95127516f7 | ||
|
|
ef0c495f16 | ||
|
|
2472bb79ab | ||
|
|
04b9174d75 | ||
|
|
d31a58f0d9 | ||
|
|
f97ab2649c | ||
|
|
d969d42784 | ||
|
|
17c4c4d993 | ||
|
|
0f7a968389 | ||
|
|
272ec8ca47 | ||
|
|
a0e8e1c0fb | ||
|
|
b477dba34f | ||
|
|
8d524ef971 | ||
|
|
83cb5152fd | ||
|
|
3c100575a3 | ||
|
|
d1b6161f2e | ||
|
|
785781d089 | ||
|
|
b2ac4c9394 | ||
|
|
50ac792747 | ||
|
|
7d433e80e5 | ||
|
|
f9cb812bc1 | ||
|
|
f8711f2e36 | ||
|
|
0251f564d9 | ||
|
|
e4acc08cf5 | ||
|
|
2ab5d25787 | ||
|
|
acde8078f8 | ||
|
|
487ac9c1c4 | ||
|
|
f0f65f8bec | ||
|
|
23943b6180 | ||
|
|
d9125b0a81 | ||
|
|
d1d46db907 | ||
|
|
02056d1030 | ||
|
|
14a3686a3f | ||
|
|
c04b182c6c | ||
|
|
f5462d98e0 | ||
|
|
75d0c07b46 | ||
|
|
1de8dcb695 | ||
|
|
0e167bef27 | ||
|
|
34df817638 | ||
|
|
311a5790d6 | ||
|
|
b4c75f97b4 | ||
|
|
70692e6618 | ||
|
|
529d584262 | ||
|
|
c0fb275d5b | ||
|
|
24f46be144 | ||
|
|
0fe3da9dea | ||
|
|
03d0ad3f03 | ||
|
|
a87f43701f | ||
|
|
dce3a3a2a5 | ||
|
|
7b20084b28 | ||
|
|
542808102f | ||
|
|
fd18b6a41f | ||
|
|
4c9c95f80f | ||
|
|
89460154bb | ||
|
|
b026e8877a | ||
|
|
597381b2e0 | ||
|
|
df70729b14 | ||
|
|
61b116c1a5 | ||
|
|
d37a10bbd0 | ||
|
|
5d75c5c861 | ||
|
|
db4089cc71 | ||
|
|
f9f4f14edf | ||
|
|
58946ce879 | ||
|
|
de1d5ff793 | ||
|
|
a4be8fb260 | ||
|
|
031a27f5fa | ||
|
|
f3c1931cac | ||
|
|
2e48d81c20 | ||
|
|
e7ef3fc95d | ||
|
|
2d01aa1c6a | ||
|
|
ae07124550 | ||
|
|
1f744f4dac | ||
|
|
ad88ac642f | ||
|
|
f7956e9d80 | ||
|
|
da6892aa96 | ||
|
|
7a3bd20804 | ||
|
|
f7015166dc | ||
|
|
c6f346a355 | ||
|
|
911a3ba97f | ||
|
|
a1668a6b28 | ||
|
|
a4cb74a83f | ||
|
|
bbf9552186 | ||
|
|
e621b5828e | ||
|
|
68e282c50a | ||
|
|
403a50fbcd | ||
|
|
8e98cd4f41 | ||
|
|
499c385000 | ||
|
|
aecafc099d | ||
|
|
785c7d700a | ||
|
|
cecc8a0f95 | ||
|
|
52ff7e9686 | ||
|
|
e2eb2cfdb1 | ||
|
|
a5b4c1c04d | ||
|
|
e38f960d7e | ||
|
|
9e74ee686e | ||
|
|
e068625d7d | ||
|
|
00e8446a58 | ||
|
|
2aecfede5b | ||
|
|
9eebefb976 | ||
|
|
b709cba371 | ||
|
|
1d919f59e3 | ||
|
|
0e8d3f9208 | ||
|
|
8931f34582 | ||
|
|
843706e36e | ||
|
|
172bfdb128 | ||
|
|
2c741ec6ae | ||
|
|
12a457829f | ||
|
|
0ff6850237 | ||
|
|
63f6088476 | ||
|
|
206f6413f8 | ||
|
|
a2c0424b37 | ||
|
|
6ea7d3cef6 | ||
|
|
2cafd8c060 | ||
|
|
7271dfebfb | ||
|
|
2eff3590c1 | ||
|
|
b578f45060 | ||
|
|
a34ccfe129 | ||
|
|
8f4442f2df | ||
|
|
598e42e00b | ||
|
|
b93912b9dc | ||
|
|
71eca8a5d6 | ||
|
|
f0bb0e1f7f | ||
|
|
2cb0639208 | ||
|
|
081e3b36ec | ||
|
|
d521d7927f | ||
|
|
7313d26720 | ||
|
|
577a07d47f | ||
|
|
f6093d40d7 | ||
|
|
1dd034d4aa | ||
|
|
2417b196a4 | ||
|
|
50bb712e22 | ||
|
|
7b0747c907 | ||
|
|
b878e414de | ||
|
|
c447868e0f | ||
|
|
681d5aa9c4 | ||
|
|
0263dcc621 | ||
|
|
1ae24c5c5d | ||
|
|
6c02ebe4af | ||
|
|
a08e44c5a9 | ||
|
|
08f30098db | ||
|
|
5dbd91e4fa | ||
|
|
5b2eb54794 | ||
|
|
2c17007693 | ||
|
|
2770c32f3d | ||
|
|
8c8f6d8346 | ||
|
|
3950130e73 | ||
|
|
c8bcd89c7c | ||
|
|
7f7aaa115f | ||
|
|
53aeb26407 | ||
|
|
bba0d12510 | ||
|
|
035d8227a5 | ||
|
|
003a4da87e | ||
|
|
1977631b7f | ||
|
|
c9a0ec000a | ||
|
|
ba842ba113 | ||
|
|
6304919d0d | ||
|
|
d2d27e96e5 | ||
|
|
fc6f7c532c | ||
|
|
1bee85e518 | ||
|
|
66b211987d | ||
|
|
15fdae4de6 | ||
|
|
5b4497fa6b | ||
|
|
612e124ec9 | ||
|
|
abd2b35738 | ||
|
|
2afb875099 | ||
|
|
88507f65f8 | ||
|
|
275d1f5494 | ||
|
|
e6420c8d13 | ||
|
|
60bcf1cadb | ||
|
|
d86ca5826c | ||
|
|
2af00f4c33 | ||
|
|
0fe86e77c1 | ||
|
|
1ca451f779 | ||
|
|
ebe0c8cf8e | ||
|
|
de9feb26d5 | ||
|
|
b8528f6c8e | ||
|
|
c9fbbac391 | ||
|
|
98e1b4ccf8 | ||
|
|
ae19491620 | ||
|
|
64f3760b19 | ||
|
|
0b38bc050f | ||
|
|
9431338654 | ||
|
|
88be750a32 | ||
|
|
032c8b8291 | ||
|
|
2d90d8ac60 | ||
|
|
a8b2cb99ee | ||
|
|
68274311fb | ||
|
|
88f84f80b5 | ||
|
|
2bf898e8fe | ||
|
|
d45f451943 | ||
|
|
c0a075724f | ||
|
|
f1a39375c9 | ||
|
|
b9c1b22163 | ||
|
|
be75e3be17 | ||
|
|
9793caa1ef | ||
|
|
0792fe2c52 | ||
|
|
4408aa3450 | ||
|
|
e4ba8d5d21 | ||
|
|
689f14a867 | ||
|
|
aa5b414e8e | ||
|
|
07467a733b | ||
|
|
74391b793c | ||
|
|
f0b8bc1756 | ||
|
|
3e54b95bf2 | ||
|
|
313d26cb3b | ||
|
|
44b85be014 | ||
|
|
e344b1f9d8 | ||
|
|
f5f4205969 | ||
|
|
538c717ded | ||
|
|
8dd81e661a | ||
|
|
26b61b98f9 | ||
|
|
ab9450d92f | ||
|
|
a4a23b9a93 | ||
|
|
2f83ea5a7f | ||
|
|
7940d223ec | ||
|
|
c6fa951520 | ||
|
|
e5c8194a70 | ||
|
|
4a7e3dd11f | ||
|
|
638fccb79d | ||
|
|
e9d84ec43b | ||
|
|
bb35b9afb1 | ||
|
|
1496c04592 | ||
|
|
3e9725a5a8 | ||
|
|
1565ec7658 | ||
|
|
f1aa2f01c7 | ||
|
|
7284542116 | ||
|
|
4f17e8fd1c | ||
|
|
f536507b9d | ||
|
|
592300a0f0 | ||
|
|
28b1bd16de | ||
|
|
19fdd84f80 | ||
|
|
631e2a051a | ||
|
|
df0f7b5f95 | ||
|
|
a0dfc4160e | ||
|
|
ee8331023e | ||
|
|
a2ee71f5ab | ||
|
|
60163194e2 | ||
|
|
ba37682300 | ||
|
|
15265b9352 | ||
|
|
0a929310de | ||
|
|
a8670c1cfa | ||
|
|
ebd18d45fa | ||
|
|
ccbfec611a | ||
|
|
98a0999c2e | ||
|
|
e504142d3a | ||
|
|
c04e13169f | ||
|
|
96a4bff67b | ||
|
|
46de5ea462 | ||
|
|
22257b8342 | ||
|
|
a0be9fe272 | ||
|
|
1fd5a2a38e | ||
|
|
13aec18bd5 | ||
|
|
a85938d092 | ||
|
|
0d4af57abe | ||
|
|
e00db53112 | ||
|
|
b941618a10 | ||
|
|
d895c762a1 | ||
|
|
427c4b01c9 | ||
|
|
9d5a728cb8 | ||
|
|
1ed99d4ac7 | ||
|
|
ceb17fac7d | ||
|
|
c638f45542 | ||
|
|
c09905d537 | ||
|
|
117a428030 | ||
|
|
2ada7c40d7 | ||
|
|
cd162bf3d6 | ||
|
|
e6e71675ac | ||
|
|
c54e0caf6f | ||
|
|
b64552780e | ||
|
|
add8c66e82 | ||
|
|
aecbd2c16a | ||
|
|
32a649c1cd | ||
|
|
a1e657cb75 | ||
|
|
6bb4c4de03 | ||
|
|
ea5b56d434 | ||
|
|
a4082748cf | ||
|
|
4d763846c3 | ||
|
|
d56cc88f5f | ||
|
|
5e27b7b72b | ||
|
|
260d6d16ab | ||
|
|
1a82f126a4 | ||
|
|
3cf3eee2ea | ||
|
|
9e8a41b2d3 | ||
|
|
a54d9212b8 | ||
|
|
ee33608f75 | ||
|
|
6e91275782 | ||
|
|
c6f27541c1 | ||
|
|
a2662a4b51 | ||
|
|
1d29262949 | ||
|
|
1d655fa8f2 | ||
|
|
4bec785f4c | ||
|
|
e3273f731f | ||
|
|
a8564a7045 | ||
|
|
e2bac56556 | ||
|
|
a38e6b7a0d | ||
|
|
72199ca71d | ||
|
|
9420945f59 | ||
|
|
4ca1736f86 | ||
|
|
fd85ca443e | ||
|
|
187e86749e | ||
|
|
e04a7aef6a | ||
|
|
0b1c8c92c0 | ||
|
|
bf270d144f | ||
|
|
2c009d1ec3 | ||
|
|
08555f545c | ||
|
|
65d3af694b | ||
|
|
8aa43eaedd | ||
|
|
334c732691 | ||
|
|
24587110ad | ||
|
|
b98421fe51 | ||
|
|
752e3f8bc3 | ||
|
|
6c8e70b1f8 | ||
|
|
c6fbfd2a67 | ||
|
|
56ea6e52e4 | ||
|
|
4726e0af91 | ||
|
|
ee82679908 | ||
|
|
aedd5a938f | ||
|
|
2d9c3fb6f0 | ||
|
|
6f07f4af22 | ||
|
|
656bd640e6 | ||
|
|
bc2cb15a96 | ||
|
|
dbf205b311 | ||
|
|
0e0bb1c2a7 | ||
|
|
736406a9b3 | ||
|
|
886917753e | ||
|
|
f29889da99 | ||
|
|
aee33eac6d | ||
|
|
d8428ba1e6 | ||
|
|
1f725b0d2c | ||
|
|
38f895cc13 | ||
|
|
3fc07eff8e | ||
|
|
aa244c30fc | ||
|
|
f8647dae3e | ||
|
|
39fa2878ce | ||
|
|
5b5b95eb11 | ||
|
|
67caa3ebb3 | ||
|
|
d09cd173bf | ||
|
|
79fb40ab6a | ||
|
|
ae2cb6bc43 | ||
|
|
413b815ecc | ||
|
|
df89b5022e | ||
|
|
90729655ee | ||
|
|
171e9b3659 | ||
|
|
3e256fcc1e | ||
|
|
29ebbeda93 | ||
|
|
214d056911 | ||
|
|
9f065a77ca | ||
|
|
e70ac9768e | ||
|
|
ec6c98ef47 | ||
|
|
7d0f92e12a | ||
|
|
8bf6193ccc | ||
|
|
50f5dc76fb | ||
|
|
b9000999c9 | ||
|
|
52be06c314 | ||
|
|
3da560f10c | ||
|
|
f0f25300a0 | ||
|
|
c8a3a619af | ||
|
|
25f64051ba | ||
|
|
5efa6bb1c4 | ||
|
|
cb48f90f28 | ||
|
|
3ba93b1186 | ||
|
|
7babcd0ed2 | ||
|
|
b6c1f2b2e1 | ||
|
|
5f853f8e71 | ||
|
|
6cccf3ac22 | ||
|
|
5b9b186d4b | ||
|
|
57898bee03 | ||
|
|
6a6ba9f50d | ||
|
|
1e3135514f | ||
|
|
2f2a52a648 | ||
|
|
89fd8f305f | ||
|
|
353c043d8c | ||
|
|
54a40efeec | ||
|
|
f781795032 | ||
|
|
54ffb5768d | ||
|
|
8fc068821f | ||
|
|
1f91fd351e | ||
|
|
ab4399323f | ||
|
|
3b34a57bd2 | ||
|
|
62a469dc23 | ||
|
|
8b0c4127ef | ||
|
|
dc4b7c89ea | ||
|
|
b5febd991c | ||
|
|
f1323ade7c | ||
|
|
89561c321d | ||
|
|
d1b8657933 | ||
|
|
89d9cc89ca | ||
|
|
7b0cbb1632 | ||
|
|
3dc0e87c33 | ||
|
|
a19613cf19 | ||
|
|
a8e4fcdb6c | ||
|
|
a70ca83ba2 | ||
|
|
11c857a9a2 | ||
|
|
84b1f6546b | ||
|
|
c8c1757fdd | ||
|
|
9a162b5076 | ||
|
|
92d2f25048 | ||
|
|
a2ddb6abbb | ||
|
|
ac629c875b | ||
|
|
ab8ba93d1d | ||
|
|
9d62e674d7 | ||
|
|
58ca3401e4 | ||
|
|
a5926616a6 | ||
|
|
59910ae4bf | ||
|
|
69b47483c1 | ||
|
|
284e75d031 | ||
|
|
c1d63e4639 | ||
|
|
212b204af8 | ||
|
|
90f83e1b3d | ||
|
|
562af52d07 | ||
|
|
ed79d60cef | ||
|
|
42d378142b | ||
|
|
aeefd59b15 | ||
|
|
6db5091378 | ||
|
|
4e7ba9d598 | ||
|
|
60b66ac669 | ||
|
|
1fda82d9c2 | ||
|
|
55dbd617a6 | ||
|
|
841a4e0195 | ||
|
|
5d60728d4b | ||
|
|
9717a63604 | ||
|
|
fae20670ed | ||
|
|
6874eb7ce8 | ||
|
|
404e95b7ac | ||
|
|
30a5914a92 | ||
|
|
f97a36ff98 | ||
|
|
e68f617e2a | ||
|
|
3056e95ded | ||
|
|
9ba77e1ce6 | ||
|
|
84010d4467 | ||
|
|
a91aa70103 | ||
|
|
301c7fd122 | ||
|
|
8809ab2f5e | ||
|
|
71e441dcc2 | ||
|
|
f11197fd04 | ||
|
|
7ac771da67 | ||
|
|
a47751f4de | ||
|
|
c91217860c | ||
|
|
f69e9df983 | ||
|
|
2ee1d5daef | ||
|
|
d3dba63659 | ||
|
|
c016d74379 | ||
|
|
a6d887ee09 | ||
|
|
aa017a4dcb | ||
|
|
b7c7d7e72c | ||
|
|
d164a9b93f | ||
|
|
0105cf538e | ||
|
|
957e389fe7 | ||
|
|
88d4d38afa | ||
|
|
8b4b985894 | ||
|
|
986eb8bdb1 | ||
|
|
052eac333c | ||
|
|
acae645db1 | ||
|
|
7cb5f6ff3c | ||
|
|
0fe940a621 | ||
|
|
81ed03ffb2 | ||
|
|
3947f7455a | ||
|
|
406a2e6755 | ||
|
|
0b3fcb254a | ||
|
|
054843505a | ||
|
|
92016cd80e | ||
|
|
0c06e72f0c | ||
|
|
a722a4b33c | ||
|
|
4afb8a516c | ||
|
|
e9fc50bc6d | ||
|
|
0a5a980ab7 | ||
|
|
30505b7758 | ||
|
|
9f867f2ec8 | ||
|
|
5e2020804a | ||
|
|
b5cea26e57 | ||
|
|
03e6d3a5a2 | ||
|
|
89513f9d15 | ||
|
|
f20ef46e90 | ||
|
|
d7ebc46a2d | ||
|
|
bd6d4bcd6f | ||
|
|
4f8862cedd | ||
|
|
1afc5ec71b | ||
|
|
925ff6050a | ||
|
|
6ce97090f3 | ||
|
|
6538f33825 | ||
|
|
a1e4a2741f | ||
|
|
fe57a821d8 | ||
|
|
c42fe88dcc | ||
|
|
28d7fc35a6 | ||
|
|
9fa279897b | ||
|
|
6465a3a4af | ||
|
|
1582df8d70 | ||
|
|
8d9aec5a55 | ||
|
|
7f4df95922 | ||
|
|
98a5affa4a | ||
|
|
b720559d56 | ||
|
|
cd5cc91cd4 | ||
|
|
93d92d1f36 | ||
|
|
ce3674dd07 | ||
|
|
e4cfb62a74 | ||
|
|
bb51701614 | ||
|
|
6ffb248f03 | ||
|
|
50af8058ef | ||
|
|
b1d9e147b3 | ||
|
|
7ea91d2d68 | ||
|
|
c7640d3cf4 | ||
|
|
399240295b | ||
|
|
b965d8eb5d | ||
|
|
380bf1e279 | ||
|
|
6272b60d36 | ||
|
|
0c7699f21c | ||
|
|
1086a8455f | ||
|
|
d8e7578e41 | ||
|
|
2d0fbcc23c | ||
|
|
21d5d43ac5 | ||
|
|
027f90380b | ||
|
|
1a68f63525 | ||
|
|
798c53a4cb | ||
|
|
e3a89050f2 | ||
|
|
f176cb55c1 | ||
|
|
97f2e8744b | ||
|
|
c513459c10 | ||
|
|
fd6f1d47be | ||
|
|
6b8b5bfd01 | ||
|
|
e6e96165aa | ||
|
|
42875c0b72 | ||
|
|
b35e2cf82a | ||
|
|
be320880b2 | ||
|
|
fe1712d642 | ||
|
|
63d815912e | ||
|
|
cd4dc75c93 | ||
|
|
058b157b36 | ||
|
|
19c9941e7f | ||
|
|
7895ca0821 | ||
|
|
2b20bb424f | ||
|
|
82535feb90 | ||
|
|
81f007bdd7 | ||
|
|
0b35996d5e | ||
|
|
fc5cf506ae | ||
|
|
c4582e0041 | ||
|
|
c80d215674 | ||
|
|
3ee9d5d0df | ||
|
|
d86ba0e1c7 | ||
|
|
c295ee50de | ||
|
|
54c25a55fe | ||
|
|
583a4ec5f6 | ||
|
|
69b78dad51 | ||
|
|
0685e0315a | ||
|
|
5e429001d9 | ||
|
|
707b810f1b | ||
|
|
e51cfc142d | ||
|
|
b259e793bb | ||
|
|
32738a97d9 | ||
|
|
45c6fbaced | ||
|
|
bba4f1bd1b | ||
|
|
ade2596e92 | ||
|
|
0e804a2b5c | ||
|
|
33ab1679f3 | ||
|
|
f2b12633f3 | ||
|
|
5a4b2769ee | ||
|
|
2439bae409 | ||
|
|
6ed7e40d03 | ||
|
|
2fc76fe1b0 | ||
|
|
e541a56130 | ||
|
|
6378750b12 | ||
|
|
fc25f60718 | ||
|
|
3b351e7650 | ||
|
|
999a535603 | ||
|
|
a726dd812b | ||
|
|
58d54afac6 | ||
|
|
001d776176 | ||
|
|
4bcd25fb4d | ||
|
|
f0fa9e66a6 | ||
|
|
12f9bf09b0 | ||
|
|
bf8f5a995f | ||
|
|
d3a3feace4 | ||
|
|
30331b2bea | ||
|
|
f5bf9d9659 | ||
|
|
2865bd8653 | ||
|
|
4513b6965f | ||
|
|
b47e8c61d7 | ||
|
|
40bf079566 | ||
|
|
2eaf854e79 | ||
|
|
7c12cf029c | ||
|
|
597d90b326 | ||
|
|
5dcad26b79 | ||
|
|
f8c2d5cb4f | ||
|
|
b0f432b2f8 | ||
|
|
651a4bad67 | ||
|
|
f52e350adc | ||
|
|
35e8019447 | ||
|
|
a6fab4a111 | ||
|
|
64d62d2472 | ||
|
|
10b322aa48 | ||
|
|
981955301d | ||
|
|
ac425f04ef | ||
|
|
1dda1563da | ||
|
|
0e71b1e711 | ||
|
|
841f6f2387 | ||
|
|
2eb7fb97de | ||
|
|
e781983e6b | ||
|
|
7d17a12783 | ||
|
|
d888057689 | ||
|
|
76d6e05399 | ||
|
|
1bd0ca6dea | ||
|
|
8e1f7fddca | ||
|
|
abda47903b | ||
|
|
030e72c9c6 | ||
|
|
88dbad9650 | ||
|
|
07263b8533 | ||
|
|
f67741b6dc | ||
|
|
4724ff12e2 | ||
|
|
f7c7ff46b4 | ||
|
|
c4868f8549 | ||
|
|
85951052ca | ||
|
|
28bd15b76c | ||
|
|
1fdb715967 | ||
|
|
0279547232 | ||
|
|
7fabf32d9e | ||
|
|
4cbf0eb2ac | ||
|
|
071b0830f8 | ||
|
|
39cd4a6594 | ||
|
|
0d2332fe5e | ||
|
|
c807fa6ec5 | ||
|
|
4a10806d9a | ||
|
|
57031f8cb0 | ||
|
|
b292486ac9 | ||
|
|
595f9bb0b1 | ||
|
|
ebe3397ad9 | ||
|
|
bc12b76bcc | ||
|
|
d8df85e99c | ||
|
|
80b9879d0a | ||
|
|
d54f9ec931 | ||
|
|
d2258acbb3 | ||
|
|
6c3a4bd019 | ||
|
|
7903211546 | ||
|
|
6b87fcf903 | ||
|
|
07d0d1b275 | ||
|
|
95361172a0 | ||
|
|
a0f3086167 | ||
|
|
65a59b41e5 | ||
|
|
78dc02fb81 | ||
|
|
dc577b8bfa | ||
|
|
ed9862ea91 | ||
|
|
f710c61179 | ||
|
|
d86cf14f0d | ||
|
|
bdfd5817dc | ||
|
|
3b2ece4ddc | ||
|
|
3452f364aa | ||
|
|
dad77a4d4b | ||
|
|
c8b925c581 | ||
|
|
24297c87e6 | ||
|
|
0c589eff04 | ||
|
|
3a3684ca78 | ||
|
|
6e39366c40 | ||
|
|
fd0ccb7741 | ||
|
|
81f151245e | ||
|
|
acfdd5b59e | ||
|
|
588800c495 | ||
|
|
cfd157ab7d | ||
|
|
377eb6126d | ||
|
|
2cad4c96bd | ||
|
|
2453501c83 | ||
|
|
dcc3c6290e | ||
|
|
54240e1b4f | ||
|
|
6639f91ba2 | ||
|
|
15bf309760 | ||
|
|
1de0302ecb | ||
|
|
871cbda3f8 | ||
|
|
c4ea85d7e5 | ||
|
|
a8f3125774 | ||
|
|
c564af180b | ||
|
|
3ebe1ed728 | ||
|
|
6b030b2978 | ||
|
|
9f2fa0f7af | ||
|
|
da06e35443 | ||
|
|
da4d047a37 | ||
|
|
782314abff | ||
|
|
94d53f052b | ||
|
|
9ceebf96db | ||
|
|
8611bb7322 | ||
|
|
ba2e1c95ef | ||
|
|
5979b5ca75 | ||
|
|
f144cae300 | ||
|
|
8bcf473b68 | ||
|
|
b10fdfc7b8 | ||
|
|
95eb1c0978 | ||
|
|
7a5cb8d374 | ||
|
|
f23b133d29 | ||
|
|
0bdee49f7f |
+2
-3
@@ -1,6 +1,6 @@
|
||||
/.eunit
|
||||
/.release
|
||||
/docs
|
||||
/doc
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
@@ -10,5 +10,4 @@
|
||||
/lib/elixir/test/ebin
|
||||
/rel/elixir
|
||||
erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.elixir.plt
|
||||
|
||||
@@ -7,3 +7,5 @@ notifications:
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- 17.0
|
||||
- 17.1
|
||||
- 17.3
|
||||
|
||||
+295
-7
@@ -1,18 +1,306 @@
|
||||
# Changelog
|
||||
|
||||
## v0.14.3-dev
|
||||
## v1.0.5 (2015-06-29)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Support 18.0
|
||||
* [IEx] Rely only on loaded applications for autocompletion on IEx
|
||||
* [Record] Expand attributes and macros in record extractor
|
||||
* [String] `Optimize String.rstrip/1`
|
||||
* [String] `Optimize String.downcase/1`
|
||||
* [String] `Optimize String.upcase/1`
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` were made left associative in operator table
|
||||
* [Kernel] `<`, `>`, `<=`, `>=` were given higher precedence than comparison ones (`==`, `!=`, etc) in operator table
|
||||
* [EEx] Ensure blocks do not clobber EEx buffers
|
||||
* [Enum] Ensure `Enum.take/2` does not consume one extra item when halting on the last emittable item
|
||||
* [ExUnit] Fix `StringIO` processes leakage in the `ExUnit.CaptureIO` when there are errors inside the `capture_io` block
|
||||
* [Float] Avoid rounding errors on `Float.parse/1`
|
||||
* [GenEvent] Fix `GenEvent` detection of modules that aren't loaded
|
||||
* [IO] Read 4K blocks instead of lines in `IO.binread/2`. This fixes a bug where CRLF were being ignored and is also going to improve performance
|
||||
* [Logger] Handle `:undefined` arity in `Logger.Translator` (we get :undefined when a temporary worker of a `simple_one_for_one` supervisor crashes)
|
||||
* [Mix] Ensure config is escaped before being injected into escripts. This fixes a bug where escripts failed to build when containing values like maps in config files
|
||||
* [Mix] Ensure we properly underscore acronyms followed by paths, for example, `HTTP.Foo`
|
||||
* [Stream] Ensure `Stream.flat_map/2` does not consume more items than necessary when piped to another `Stream.flat_map/2` that halts in the inner stream
|
||||
* [Version] Fix `to_string` for versions with numeric pre releases
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
## v1.0.4 (2015-04-07)
|
||||
|
||||
* Deprecations
|
||||
* Enhancements
|
||||
* [Elixir] Support Erlang 17.5 and 18.0-rc
|
||||
* [Mix] Support "--search PATTERN" in "mix help"
|
||||
* [Mix] Support `:start_permanent` that starts the application as permanent
|
||||
* [Mix] Support `:build_embedded` that compile protocols, avoid symlinks and ensure protocols are loaded on boot
|
||||
* [Mix] Make "mix app.start" public and support "--permanent" and "--temporary" options
|
||||
* [URI] Speed decoding operations
|
||||
|
||||
* Bug fixes
|
||||
* [Elixir] Produce clearer error messages for syntax errors before a sigil
|
||||
* [Elixir] Return value when matching on `_` in the last line of a block
|
||||
* [Elixir] Ensure changes in child deps forces the parent to recompile
|
||||
* [Kernel] Fix a bug where `=~/2` returned false when matching on an empty string. The current behaviour was changed to match the behaviour of `String.contains?/2`
|
||||
* [Mix] Store relative paths in erlang manifests
|
||||
* [Mix] Fix httpc proxy option profile for downloading hex and other safe packges
|
||||
* [Mix] Fix missing behaviour warning when compiling Erlang modules
|
||||
|
||||
## v1.0.3 (2015-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Elixir] Do not show exported vars warning
|
||||
* [Elixir] Improve error messages when using invalid expressions inside maps
|
||||
* [Elixir] Skip aliases and import warnings when code cannot compile
|
||||
* [IEx] Improve autocompletion for Erlang/Elixir based on loaded applications
|
||||
* [IO] Make `IO.ANSI.Docs` readable on white background
|
||||
* [Mix] Provide clearer error message when updating archives on Windows
|
||||
* [Mix] Don't go through hex.pm API server to install Hex
|
||||
|
||||
* Bug fixes
|
||||
* [Elixir] Ensure all Elixir applications work with code upgrades
|
||||
* [Elixir] Don't leave spurious processes and stale data when the parallel compiler fails
|
||||
* [Inspect] Keep original stacktrace on Inspect errors
|
||||
* [IO] Fix `IO.ANSI.Docs` handling of `*`, `**`, `_` where they would be activated in the middle of words
|
||||
* [Logger] Fix logging with `metadata[:function]`
|
||||
* [Mix] Don't show hex update message on mix `local.hex`
|
||||
* [Mix] Ensure tasks are properly underscored and camelized
|
||||
* [Mix] Ensure Mix does not use ANSI escapes on Windows unless ANSI is enabled
|
||||
* [Mix] Only shutdown Logger if the Mix application is being actually started
|
||||
* [Mix] Ensure priv/include are copied on Windows even if source did not change
|
||||
* [Mix] Ensure rebar dependencies work when user path has a space on Windows
|
||||
* [Regex] Fix `Regex.replace/4` failures when the number of substitutions in pattern is more than the number of matches and support `\g{...}` patterns
|
||||
* [String] Fix LFCR being treated as a grapheme instead of CRLF
|
||||
* [URI] Do more strict matching on URI schema
|
||||
|
||||
## v1.0.2 (2014-10-21)
|
||||
|
||||
* Bug fixes
|
||||
* [EEx] Remove invalid warning when attempting to inline code in EEx
|
||||
* [EEx] Unify syntax errors to always include `file:line:`
|
||||
* [IEx] Set null prompt function in dumb term mode
|
||||
* [Kernel] Do not define an ets table with the same name as the module as it clashes with user code
|
||||
* [Kernel] Fail compiling try expressions if they lack one of catch/after/rescue/else clauses
|
||||
* [Kernel] Improve error messages when parsing fails on aliases
|
||||
* [Kernel] Improve warnings coming from compile-time evaluation of arguments
|
||||
* [String] Fix `String.replace/4` typespec
|
||||
|
||||
## v1.0.1 (2014-10-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add MIX_ENV output to archive.build (as on escript.build)
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Fix Elixir CLI when running on Cygwin
|
||||
* [ExUnit] Include stacktraces in ExUnit timeouts
|
||||
* [Mix] Load dependencies before `deps.check` compiles dependencies
|
||||
* [Mix] Ensure `Mix.Config` is deep merged
|
||||
* [Mix] Change version requirement in new apps to `~> MAJOR.MINOR`
|
||||
* [Mix] Report correct location if `local.hex` fails and give higher priority to powershell install on Windows
|
||||
* [Mix] Ensure `--elixirc-paths` option for `compile.elixir` is a subset of the project `:elixirc_paths` configuration and it does not remove skipped entries from the manifest
|
||||
* [Mix] Ensure `compile.elixir` writes to manifest when files are removed
|
||||
* [Mix] Ensure `compile.elixir` purges and deletes modules before compiling
|
||||
* [Mix] Do not crash on malformed proxy env var
|
||||
* [Stream] Ensure `chunk/4` works correctly when halted
|
||||
* [System] Do not allow relative paths in `System.cmd/3` as documented
|
||||
* [System] Fix `:stderr_to_stdout` option in `System.cmd/3`
|
||||
|
||||
## v1.0.0 (2014-09-10)
|
||||
|
||||
* Enhancements
|
||||
* [Logger] Add `Logger.enable/1` and `Logger.disable/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] No longer support `{:swap_handler, ...}` as callback return value
|
||||
* [List] `List.unzip/2` was removed
|
||||
|
||||
## v1.0.0-rc2 (2014-09-07)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Print process mailbox on failing `assert_receive`
|
||||
* [ExUnit] Tag all doctests with the `:doctest` tag
|
||||
* [Kernel] Friendlier `unquote_splicing` error on inline quote
|
||||
* [Kernel] Add `<~`, `~>`, `<<~`, `~>>`, `<~>`, `<|>` as operators
|
||||
* [Mix] `mix deps.unlock` warns when given a missing dependency
|
||||
* [Mix] Fetch hex using HTTPS
|
||||
|
||||
* Bug fixes
|
||||
* [File] Ensure `File.cwd` generates lowercase drive letters on Windows
|
||||
* [GenEvent] Ensure the event manager does not crash on `:sys.replace_state/3`
|
||||
* [GenEvent] Do not consider swap handlers to be failures in a stream
|
||||
* [Logger] Fix `Logger.log/2` not to raise on Logger exit
|
||||
* [Logger] Recompute sync/async mode on Logger configuration
|
||||
* [System] Ensure `System.cwd` generates lowercase drive letters on Windows
|
||||
|
||||
* Deprecations
|
||||
* [GenEvent] The `:monitor` option in `add_handler/4` and `swap_handler/6` is deprecated in favor of `add_mon_handler/3` and `swap_mon_handler/5`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Supervisor] Make `max_restarts` default to 3 (closer to upcoming Erlang defaults)
|
||||
|
||||
## v1.0.0-rc1 (2014-08-30)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add timeouts to tests (which is configurable via tags)
|
||||
* [GenEvent] Add support for `GenEvent.ack_notify/2`
|
||||
* [GenEvent] Add support for monitored handlers to `GenEvent.add_handler/4` that removes the drawbacks in the previous linked handlers
|
||||
* [Logger] Allow any value that implements `String.Chars` to be logger
|
||||
* [Logger] Allow `Logger.log/3` timeout to be configured
|
||||
* [Mix] Add `--force` option to `mix local.rebar`
|
||||
* [Mix] Fix `--no-color`/`--color` option for `mix test`
|
||||
* [Mix] Add a `:language` option to improve the usability of Erlang projects using Mix
|
||||
* [Mix] Accept wildcards on `Mix.Config.import_config/1`
|
||||
* [Mix] Support protocol consolidation on escripts
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print the underlying type for `@opaque` in the `t` helper
|
||||
* [GenEvent] Do not allow a handler to be added more than once
|
||||
* [GenServer] Do not treat bad arguments as exits in `GenServer.call/2`
|
||||
* [GenServer] Do not deliver out of order messages on `GenServer.cast/2` on distributed mode
|
||||
* [Mix] Do not pre-compile a Mix project if an alias was found
|
||||
* [Mix] Properly handle compilation errors in the Erlang compiler
|
||||
* [Mix] Always try to compile project if task cannot be found
|
||||
* [Mix] Purge in memory modules before consolidating
|
||||
* [Mix] Always compile a project if a task cannot be found
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Deprecate `Collectable.empty/1` and `Enum.traverse/2`
|
||||
* [Integer] `odd?/1` and `even?/1` are deprecated in favor of `is_odd/1` and `is_even/1`
|
||||
* [Kernel] `nil?/1` is deprecated in favor of `is_nil/1`
|
||||
* [Kernel] `x.Alias` is deprecated in favor of an explicit `Module.concat/2`
|
||||
* [Record] `record?/1` and `record?/2` are deprecated in favor of `is_record/1` and `is_record/2`
|
||||
* [Stream] Returning `{item, acc} | nil` from `Stream.resource/2` is deprecated, instead return `{[item], acc} | {:halt, acc}` (similar to `Stream.transform/3`)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.cancel_stream/1` no longer is
|
||||
* [GenEvent] The `:mode` and `:duration` options are deprecated and no longer supported in `GenEvent.stream/2` (the mode is now a property of the sender)
|
||||
* [GenEvent] The `:linked` option is deprecated and no longer supported in `GenEvent.add_handler/4` (check monitored handlers instead)
|
||||
|
||||
## v0.15.1 (2014-08-10)
|
||||
|
||||
* Enhancements
|
||||
* [GenEvent] Support `:ack` mode for GenEvent streams
|
||||
* [Inspect] Support `:base` option in `inspect/2` to choose the base (`:binary`, `:octal`, `:decimal` or `:hex`) numbers a printed
|
||||
* [kernel] Print warnings when used ? with characters with escape codes
|
||||
* [Logger] Add SASL log forwarding option to Logger
|
||||
* [Logger] Add `$padlevel` to option Logger formatter
|
||||
* [Logger] Backends receive the exact handler value when added, allowing a key to be passed for configuration lookup
|
||||
* [Logger] Add `Logger.flush/0` to flush the Logger (useful for testing)
|
||||
* [Logger] Persist backends dynamically added or removed via `add_backend/2` and `remove_backend/2`
|
||||
* [Macro] Add `Macro.validate/1` to recursively check if a value is a valid quoted expression
|
||||
* [Mix] Load mix deps only when there is a need to use them (this improves the timing for the majority of tasks in a Mix project)
|
||||
* [Mix] Make the environment explicit on the success message generated after `escript.build` is invoked
|
||||
* [Mix] Load `config/config.exs` inside escripts
|
||||
* [Mix] Store and check Elixir version requirement for generated archives
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Fix shell bugs when running on Windows with Cygwin or MinGW
|
||||
* [Kernel] Modules compiled by Elixir now report the correct beam location when `:code.which/1` is invoked. If the bytecode is only available in memory, `:code.which/1` returns `:in_memory`
|
||||
* [Kernel] Do not expand args for unknown functions/macros
|
||||
* [Kernel] Ensure `defstruct`, `@attr` inside and friends raise a nice error messages when values cannot be properly escaped
|
||||
* [Kernel] Do not raise conflicts on imports used from inside quotes
|
||||
* [Logger] Metadata is now correctly merged on each `Logger.metadata/1` call
|
||||
* [Logger] Use the Logger PID on `:error_logger` wrapper to avoid race conditions on shutdown
|
||||
* [Macro] Ensure bitstrings work with `Macro.escape/1`
|
||||
* [Mix] Ensure aliases are invoked on umbrella recursive tasks
|
||||
* [Mix] Leave it up to the application to start the Logger after compilation
|
||||
* [Mix] Accept more forms of git versions (like "git version 1.9.3 (Apple Git-50)")
|
||||
* [Path] Do not normalize paths in `Path.join/2` as normalization is beyond the scope of such function
|
||||
* [URI] `to_string/1` now properly converts URI to strings when the schema is missing
|
||||
|
||||
* Deprecations
|
||||
* [Collectable] Having a function as collectable is deprecated
|
||||
* [Module] `Module.function/3` is deprecated, please use `:erlang.make_fun/3` instead
|
||||
|
||||
## v0.15.0 (2014-08-02)
|
||||
|
||||
* Enhancements
|
||||
* [Agent] Improve the Agent API to also accept functions that receive explicit module, function and arguments
|
||||
* [IEx] Support `--werl` call on Windows
|
||||
* [Logger] Add `Logger`
|
||||
* [Map] Add `Map.from_struct/1`
|
||||
* [Mix] Allow `--app` flag to be passed to `mix new`
|
||||
* [Mix] Support lowercase `http(s)_proxy` environment variables
|
||||
* [Mix] Allow `elixirc_paths` to also be given through the command line to `mix compile.elixir`
|
||||
* [String] `String.slice/2` and `String.slice/3` have been optimized
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Ensure functions in `Kernel.SpecialForms` and `IEx.Helpers` are also auto-completed
|
||||
* [IEx] Ensure remote shells can be started with `--remsh`
|
||||
* [Kernel] Correctly parse unary/binary operators regardless of number of spaces
|
||||
* [Kernel] Ensure private functions are not exported
|
||||
* [Protocol] Do not expose protocol convention on `assert_impl!/2`
|
||||
* [Regex] Do not consider include captures on `Regex.split/3` results
|
||||
* [Stream] Implement the `Inspect` protocol for Streams so we do not leak the Stream representation
|
||||
|
||||
* Deprecations
|
||||
* [IEx] IEx color configuration expects a list of atoms instead of a string with colors separated by comma
|
||||
* [Inspect] `Inspect.Algebra.surround_many/6` now expects Inspect.Opts instead of an integer limit
|
||||
* [Inspect] `Inspect.Algebra.pretty/2` is deprecated in favor of `Inspect.Algebra.format/2` that instead returns iodata. This function was used only by documentation examples and it is unlikely to affect actual code
|
||||
* [IO] `IO.ANSI.terminal?` is deprecated in favor of `IO.ANSI.enabled?`
|
||||
* [IO] `IO.ANSI.escape/2` and `IO.ANSI.escape_fragment/2` is deprecated in favor of `IO.ANSI.format/2` and `IO.ANSI.format_fragment/2`
|
||||
* [Kernel] Leading `0` for octals is deprecated in favor of `0o`
|
||||
* [Kernel] `0X` for hexadecimals is deprecated in favor of `0x`
|
||||
* [Kernel] `0B` for binaries is deprecated in favor of `0b`
|
||||
* [Mix] Mix color configuration expects a list of atoms instead of a string with colors separated by comma
|
||||
* [String] `\NNN`, `\NN` and `\N` for octals are deprecated inside string, sigils and chars in favor of hexadecimal entries with `\x`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `binding/1` and `binding/2` expecting a list were removed
|
||||
* [Regex] Do not consider include captures on `Regex.split/3` results
|
||||
|
||||
## v0.14.3 (2014-07-12)
|
||||
|
||||
* Enhancements
|
||||
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
|
||||
* [Enum] Add `Enum.sort_by/3`
|
||||
* [ExUnit] Match the line filter by proximity instead of exact match
|
||||
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
|
||||
* [IO] Add `IO.(bin)read(device, :all)`
|
||||
* [Kernel] Print a warning if a dangling `@doc` clause is found
|
||||
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
|
||||
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
|
||||
* [Mix] Issue warning and reset mtime for source files from the future
|
||||
* [Mix] Support task aliases in Mix
|
||||
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
|
||||
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
|
||||
* [String] Improve performance of `String.split/1`
|
||||
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
|
||||
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
|
||||
|
||||
* Bug fixes
|
||||
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
|
||||
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
|
||||
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
|
||||
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
|
||||
* [Kernel] Run command line and escripts in a process that does not trap exits
|
||||
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
|
||||
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
|
||||
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
|
||||
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
|
||||
* [Macro] `to_string` correctly displays sigils
|
||||
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
|
||||
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
|
||||
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
|
||||
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
|
||||
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
|
||||
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
|
||||
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
|
||||
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
|
||||
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
|
||||
* [System] Giving a char list to `System.find_executable/1` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Access] No longer fill in missing intermediate values with empty maps
|
||||
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
|
||||
* [Kernel] `defstruct` no longer automatically defines a type
|
||||
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
|
||||
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
|
||||
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
|
||||
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
|
||||
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
|
||||
|
||||
## v0.14.2 (2014-06-29)
|
||||
|
||||
@@ -28,7 +316,7 @@
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
||||
* [IEx] Remove delay when printing data from the an application start callback
|
||||
* [IEx] Remove delay when printing data from the application start callback
|
||||
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
||||
* [Kernel] Ensure derived protocols are defined with a file
|
||||
* [Kernel] Change precedence of `&` to not special case `/`
|
||||
@@ -70,7 +358,7 @@
|
||||
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `:escript_*` options were moved into a single `:escript` group
|
||||
* [Mix] `:escript_*` options moved into a single `:escript` group
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
|
||||
|
||||
+2
-23
@@ -10,7 +10,7 @@ Use the issues tracker for:
|
||||
* [bug reports](#bugs-reports)
|
||||
* [submitting pull requests](#pull-requests)
|
||||
|
||||
Please **do not** use the issues tracker for personal support requests nor feature requests. Support requests should be send to:
|
||||
Please **do not** use the issue tracker for personal support requests nor feature requests. Support requests should be sent to:
|
||||
|
||||
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
|
||||
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
|
||||
@@ -18,7 +18,7 @@ Please **do not** use the issues tracker for personal support requests nor featu
|
||||
|
||||
Feature requests can be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core).
|
||||
|
||||
We do our best to keep the issues tracker tidy and organized, making it useful
|
||||
We do our best to keep the issue tracker tidy and organized, making it useful
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
@@ -107,26 +107,6 @@ check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
### Building on Windows
|
||||
|
||||
There are a few extra steps you'll need to take for contributing from Windows.
|
||||
Basically, once you have Erlang 17, Git, and MSYS from MinGW on your system,
|
||||
you're all set. Specifically, here's what you need to do to get up and running:
|
||||
|
||||
1. Install [Git](http://www.git-scm.com/download/win),
|
||||
[Erlang](http://www.erlang.org/download.html), and the
|
||||
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
|
||||
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
|
||||
msys-grep packages.
|
||||
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
|
||||
to your "Path" environment variable . (This is under Control Panel > System
|
||||
and Security > System > Advanced system settings > Environment Variables >
|
||||
System variables)
|
||||
|
||||
You can now work in the Command Prompt similar to how you would on other OS'es,
|
||||
except for some things (like creating symlinks) you'll need to run the Command
|
||||
Prompt as an Administrator.
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
|
||||
@@ -279,7 +259,6 @@ We have saved some excellent pull requests we have received in the past in case
|
||||
you are looking for some examples:
|
||||
|
||||
* https://github.com/elixir-lang/elixir/pull/992
|
||||
* https://github.com/elixir-lang/elixir/pull/1041
|
||||
* https://github.com/elixir-lang/elixir/pull/1058
|
||||
* https://github.com/elixir-lang/elixir/pull/1059
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2012-2013 Plataformatec.
|
||||
Copyright 2012 Plataformatec
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
REBAR := rebar
|
||||
DOCS := v1.0
|
||||
CANONICAL := stable
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -11,7 +13,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
|
||||
.PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Functions
|
||||
@@ -32,7 +34,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
|
||||
@@ -68,10 +70,10 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile EEx fully
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
|
||||
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
@@ -86,10 +88,11 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
@ echo "Embedding the Unicode database... (this may take a while)"
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,logger,Logger))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
$(eval $(call APP_TEMPLATE,mix,Mix))
|
||||
$(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
@@ -123,16 +126,18 @@ clean_exbeam:
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
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)" -o docs/$(2) -p http://elixir-lang.org/docs.html
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}\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 "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
$(Q) rm -rf docs
|
||||
$(call DOCS,Elixir,elixir,Kernel)
|
||||
$(call DOCS,EEx,eex,EEx)
|
||||
$(call DOCS,Mix,mix,Mix)
|
||||
$(call DOCS,IEx,iex,IEx)
|
||||
$(call DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(Q) rm -rf doc
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
|
||||
$(call COMPILE_DOCS,EEx,eex,EEx)
|
||||
$(call COMPILE_DOCS,Mix,mix,Mix)
|
||||
$(call COMPILE_DOCS,IEx,iex,IEx)
|
||||
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(call COMPILE_DOCS,Logger,logger,Logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -140,12 +145,21 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE Makefile README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
rm -rf ../docs/$(DOCS)/*/
|
||||
mv 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
|
||||
|
||||
@@ -164,7 +178,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_doc_test test_mix test_eex test_iex
|
||||
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)"
|
||||
@@ -173,16 +187,26 @@ test_doc_test: compile
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) exec epmd & exit
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
$(Q) if [ "$(OS)" = "Windows_NT" ]; then \
|
||||
cd lib/elixir && cmd //C call ../../bin/elixir.bat -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
|
||||
else \
|
||||
cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"; \
|
||||
fi
|
||||
|
||||
.dialyzer.base_plt:
|
||||
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
|
||||
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
|
||||
#==> Dialyzer tasks
|
||||
|
||||
dialyze: .dialyzer.base_plt
|
||||
$(Q) rm -f .dialyzer_plt
|
||||
$(Q) cp .dialyzer.base_plt .dialyzer_plt
|
||||
@ echo "==> Adding Elixir to PLT..."
|
||||
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
|
||||
PLT = .elixir.plt
|
||||
|
||||
$(PLT):
|
||||
@ echo "==> Building PLT with Elixir's dependencies..."
|
||||
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
|
||||
|
||||
clean_plt:
|
||||
$(Q) rm -f $(PLT)
|
||||
|
||||
build_plt: clean_plt $(PLT)
|
||||
|
||||
dialyze: compile $(PLT)
|
||||
@ echo "==> Dialyzing Elixir..."
|
||||
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
||||
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
|
||||
|
||||
@@ -12,6 +12,8 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
$ 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).
|
||||
|
||||
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.
|
||||
|
||||
+26
-14
@@ -1,31 +1,43 @@
|
||||
# Release process
|
||||
|
||||
## All releases
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1. Remove `-dev` extension from VERSION
|
||||
1. Ensure you are running on the oldest supported Erlang version
|
||||
|
||||
2. Ensure CHANGELOG is updated and timestamp
|
||||
2. Remove all `-dev` extension from versions (see below for all files)
|
||||
|
||||
3. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
3. Ensure CHANGELOG is updated and add current date
|
||||
|
||||
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
4. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
5. Push master and tags
|
||||
5. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
6. Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
6. Ensure minimum supported Hex works with new release (instructions upcoming)
|
||||
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
7. Push branch and the new tag
|
||||
|
||||
8. Merge master into stable branch and push it
|
||||
8. Release new docs with `make release_docs`, copy docs to `docs/stable` if appropriate, and push
|
||||
|
||||
9. After release, bump versions, add `-dev` back and commit
|
||||
9. Release new zip with `make release_zip`, push `Precompiled.zip` to GitHub Releases
|
||||
|
||||
10. `make release_docs` once again and push it to `elixir-lang.org`
|
||||
10. Add the release to `elixir.csv` file in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
11. Also update `release` file in `elixir-lang.org`
|
||||
11. Build and push standalone Mix with `make publish_mix` (requires AWS credentials)
|
||||
|
||||
## New vMAJOR.MINOR releases
|
||||
|
||||
12. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
|
||||
|
||||
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
|
||||
* CHANGELOG
|
||||
* src/elixir.app.src
|
||||
* VERSION (make sure there is no newline in this file)
|
||||
* CHANGELOG.md
|
||||
* src/elixir.app.src (not lib/elixir/src/elixir.app.src)
|
||||
|
||||
+8
-1
@@ -80,7 +80,14 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-noshell -s elixir start_cli $ERL"; fi
|
||||
|
||||
# Check for terminal support
|
||||
if [ "$OS" != "Windows_NT" ]; then
|
||||
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
|
||||
fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
|
||||
+31
-20
@@ -1,4 +1,5 @@
|
||||
@echo off
|
||||
setlocal
|
||||
if "%1"=="" goto documentation
|
||||
if "%1"=="--help" goto documentation
|
||||
if "%1"=="-h" goto documentation
|
||||
@@ -22,12 +23,13 @@ echo --sname name Makes and assigns a short name to the distributed node
|
||||
echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --werl Uses Erlang's Windows shell GUI
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
|
||||
goto :EOF
|
||||
goto end
|
||||
|
||||
:parseopts
|
||||
|
||||
@@ -46,6 +48,9 @@ set beforeExtra=
|
||||
rem Flag which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
|
||||
rem Designates which mode / Elixir component to run as
|
||||
set runMode="elixir"
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
@@ -59,23 +64,24 @@ if "%par%"=="""" (
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF "%par%"==""--werl"" (Set useWerl=1)
|
||||
IF "%par%"==""+iex"" (Set runMode="iex")
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
IF NOT "%par%"=="%par:-r=%" (shift)
|
||||
IF NOT "%par%"=="%par:-pr=%" (shift)
|
||||
IF NOT "%par%"=="%par:-pa=%" (shift)
|
||||
IF NOT "%par%"=="%par:-pz=%" (shift)
|
||||
IF NOT "%par%"=="%par:--app=%" (shift)
|
||||
IF NOT "%par%"=="%par:--remsh=%" (shift)
|
||||
IF """"=="%par:-e=%" (shift)
|
||||
IF """"=="%par:-r=%" (shift)
|
||||
IF """"=="%par:-pr=%" (shift)
|
||||
IF """"=="%par:-pa=%" (shift)
|
||||
IF """"=="%par:-pz=%" (shift)
|
||||
IF """"=="%par:--app=%" (shift)
|
||||
IF """"=="%par:--remsh=%" (shift)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
|
||||
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
goto:startloop
|
||||
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
@@ -88,8 +94,13 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
IF NOT %runMode% == "iex" (
|
||||
set beforeExtra=-s elixir start_cli %beforeExtra%
|
||||
)
|
||||
IF %useWerl% EQU 1 (
|
||||
start werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
:end
|
||||
endlocal
|
||||
|
||||
@@ -38,4 +38,6 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
if [ "$OS" = "Windows_NT" ]; then NOSHELL="-noshell "; fi
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "$NOSHELL -user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env elixir
|
||||
# Reminder: apply any changes in this file to bin\mix.bat
|
||||
Mix.start
|
||||
Mix.CLI.main
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
|
||||
|
||||
+10
-14
@@ -1,27 +1,23 @@
|
||||
# Initialize with path to mix.bat relative to caller's working directory
|
||||
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat'
|
||||
# Store path to mix.bat as a FileInfo object
|
||||
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
|
||||
$newArgs = @()
|
||||
|
||||
foreach ($arg in $args)
|
||||
for ($i = 0; $i -lt $args.length; $i++)
|
||||
{
|
||||
$toCmd += ' '
|
||||
|
||||
if ($arg -is [array])
|
||||
if ($args[$i] -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($i = 0; $i -lt $arg.length; $i++)
|
||||
for ($j = 0; $j -lt $args[$i].length - 1; $j++)
|
||||
{
|
||||
$toCmd += $arg[$i]
|
||||
if ($i -ne ($arg.length - 1))
|
||||
{
|
||||
$toCmd += ', '
|
||||
}
|
||||
$newArgs += ($args[$i][$j] + ',')
|
||||
}
|
||||
$newArgs += $args[$i][-1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$toCmd += $arg
|
||||
$newArgs += $args[$i]
|
||||
}
|
||||
}
|
||||
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
cmd /c $toCmd
|
||||
& $mixBatPath $newArgs
|
||||
|
||||
+8
-5
@@ -1,5 +1,9 @@
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:message]
|
||||
defexception [:message, :file, :line]
|
||||
|
||||
def message(exception) do
|
||||
"#{exception.file}:#{exception.line}: #{exception.message}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx do
|
||||
@@ -34,10 +38,9 @@ defmodule EEx do
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling
|
||||
from a string.
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling from a string.
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -12,10 +12,14 @@ defmodule EEx.Compiler do
|
||||
def compile(source, opts) do
|
||||
file = opts[:file] || "nofile"
|
||||
line = opts[:line] || 1
|
||||
tokens = EEx.Tokenizer.tokenize(source, line)
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
case EEx.Tokenizer.tokenize(source, line) do
|
||||
{:ok, tokens} ->
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
{:error, line, message} ->
|
||||
raise EEx.SyntaxError, line: line, file: file, message: message
|
||||
end
|
||||
end
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
@@ -51,16 +55,17 @@ defmodule EEx.Compiler do
|
||||
{buffer, t}
|
||||
end
|
||||
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token #{inspect chars}", file: state.file, line: line
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
state.engine.handle_body(buffer)
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected end of string. expecting a closing <% end %>."
|
||||
defp generate_buffer([], _buffer, _scope, state) do
|
||||
raise EEx.SyntaxError, message: "unexpected end of string, expected a closing '<% end %>'",
|
||||
file: state.file, line: state.line
|
||||
end
|
||||
|
||||
# Creates a placeholder and wrap it inside the expression block
|
||||
|
||||
@@ -64,7 +64,8 @@ defmodule EEx.Engine do
|
||||
end
|
||||
|
||||
"""
|
||||
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) 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))
|
||||
end
|
||||
|
||||
@@ -97,16 +98,16 @@ defmodule EEx.Engine do
|
||||
"""
|
||||
def handle_expr(buffer, "=", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp <> to_string(unquote(expr))
|
||||
tmp1 = unquote(buffer)
|
||||
tmp1 <> String.Chars.to_string(unquote(expr))
|
||||
end
|
||||
end
|
||||
|
||||
def handle_expr(buffer, "", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp2 = unquote(buffer)
|
||||
unquote(expr)
|
||||
tmp
|
||||
tmp2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,63 +1,3 @@
|
||||
defmodule EEx.TransformerEngine do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, transform(expr))
|
||||
end
|
||||
|
||||
defp transform({a, b, c}) do
|
||||
{transform(a), b, transform(c)}
|
||||
end
|
||||
|
||||
defp transform({a, b}) do
|
||||
{transform(a), transform(b)}
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
for i <- list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.AssignsEngine do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
The default engine used by EEx.
|
||||
|
||||
@@ -3,7 +3,8 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
@doc """
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
It returns {:ok, list} with the following tokens:
|
||||
|
||||
* `{:text, contents}`
|
||||
* `{:expr, line, marker, contents}`
|
||||
@@ -11,33 +12,43 @@ defmodule EEx.Tokenizer do
|
||||
* `{:middle_expr, line, marker, contents}`
|
||||
* `{:end_expr, line, marker, contents}`
|
||||
|
||||
Or `{:error, line, error}` in case of errors.
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(String.to_char_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
Enum.reverse(tokenize(list, line, [], []))
|
||||
tokenize(list, line, [], [])
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
case expr(t, line, [?%, ?<|buffer]) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, buffer, new_line, rest} ->
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{_, new_line, rest} = tokenize_expr t, line, []
|
||||
tokenize rest, new_line, buffer, acc
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, _, new_line, rest} ->
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{marker, t} = retrieve_marker(t)
|
||||
{expr, new_line, rest} = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
case expr(t, line, []) do
|
||||
{:error, _, _} = error -> error
|
||||
{:ok, expr, new_line, rest} ->
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, line, buffer, acc) do
|
||||
@@ -49,7 +60,7 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize([], _line, buffer, acc) do
|
||||
tokenize_text(buffer, acc)
|
||||
{:ok, Enum.reverse(tokenize_text(buffer, acc))}
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
@@ -64,20 +75,20 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{buffer, line, t}
|
||||
defp expr([?%, ?>|t], line, buffer) do
|
||||
{:ok, buffer, line, t}
|
||||
end
|
||||
|
||||
defp tokenize_expr('\n' ++ t, line, buffer) do
|
||||
tokenize_expr t, line + 1, [?\n|buffer]
|
||||
defp expr('\n' ++ t, line, buffer) do
|
||||
expr t, line + 1, [?\n|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([h|t], line, buffer) do
|
||||
tokenize_expr t, line, [h|buffer]
|
||||
defp expr([h|t], line, buffer) do
|
||||
expr t, line, [h|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([], _line, _buffer) do
|
||||
raise EEx.SyntaxError, message: "missing token: %>"
|
||||
defp expr([], line, _buffer) do
|
||||
{:error, line, "missing token '%>'"}
|
||||
end
|
||||
|
||||
# Receive an expression content and check
|
||||
|
||||
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
test "compiled preserved line numbers" do
|
||||
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
|
||||
Macro.prewalk(result, fn
|
||||
{_left, meta, _right} ->
|
||||
assert Keyword.get(meta, :line, 0) in [0, 1]
|
||||
_ ->
|
||||
:ok
|
||||
end)
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
|
||||
@@ -5,23 +5,26 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
|
||||
assert T.tokenize('foo', 1) == {:ok, [{:text, 'foo'}]}
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
|
||||
assert T.tokenize("foo", 1) == {:ok, [{:text, 'foo'}]}
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
|
||||
assert T.tokenize('foo <% bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, "", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, "=", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) ==
|
||||
{:ok, [{:text, 'foo\n'}, {:expr, 2, "=", ' bar '}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -32,50 +35,50 @@ baz %>
|
||||
<% foo %>
|
||||
'''
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
assert T.tokenize(string, 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, '\n'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
assert T.tokenize('foo <%% true %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true %>'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == {:ok, [
|
||||
{:text, 'foo <% true do %>bar<% end %>'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
assert T.tokenize('foo <%# true %>', 1) == {:ok, [
|
||||
{:text, 'foo '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == {:ok, [
|
||||
{:text, 'foo bar'}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' cond do '},
|
||||
{:middle_expr, 1, "", ' false -> '},
|
||||
@@ -83,23 +86,21 @@ baz %>
|
||||
{:middle_expr, 1, "", ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == {:ok, [
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' else '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "raise syntax error when there is start mark and no end mark" do
|
||||
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
|
||||
T.tokenize('foo <% :bar', 1)
|
||||
end
|
||||
assert T.tokenize('foo <% :bar', 1) == {:error, 1, "missing token '%>'"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,8 @@ defmodule EExTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest EEx
|
||||
doctest EEx.AssignsEngine
|
||||
doctest EEx.Engine
|
||||
doctest EEx.SmartEngine
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
@@ -110,25 +111,25 @@ defmodule EExTest do
|
||||
end
|
||||
|
||||
test "raises a syntax error when the token is invalid" do
|
||||
assert_raise EEx.SyntaxError, "missing token: %>", fn ->
|
||||
assert_raise EEx.SyntaxError, "nofile:1: missing token '%>'", fn ->
|
||||
EEx.compile_string "foo <%= bar"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
|
||||
EEx.compile_string "foo <% end %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when start expression is found without an end expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected end of string. expecting a closing <% end %>.", fn ->
|
||||
EEx.compile_string "foo <% if true do %>"
|
||||
assert_raise EEx.SyntaxError, "nofile:2: unexpected end of string, expected a closing '<% end %>'", fn ->
|
||||
EEx.compile_string "foo\n<% if true do %>"
|
||||
end
|
||||
end
|
||||
|
||||
test "raises a syntax error when nested end expression is found without an start expression" do
|
||||
assert_raise EEx.SyntaxError, "unexpected token: ' end ' at line 1", fn ->
|
||||
test "raises a syntax error when nested end expression is found without a start expression" do
|
||||
assert_raise EEx.SyntaxError, "nofile:1: unexpected token ' end '", fn ->
|
||||
EEx.compile_string "foo <% if true do %><% end %><% end %>"
|
||||
end
|
||||
end
|
||||
@@ -251,7 +252,7 @@ Number <%= x %>
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "do not consider already finished functions" do
|
||||
test "properly handle functions on the left side of clauses" do
|
||||
expected = """
|
||||
foo
|
||||
|
||||
@@ -286,6 +287,18 @@ foo
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "evaluates expressions with buffers" do
|
||||
string = """
|
||||
<%= 123 %>
|
||||
<% if true do %>
|
||||
<%= 456 %>
|
||||
<% end %>
|
||||
<%= 789 %>
|
||||
"""
|
||||
|
||||
assert_eval "123\n\n789\n", string
|
||||
end
|
||||
|
||||
test "for comprehensions" do
|
||||
string = """
|
||||
<%= for _name <- packages || [] do %>
|
||||
|
||||
@@ -119,7 +119,7 @@ defimpl Access, for: Map do
|
||||
|
||||
def get_and_update!(other, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,8 +132,9 @@ defimpl Access, for: Atom do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
def get_and_update(nil, _, fun) do
|
||||
fun.(nil)
|
||||
def get_and_update(nil, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not put/update key #{inspect key} on a nil value"
|
||||
end
|
||||
|
||||
def get_and_update(atom, _key, _fun) do
|
||||
|
||||
+89
-18
@@ -68,22 +68,22 @@ defmodule Agent do
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
provides two APIs, one that works with anonymous functions and another
|
||||
that expects explicit module, function and arguments.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them with
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
crash. That said, if you plan to run in distributed environments, agents
|
||||
should likely be avoided.
|
||||
In a distributed setup with multiple nodes, the API that accepts anonymous
|
||||
functions only works if the caller (client) and the agent have the same
|
||||
version of the caller module.
|
||||
|
||||
Note, however, that agents work fine if you want to perform hot code
|
||||
swapping, as it keeps both the old and new versions of a given module.
|
||||
We detail how to do hot code swapping with agents in the next section.
|
||||
Keep in mind this issue also shows up when performing "rolling upgrades"
|
||||
with agents. By rolling upgrades we mean the following situation: you wish
|
||||
to deploy a new version of your software by *shutting down* some of your
|
||||
nodes and replacing them with nodes running a new version of the software.
|
||||
In this setup, part of your environment will have one version of a given
|
||||
module and the other part another version (the newer one) of the same module.
|
||||
|
||||
The best solution is to simply use the explicit module, function and arguments
|
||||
APIs when working with distributed agents.
|
||||
|
||||
## Hot code swapping
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Agent do
|
||||
@type state :: term
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process.
|
||||
Starts an agent linked to the current process with the given function.
|
||||
|
||||
This is often used to start the agent as part of a supervision tree.
|
||||
|
||||
@@ -149,6 +149,18 @@ defmodule Agent do
|
||||
GenServer.start_link(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process with the given module
|
||||
function and arguments.
|
||||
|
||||
Same as `start_link/2` but a module, function and args are expected
|
||||
instead of an anonymous function.
|
||||
"""
|
||||
@spec start_link(module, atom, [any], GenServer.options) :: on_start
|
||||
def start_link(module, fun, args, options \\ []) do
|
||||
GenServer.start_link(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent process without links (outside of a supervision tree).
|
||||
|
||||
@@ -160,7 +172,18 @@ defmodule Agent do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the agent value and executes the given function.
|
||||
Starts an agent with the given module function and arguments.
|
||||
|
||||
Similar to `start/2` but a module, function and args are expected
|
||||
instead of an anonymous function.
|
||||
"""
|
||||
@spec start(module, atom, [any], GenServer.options) :: on_start
|
||||
def start(module, fun, args, options \\ []) do
|
||||
GenServer.start(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an agent value via the given function.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The result of the function invocation is
|
||||
@@ -173,6 +196,18 @@ defmodule Agent do
|
||||
GenServer.call(agent, {:get, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an agent value via the given function.
|
||||
|
||||
Same as `get/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec get(agent, module, atom, [term], timeout) :: any
|
||||
def get(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
@@ -188,6 +223,18 @@ defmodule Agent do
|
||||
GenServer.call(agent, {:get_and_update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
Same as `get_and_update/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec get_and_update(agent, module, atom, [term], timeout) :: any
|
||||
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
@@ -197,11 +244,23 @@ defmodule Agent do
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec update(agent, (state -> state)) :: :ok
|
||||
@spec update(agent, (state -> state), timeout) :: :ok
|
||||
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
Same as `update/3` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec update(agent, module, atom, [term], timeout) :: :ok
|
||||
def update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
@@ -213,7 +272,19 @@ defmodule Agent do
|
||||
"""
|
||||
@spec cast(agent, (state -> state)) :: :ok
|
||||
def cast(agent, fun) when is_function(fun, 1) do
|
||||
GenServer.cast(agent, fun)
|
||||
GenServer.cast(agent, {:cast, fun})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
Same as `cast/2` but a module, function and args are expected
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of args.
|
||||
"""
|
||||
@spec cast(agent, module, atom, [term]) :: :ok
|
||||
def cast(agent, module, fun, args) do
|
||||
GenServer.cast(agent, {:cast, {module, fun, args}})
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -4,20 +4,20 @@ defmodule Agent.Server do
|
||||
use GenServer
|
||||
|
||||
def init(fun) do
|
||||
{:ok, fun.()}
|
||||
{:ok, run(fun, [])}
|
||||
end
|
||||
|
||||
def handle_call({:get, fun}, _from, state) do
|
||||
{:reply, fun.(state), state}
|
||||
{:reply, run(fun, [state]), state}
|
||||
end
|
||||
|
||||
def handle_call({:get_and_update, fun}, _from, state) do
|
||||
{reply, state} = fun.(state)
|
||||
{reply, state} = run(fun, [state])
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:update, fun}, _from, state) do
|
||||
{:reply, :ok, fun.(state)}
|
||||
{:reply, :ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
@@ -28,16 +28,16 @@ defmodule Agent.Server do
|
||||
super(msg, from, state)
|
||||
end
|
||||
|
||||
def handle_cast(fun, state) when is_function(fun, 1) do
|
||||
{:noreply, fun.(state)}
|
||||
def handle_cast({:cast, fun}, state) do
|
||||
{:noreply, run(fun, [state])}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
|
||||
def code_change(_old, state, { m, f, a }) do
|
||||
{:ok, apply(m, f, [state|a])}
|
||||
def code_change(_old, state, fun) do
|
||||
{:ok, run(fun, [state])}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
@@ -50,4 +50,7 @@ defmodule Agent.Server do
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
defp run({m, f, a}, extra), do: apply(m, f, extra ++ a)
|
||||
defp run(fun, extra), do: apply(fun, extra)
|
||||
end
|
||||
|
||||
@@ -201,7 +201,7 @@ defmodule Application do
|
||||
`:applications` in the `.app` file in case they were not previously
|
||||
started.
|
||||
"""
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, {app, term}}
|
||||
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_all_started(app, type)
|
||||
end
|
||||
@@ -322,7 +322,7 @@ defmodule Application do
|
||||
|
||||
@doc """
|
||||
Formats the error reason returned by `start/2`,
|
||||
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
|
||||
`ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
|
||||
returns a string.
|
||||
"""
|
||||
@spec format_error(any) :: String.t
|
||||
|
||||
@@ -499,7 +499,7 @@ defmodule Base do
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
@@ -514,7 +514,7 @@ defmodule Base do
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
split = byte_size(string) - 4
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
@@ -534,7 +534,7 @@ defmodule Base do
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
<<main::size(split)-binary, rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
@@ -561,7 +561,7 @@ defmodule Base do
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
<<main::size(split)-binary, rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
|
||||
+12
-3
@@ -51,6 +51,14 @@ defmodule Code do
|
||||
:code.del_path(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def readd_paths() do
|
||||
{pa, pz} = :elixir_code_server.call(:paths)
|
||||
:code.add_pathsa(pa)
|
||||
:code.add_pathsz(pz)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
@@ -243,7 +251,7 @@ defmodule Code do
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evalutes the file contents and returns the
|
||||
byte code, `eval_file` simply evaluates the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
@@ -308,7 +316,7 @@ defmodule Code do
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
:elixir_code_server.call :compiler_options
|
||||
:elixir_config.get :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,7 +355,8 @@ defmodule Code do
|
||||
bad = bad |> Keyword.keys |> Enum.join(", ")
|
||||
raise ArgumentError, message: "unknown compiler options: #{bad}"
|
||||
end
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
|
||||
:elixir_config.update :compiler_options, update
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -8,12 +8,6 @@ defprotocol Collectable do
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
@@ -27,25 +21,13 @@ defprotocol Collectable do
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
`Enumerable` protocol. `into/1` can be seen as the opposite of
|
||||
`Enumerable.reduce/3`. If `Enumerable` is about taking values out,
|
||||
`Collectable.into/1` is about collecting those values into a structure.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
@@ -65,10 +47,6 @@ defprotocol Collectable do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x|list]
|
||||
@@ -79,34 +57,16 @@ defimpl Collectable, for: List do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
|
||||
bitstring, :done -> bitstring
|
||||
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
|
||||
acc, :done -> IO.iodata_to_binary(acc)
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
|
||||
+78
-78
@@ -288,8 +288,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(d))
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(dict))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -304,8 +304,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(d))
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(dict))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -319,8 +319,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(d)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(dict)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -334,10 +334,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(dict, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
iex> Dict.has_key?(dict, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -352,12 +352,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(dict, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
iex> Dict.get(dict, :b)
|
||||
nil
|
||||
iex> Dict.get(d, :b, 3)
|
||||
iex> Dict.get(dict, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -371,10 +371,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(dict, :a)
|
||||
{:ok, 1}
|
||||
iex> Dict.fetch(d, :b)
|
||||
iex> Dict.fetch(dict, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@@ -389,8 +389,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(dict, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -405,9 +405,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.put(dict, :a, 3)
|
||||
iex> Dict.get(dict, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -421,9 +421,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put_new(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
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
|
||||
|
||||
"""
|
||||
@@ -438,13 +438,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.delete(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> dict = Dict.delete(dict, :a)
|
||||
iex> Dict.get(dict, :a)
|
||||
nil
|
||||
|
||||
iex> d = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(d, :a) == d
|
||||
iex> dict = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(dict, :a) == dict
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -454,10 +454,10 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the dict `b` into dict `a`.
|
||||
Merges the dict `dict2` into dict `dict1`.
|
||||
|
||||
If one of the dict `b` entries already exists in the `dict`,
|
||||
the functions in entries in `b` have higher precedence unless a
|
||||
If one of the `dict2` entries already exists in `dict1`, the
|
||||
functions in entries in `dict2` have higher precedence unless a
|
||||
function is given to resolve conflicts.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
@@ -466,18 +466,18 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
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]
|
||||
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
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(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -502,18 +502,18 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(d)}
|
||||
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, d} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(d)}
|
||||
iex> {v, dict} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(dict)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@@ -528,9 +528,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :a)
|
||||
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
|
||||
|
||||
"""
|
||||
@@ -546,9 +546,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :c)
|
||||
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
|
||||
|
||||
"""
|
||||
@@ -566,19 +566,19 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
|
||||
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> d = Enum.into([], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c])
|
||||
iex> {Dict.to_list(d1), Dict.to_list(d2)}
|
||||
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> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
|
||||
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], []}
|
||||
|
||||
"""
|
||||
@@ -593,14 +593,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
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> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:c, :d])
|
||||
iex> Dict.to_list(d) |> Enum.sort
|
||||
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]
|
||||
|
||||
"""
|
||||
@@ -616,12 +616,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.take(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
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> d = Dict.take(d, [:c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
iex> dict = Dict.take(dict, [:c, :d])
|
||||
iex> Dict.to_list(dict)
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -645,14 +645,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(a, b)
|
||||
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> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = []
|
||||
iex> Dict.equal?(a, b)
|
||||
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> dict2 = []
|
||||
iex> Dict.equal?(dict1, dict2)
|
||||
false
|
||||
|
||||
"""
|
||||
|
||||
+76
-39
@@ -270,8 +270,13 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Returns `default` if index is out of bounds.
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
@@ -287,7 +292,6 @@ defmodule Enum do
|
||||
:none
|
||||
|
||||
"""
|
||||
@spec at(t, integer) :: element | nil
|
||||
@spec at(t, integer, default) :: element | default
|
||||
def at(collection, n, default \\ nil) do
|
||||
case fetch(collection, n) do
|
||||
@@ -330,7 +334,6 @@ defmodule Enum do
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
"""
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
|
||||
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
@@ -338,7 +341,7 @@ defmodule Enum do
|
||||
{_, {acc, {buffer, i}}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
|
||||
|
||||
if nil?(pad) || i == 0 do
|
||||
if is_nil(pad) || i == 0 do
|
||||
:lists.reverse(acc)
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ take(pad, n - i)
|
||||
@@ -566,12 +569,17 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Returns `{:ok, element}` if found, otherwise `:error`.
|
||||
|
||||
A negative index can be passed, which means the collection is
|
||||
enumerated once and the index is counted from the end (i.e.
|
||||
`-1` fetches the last element).
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
@@ -585,11 +593,11 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec fetch(t, integer) :: {:ok, element} | :error
|
||||
def fetch(collection, n) when is_list(collection) and n >= 0 do
|
||||
def fetch(collection, n) when is_list(collection) and is_integer(n) and n >= 0 do
|
||||
do_fetch(collection, n)
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n >= 0 do
|
||||
def fetch(collection, n) when is_integer(n) and n >= 0 do
|
||||
res =
|
||||
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
|
||||
if acc == n do
|
||||
@@ -605,15 +613,20 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n < 0 do
|
||||
def fetch(collection, n) when is_integer(n) and n < 0 do
|
||||
do_fetch(reverse(collection), abs(n + 1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
|
||||
Raises `OutOfBoundsError` if the given position
|
||||
is outside the range of the collection.
|
||||
|
||||
Note this operation takes linear time. In order to access
|
||||
the element at index `n`, it will need to traverse `n`
|
||||
previous elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch!([2, 4, 6], 0)
|
||||
@@ -689,7 +702,6 @@ defmodule Enum do
|
||||
3
|
||||
|
||||
"""
|
||||
@spec find(t, (element -> any)) :: element | nil
|
||||
@spec find(t, default, (element -> any)) :: element | default
|
||||
def find(collection, ifnone \\ nil, fun)
|
||||
|
||||
@@ -716,7 +728,6 @@ defmodule Enum do
|
||||
true
|
||||
|
||||
"""
|
||||
@spec find_value(t, (element -> any)) :: any | :nil
|
||||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||||
def find_value(collection, ifnone \\ nil, fun)
|
||||
|
||||
@@ -931,7 +942,6 @@ defmodule Enum do
|
||||
"1 = 2 = 3"
|
||||
|
||||
"""
|
||||
@spec join(t) :: String.t
|
||||
@spec join(t, String.t) :: String.t
|
||||
def join(collection, joiner \\ "")
|
||||
|
||||
@@ -990,7 +1000,6 @@ defmodule Enum do
|
||||
"2 = 4 = 6"
|
||||
|
||||
"""
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@spec map_join(t, String.t, (element -> any)) :: String.t
|
||||
def map_join(collection, joiner \\ "", mapper)
|
||||
|
||||
@@ -1252,6 +1261,15 @@ defmodule Enum do
|
||||
:lists.foldl(fun, acc, collection)
|
||||
end
|
||||
|
||||
def reduce(%{__struct__: _} = collection, acc, fun) do
|
||||
Enumerable.reduce(collection, {:cont, acc},
|
||||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
end
|
||||
|
||||
def reduce(%{} = collection, acc, fun) do
|
||||
:maps.fold(fn k, v, acc -> fun.({k, v}, acc) end, acc, collection)
|
||||
end
|
||||
|
||||
def reduce(collection, acc, fun) do
|
||||
Enumerable.reduce(collection, {:cont, acc},
|
||||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
@@ -1396,7 +1414,7 @@ defmodule Enum do
|
||||
result. For example, one could do the following to set a seed
|
||||
dynamically:
|
||||
|
||||
:random.seed(:erlang.now)
|
||||
:random.seed(:os.timestamp)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1568,6 +1586,46 @@ defmodule Enum do
|
||||
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the mapped results of the `collection` according to the `sorter` function.
|
||||
|
||||
This function maps each element of the collection using the `mapper`
|
||||
function. The collection is then sorted by the mapped elements using the
|
||||
`sorter` function, which defaults to `<=/2`
|
||||
|
||||
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
|
||||
value for each element in the collection once instead of once for each
|
||||
element in each comparison. If the same function is being called on both
|
||||
element, it's also more compact to use `sort_by/3`.
|
||||
|
||||
This technique is also known as a
|
||||
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
|
||||
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
|
||||
original `collection`, then `sorter` is sorting the decorations, and finally
|
||||
the `collection` is being undecorated so only the original elements remain,
|
||||
but now in sorted order.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the default `sorter` of `<=/2`:
|
||||
|
||||
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
|
||||
["of", "some", "kind", "monster"]
|
||||
|
||||
Using a custom `sorter` to override the order:
|
||||
|
||||
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
|
||||
["monster", "some", "kind", "of"]
|
||||
|
||||
"""
|
||||
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
|
||||
def sort_by(collection, mapper, sorter \\ &<=/2) do
|
||||
collection
|
||||
|> map(&{&1, mapper.(&1)})
|
||||
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|
||||
|> map(&elem(&1, 0))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the enumerable into two collections, leaving `count`
|
||||
elements in the first one. If `count` is a negative number,
|
||||
@@ -1673,23 +1731,23 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
def take(collection, count) when is_list(collection) and count > 0 do
|
||||
def take(collection, count) when is_list(collection) and is_integer(count) and count > 0 do
|
||||
do_take(collection, count)
|
||||
end
|
||||
|
||||
def take(collection, count) when count > 0 do
|
||||
def take(collection, count) when count > 0 and is_integer(count) do
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
|
||||
if count > 1 do
|
||||
{:cont, {[entry|list], count - 1}}
|
||||
else
|
||||
{:halt, {[entry|list], count}}
|
||||
case count do
|
||||
0 -> {:halt, {list, count}}
|
||||
1 -> {:halt, {[entry|list], count - 1}}
|
||||
_ -> {:cont, {[entry|list], count - 1}}
|
||||
end
|
||||
end)
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
def take(collection, count) when count < 0 do
|
||||
def take(collection, count) when count < 0 and is_integer(count) do
|
||||
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
@@ -1749,27 +1807,6 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enumerates the collection, removing all duplicated items.
|
||||
|
||||
|
||||
+49
-11
@@ -5,16 +5,16 @@ defmodule Exception do
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
last throw/error/exit that occurred in the current process.
|
||||
|
||||
Do not rely on the particular format returned by the `format`
|
||||
functions in this module. They may be changed in future releases
|
||||
in order to better suit Elixir's tool chain. In other words,
|
||||
by using the functions in this module it is guarantee you will
|
||||
by using the functions in this module it is guaranteed you will
|
||||
format exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@typedoc "The exception type (as generated by defexception)"
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@@ -482,6 +482,7 @@ end
|
||||
defmodule RuntimeError do
|
||||
defexception message: "runtime error"
|
||||
|
||||
@spec exception(String.t) :: Exception.t
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%RuntimeError{message: msg}
|
||||
end
|
||||
@@ -494,6 +495,7 @@ end
|
||||
defmodule ArgumentError do
|
||||
defexception message: "argument error"
|
||||
|
||||
@spec exception(String.t) :: Exception.t
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%ArgumentError{message: msg}
|
||||
end
|
||||
@@ -562,6 +564,14 @@ defmodule BadStructError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadMapError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a map, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MatchError do
|
||||
defexception [term: nil]
|
||||
|
||||
@@ -611,12 +621,17 @@ defmodule BadArityError do
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
defexception [module: nil, function: nil, arity: nil, self: false]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
def message(%{function: function, module: module, arity: arity, self: self}) do
|
||||
if function do
|
||||
formatted = Exception.format_mfa module, function, arity
|
||||
suffix = if self or is_nil(module) or :code.is_loaded(module) do
|
||||
""
|
||||
else
|
||||
" (module #{inspect module} is not available)"
|
||||
end
|
||||
"undefined function: #{formatted}" <> suffix
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
@@ -662,7 +677,12 @@ defmodule KeyError do
|
||||
defexception key: nil, term: nil
|
||||
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
msg = "key #{inspect exception.key} not found"
|
||||
if exception.term != nil do
|
||||
msg <> " in: #{inspect exception.term}"
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -761,6 +781,20 @@ defmodule ErlangError do
|
||||
%MatchError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badmap, term}, _stacktrace) do
|
||||
%BadMapError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badkey, key}, stacktrace) do
|
||||
term =
|
||||
case stacktrace || :erlang.get_stacktrace do
|
||||
[{:maps, :update, [_, _, map], _}|_] -> map
|
||||
[{:maps, :get, [_, map], _}|_] -> map
|
||||
_ -> nil
|
||||
end
|
||||
%KeyError{key: key, term: term}
|
||||
end
|
||||
|
||||
def normalize({:case_clause, term}, _stacktrace) do
|
||||
%CaseClauseError{term: term}
|
||||
end
|
||||
@@ -770,8 +804,9 @@ defmodule ErlangError do
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
stacktrace = stacktrace || :erlang.get_stacktrace
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity, self: from_self(stacktrace)}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
@@ -798,4 +833,7 @@ defmodule ErlangError do
|
||||
defp from_stacktrace(_) do
|
||||
{nil, nil, nil}
|
||||
end
|
||||
|
||||
defp from_self([{module, _, _, _}, {module, _, _, _}|_]), do: true
|
||||
defp from_self(_), do: false
|
||||
end
|
||||
|
||||
+54
-26
@@ -75,6 +75,12 @@ defmodule File do
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@type stat_options :: [time: :local | :universal | :posix]
|
||||
@type mode :: :append | :binary | :compressed | :delayed_write | :exclusive |
|
||||
:raw | :read | :read_ahead | :sync | :write |
|
||||
{:encoding , :latin1 | :unicode | :utf16 | :utf32 | :utf8 |
|
||||
{:utf16, :big | :little} | {:utf32, :big | :little}} |
|
||||
{:read_ahead, pos_integer} |
|
||||
{:delayed_write, non_neg_integer, non_neg_integer}
|
||||
|
||||
@doc """
|
||||
Returns `true` if the path is a regular file.
|
||||
@@ -126,12 +132,12 @@ defmodule File do
|
||||
Typical error reasons are:
|
||||
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
directories of `path`
|
||||
* `:eexist` - there is already a file or directory named `path`
|
||||
* `:enoent` - a component of `path` does not exist
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
on some platforms, `:enoent` is returned instead
|
||||
"""
|
||||
@spec mkdir(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
@@ -308,11 +314,15 @@ defmodule File do
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.change_time(path, time) do
|
||||
{:error, :enoent} ->
|
||||
write(path, "")
|
||||
F.change_time(path, time)
|
||||
other ->
|
||||
other
|
||||
{:error, :enoent} -> touch_new(path, time)
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp touch_new(path, time) do
|
||||
case write(path, "", [:append]) do
|
||||
:ok -> F.change_time(path, time)
|
||||
{:error, _reason} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -548,7 +558,8 @@ defmodule File do
|
||||
[dest|acc]
|
||||
{:error, :eexist} ->
|
||||
if callback.(src, dest) do
|
||||
rm(dest)
|
||||
# If rm/1 fails, copy/2 will fail
|
||||
_ = rm(dest)
|
||||
case copy(src, dest) do
|
||||
{:ok, _} ->
|
||||
copy_file_mode!(src, dest)
|
||||
@@ -569,7 +580,8 @@ defmodule File do
|
||||
[dest|acc]
|
||||
{:error, :eexist} ->
|
||||
if callback.(src, dest) do
|
||||
rm(dest)
|
||||
# If rm/1 fails, iF.make_symlink/2 will fail
|
||||
_ = rm(dest)
|
||||
case F.make_symlink(link, dest) do
|
||||
:ok -> [dest|acc]
|
||||
{:error, reason} -> {:error, reason, src}
|
||||
@@ -606,7 +618,7 @@ defmodule File do
|
||||
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
@spec write(Path.t, iodata, [mode]) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
@@ -614,7 +626,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
@spec write!(Path.t, iodata, list) :: :ok | no_return
|
||||
@spec write!(Path.t, iodata, [mode]) :: :ok | no_return
|
||||
def write!(path, content, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.write_file(path, content, modes) do
|
||||
@@ -665,14 +677,20 @@ defmodule File do
|
||||
if match? {:win32, _}, :os.type do
|
||||
case F.read_file_info(IO.chardata_to_string(path)) do
|
||||
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
|
||||
File.chmod(path, (elem(file_info, 7) + 0200))
|
||||
F.delete(path)
|
||||
change_mode_windows(path, file_info)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp change_mode_windows(path, file_info) do
|
||||
case File.chmod(path, (elem(file_info, 7) + 0o200)) do
|
||||
:ok -> F.delete(path)
|
||||
{:error, _reason} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@@ -890,7 +908,8 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, [mode | :ram]) :: {:ok, io_device} | {:error, posix}
|
||||
@spec open(Path.t, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
@@ -923,7 +942,7 @@ defmodule File do
|
||||
end)
|
||||
|
||||
"""
|
||||
@spec open(Path.t, list, (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
@spec open(Path.t, [mode | :ram], (io_device -> res)) :: {:ok, res} | {:error, posix} when res: var
|
||||
def open(path, modes, function) do
|
||||
case open(path, modes) do
|
||||
{:ok, device} ->
|
||||
@@ -941,7 +960,7 @@ defmodule File do
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
@spec open!(Path.t, [mode]) :: io_device | no_return
|
||||
def open!(path, modes \\ []) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes) do
|
||||
@@ -956,7 +975,7 @@ defmodule File do
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
@spec open!(Path.t, [mode | :ram], (io_device -> res)) :: res | no_return when res: var
|
||||
def open!(path, modes, function) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes, function) do
|
||||
@@ -977,18 +996,27 @@ defmodule File do
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
The same as `cwd/0`, but raises an exception if it fails.
|
||||
"""
|
||||
@spec cwd!() :: binary | no_return
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{:ok, cwd} -> IO.chardata_to_string(cwd)
|
||||
case cwd() do
|
||||
{:ok, cwd} -> cwd
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -1112,7 +1140,7 @@ defmodule File do
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chmod(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
F.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
@@ -1120,7 +1148,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chmod!(Path.t, integer) :: :ok | no_return
|
||||
@spec chmod!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chmod!(path, mode) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chmod(path, mode) do
|
||||
@@ -1135,7 +1163,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chgrp(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
F.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
@@ -1143,7 +1171,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chgrp!(Path.t, integer) :: :ok | no_return
|
||||
@spec chgrp!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chgrp!(path, gid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chgrp(path, gid) do
|
||||
@@ -1158,7 +1186,7 @@ defmodule File do
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@spec chown(Path.t, integer) :: :ok | {:error, posix}
|
||||
@spec chown(Path.t, non_neg_integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
F.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
@@ -1166,7 +1194,7 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
@spec chown!(Path.t, integer) :: :ok | no_return
|
||||
@spec chown!(Path.t, non_neg_integer) :: :ok | no_return
|
||||
def chown!(path, uid) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case chown(path, uid) do
|
||||
|
||||
@@ -57,6 +57,7 @@ defmodule File.Stat do
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
|
||||
@@ -13,6 +13,8 @@ defmodule File.Stream do
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc false
|
||||
def __build__(path, modes, line_or_bytes) do
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
@@ -55,10 +57,14 @@ defmodule File.Stream do
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
# If delayed_write option is used and the last write failed will
|
||||
# MatchError here as {:error, _} is returned.
|
||||
:ok = :file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
# If delayed_write option is used and the last write failed will
|
||||
# MatchError here as {:error, _} is returned.
|
||||
:ok = :file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+69
-83
@@ -1,3 +1,5 @@
|
||||
import Kernel, except: [round: 1]
|
||||
|
||||
defmodule Float do
|
||||
@moduledoc """
|
||||
Functions for working with floating point numbers.
|
||||
@@ -26,130 +28,108 @@ defmodule Float do
|
||||
"""
|
||||
@spec parse(binary) :: {float, binary} | :error
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
case parse_unsigned(binary) do
|
||||
:error -> :error
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binary) do
|
||||
parse_unsign(binary)
|
||||
parse_unsigned(binary)
|
||||
end
|
||||
|
||||
defp parse_unsign("-" <> _), do: :error
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
defp parse_unsigned(<<char, rest::binary>>) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, false, false, <<char>>)
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse_unsign(rest, char - ?0, 1, int)
|
||||
end
|
||||
defp parse_unsigned(binary) when is_binary(binary), do:
|
||||
:error
|
||||
|
||||
defp parse_unsign(rest, int) do
|
||||
{:erlang.float(int), rest}
|
||||
end
|
||||
defp parse_unsigned(<<char, rest :: binary>>, dot?, e?, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, dot?, e?, <<acc::binary, char>>)
|
||||
|
||||
# Handle decimal points
|
||||
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
defp parse_unsigned(<<?., char, rest :: binary>>, false, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, false, <<acc::binary, ?., char>>)
|
||||
|
||||
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
# the function clause because the current approach copies a binary
|
||||
# just on this branch. If we broke it apart in the function clause,
|
||||
# the copy would happen when calling Integer.parse/1.
|
||||
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
|
||||
{exponential, after_exponential} ->
|
||||
{floatify(int, float, decimal, exponential), after_exponential}
|
||||
end
|
||||
end
|
||||
defp parse_unsigned(<<?e, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, char>>)
|
||||
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{floatify(int, float, decimal), bitstring}
|
||||
end
|
||||
defp parse_unsigned(<<?e, ?-, char, rest :: binary>>, dot?, false, acc) when char in ?0..?9, do:
|
||||
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, ?-, char>>)
|
||||
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
defp parse_unsigned(rest, dot?, _e?, acc), do:
|
||||
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
|
||||
|
||||
# Try avoiding stuff like this:
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
decimal_power_round = :math.pow(10, final_decimal_places)
|
||||
trunc(result * decimal_power_round) / decimal_power_round
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
defp add_dot(acc, true), do: acc
|
||||
defp add_dot(acc, false), do: acc <> ".0"
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
Floor also accepts a precision to round a floating point value down
|
||||
to an arbitrary number of fractional digits (between 0 and 15).
|
||||
|
||||
This function always returns floats. One may use `Kernel.trunc/1` to
|
||||
truncate the result to an integer afterwards.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34)
|
||||
34
|
||||
|
||||
iex> Float.floor(34.25)
|
||||
34
|
||||
34.0
|
||||
|
||||
iex> Float.floor(-56.5)
|
||||
-57
|
||||
-57.0
|
||||
|
||||
iex> Float.floor(34.253, 2)
|
||||
34.25
|
||||
|
||||
"""
|
||||
@spec floor(float | integer) :: integer
|
||||
def floor(num) when is_integer(num), do: num
|
||||
def floor(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num < 0 -> truncated - 1
|
||||
_ -> truncated
|
||||
end
|
||||
@spec floor(float, 0..15) :: float
|
||||
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||
power = power_of_10(precision)
|
||||
number = number * power
|
||||
truncated = trunc(number)
|
||||
variance = if number - truncated < 0, do: -1.0, else: 0.0
|
||||
(truncated + variance) / power
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest integer greater than or equal to `num`.
|
||||
|
||||
Ceil also accepts a precision to round a floating point value down to
|
||||
an arbitrary number of fractional digits (between 0 and 15).
|
||||
|
||||
This function always returns floats. One may use `Kernel.trunc/1` to
|
||||
truncate the result to an integer afterwards.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.ceil(34)
|
||||
34
|
||||
|
||||
iex> Float.ceil(34.25)
|
||||
35
|
||||
35.0
|
||||
|
||||
iex> Float.ceil(-56.5)
|
||||
-56
|
||||
-56.0
|
||||
|
||||
iex> Float.ceil(34.253, 2)
|
||||
34.26
|
||||
|
||||
"""
|
||||
@spec ceil(float | integer) :: integer
|
||||
def ceil(num) when is_integer(num), do: num
|
||||
def ceil(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num > 0 -> truncated + 1
|
||||
_ -> truncated
|
||||
end
|
||||
@spec ceil(float, 0..15) :: float
|
||||
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||
power = power_of_10(precision)
|
||||
number = number * power
|
||||
truncated = trunc(number)
|
||||
variance = if number - truncated > 0, do: 1.0, else: 0.0
|
||||
(truncated + variance) / power
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a floating point value to an arbitrary number of fractional digits
|
||||
(between 0 and 15).
|
||||
|
||||
This function only accepts floats and returns floats. Use `Kernel.round/1`
|
||||
if you want a function that accepts both floats and integers and always
|
||||
returns an integer.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
@@ -165,9 +145,15 @@ defmodule Float do
|
||||
-5.568
|
||||
|
||||
"""
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
@spec round(float, 0..15) :: float
|
||||
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||
power = power_of_10(precision)
|
||||
Kernel.round(number * power) / power
|
||||
end
|
||||
|
||||
Enum.reduce 0..15, 1, fn x, acc ->
|
||||
defp power_of_10(unquote(x)), do: unquote(acc)
|
||||
acc * 10
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+674
-288
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
defmodule GenEvent.Stream do
|
||||
@moduledoc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
|
||||
"""
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
manager: GenEvent.manager,
|
||||
timeout: timeout}
|
||||
|
||||
@doc false
|
||||
def init({_pid, _ref} = state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, _state) do
|
||||
exit({:bad_event, event})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _state) do
|
||||
exit({:bad_call, msg})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{:ack, manager, ref, event}, acc ->
|
||||
send manager, {ref, :ok}
|
||||
fun.(event, acc)
|
||||
{:async, _manager, _ref, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{:sync, manager, ref, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :ok}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager} = stream) do
|
||||
try do
|
||||
{:ok, {pid, ref}} = :gen.call(manager, self(),
|
||||
{:add_process_handler, self(), self()}, :infinity)
|
||||
mon_ref = Process.monitor(pid)
|
||||
{pid, ref, mon_ref}
|
||||
catch
|
||||
:exit, reason -> exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {pid, ref, mon_ref} = acc) do
|
||||
self = self()
|
||||
|
||||
receive do
|
||||
# Got an async event.
|
||||
{_from, {^pid, ^ref}, {:notify, event}} ->
|
||||
{[{:async, pid, ref, event}], acc}
|
||||
|
||||
# Got a sync event.
|
||||
{_from, {^pid, ^ref}, {:sync_notify, event}} ->
|
||||
{[{:sync, pid, ref, event}], acc}
|
||||
|
||||
# Got an ack event.
|
||||
{_from, {^pid, ^ref}, {:ack_notify, event}} ->
|
||||
{[{:ack, pid, ref, event}], acc}
|
||||
|
||||
# The handler was removed. Stop iteration, resolve the
|
||||
# event later. We need to demonitor now, otherwise DOWN
|
||||
# appears with higher priority in the shutdown process.
|
||||
{:gen_event_EXIT, {^pid, ^ref}, _reason} = event ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
|
||||
# The manager died. Stop iteration, resolve the event later.
|
||||
{:DOWN, ^mon_ref, _, _, _} = event ->
|
||||
send(self, event)
|
||||
{:halt, {:removed, acc}}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, we know the handler was already
|
||||
# removed, so we don't trigger a request for doing so.
|
||||
defp stop(stream, {:removed, {pid, ref, mon_ref} = acc}) do
|
||||
case wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
:ok ->
|
||||
flush_events(ref)
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
# If we reach this branch, the handler was not removed yet,
|
||||
# so we trigger a request for doing so.
|
||||
defp stop(stream, {pid, ref, _} = acc) do
|
||||
_ = GenEvent.remove_handler(pid, {pid, ref}, :shutdown)
|
||||
stop(stream, {:removed, acc})
|
||||
end
|
||||
|
||||
defp wait_for_handler_removal(pid, ref, mon_ref) do
|
||||
receive do
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason}
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 3 and elem(reason, 0) == :swapped ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:gen_event_EXIT, {^pid, ^ref}, reason} ->
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
{:error, reason}
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(ref) do
|
||||
receive do
|
||||
{_from, {_pid, ^ref}, {notify, _event}} when notify in [:notify, :ack_notify, :sync_notify] ->
|
||||
flush_events(ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@ defmodule GenServer do
|
||||
## Example
|
||||
|
||||
The GenServer behaviour abstracts the common client-server interaction.
|
||||
Developer are only required to implement the callbacks and functionality they are
|
||||
Developers are only required to implement the callbacks and functionality they are
|
||||
interested in.
|
||||
|
||||
Let's start with a code example and then explore the available callbacks.
|
||||
@@ -349,18 +349,57 @@ defmodule GenServer do
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
try do
|
||||
:gen.call(server, :"$gen_call", request, timeout)
|
||||
catch
|
||||
:exit, reason ->
|
||||
exit({reason, {__MODULE__, :call, [server, request, timeout]}})
|
||||
else
|
||||
{:ok, res} -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, regardless of whether the
|
||||
destination node or server does exists. `handle_cast/2` will be called on the
|
||||
server to handle the request.
|
||||
This function returns `:ok` immediately, regardless of
|
||||
whether the destination node or server does exists, unless
|
||||
the server is specified as an atom.
|
||||
|
||||
`handle_cast/2` will be called on the server to handle
|
||||
the request. In case the server is a node which is not
|
||||
yet connected to the caller one, the call is going to
|
||||
block until a connection happens. This is different than
|
||||
the behaviour in OTP's `:gen_server` where the message
|
||||
would be sent by another process, which could cause
|
||||
messages to arrive out of order.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
def cast(server, request)
|
||||
|
||||
def cast({:global, name}, request) do
|
||||
try do
|
||||
:global.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({:via, mod, name}, request) do
|
||||
try do
|
||||
mod.send(name, cast_msg(request))
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
def cast({name, node}, request) when is_atom(name) and is_atom(node),
|
||||
do: do_send({name, node}, cast_msg(request))
|
||||
|
||||
def cast(dest, request) when is_atom(dest) or is_pid(dest),
|
||||
do: do_send(dest, cast_msg(request))
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
@@ -371,8 +410,19 @@ defmodule GenServer do
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
def abcast(nodes \\ nodes(), name, request) when is_list(nodes) and is_atom(name) do
|
||||
msg = cast_msg(request)
|
||||
_ = for node <- nodes, do: do_send({name, node}, msg)
|
||||
:abcast
|
||||
end
|
||||
|
||||
defp cast_msg(req) do
|
||||
{:"$gen_cast", req}
|
||||
end
|
||||
|
||||
defp do_send(dest, msg) do
|
||||
send(dest, msg)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule HashDict do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule HashSet do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
|
||||
+39
-39
@@ -212,29 +212,28 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
@doc false
|
||||
# Also used by Regex
|
||||
def escape_char(char) when char in ?\000..?\377,
|
||||
do: octify(char)
|
||||
|
||||
def escape_char(char), do: hexify(char)
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
def escape_char(0) do
|
||||
<<?\\, ?0>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x10000 do
|
||||
def escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::size(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)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x1000000 do
|
||||
def escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}>>
|
||||
end
|
||||
|
||||
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 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(<<>>, binary), do: binary
|
||||
@@ -272,25 +271,6 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc ~S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
printed in keywords syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
@@ -298,9 +278,9 @@ defimpl Inspect, for: List do
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
surround_many("[", thing, "]", opts, &keyword/2)
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
|
||||
surround_many("[", thing, "]", opts, &to_doc/2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -347,7 +327,7 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts, &to_doc/2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -358,14 +338,14 @@ defimpl Inspect, for: Map do
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
map = :maps.to_list(map)
|
||||
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
|
||||
surround_many("%" <> name <> "{", map, "}", opts, traverse_fun(map))
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
defp traverse_fun(list) do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword(&1, opts)
|
||||
&Inspect.List.keyword/2
|
||||
else
|
||||
&to_map(&1, opts)
|
||||
&to_map/2
|
||||
end
|
||||
end
|
||||
|
||||
@@ -378,8 +358,28 @@ defimpl Inspect, for: Map do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
def inspect(thing, _opts) do
|
||||
Integer.to_string(thing)
|
||||
def inspect(thing, %Inspect.Opts{base: base}) do
|
||||
Integer.to_string(thing, base_to_value(base))
|
||||
|> prepend_prefix(base)
|
||||
end
|
||||
|
||||
defp base_to_value(base) do
|
||||
case base do
|
||||
:binary -> 2
|
||||
:decimal -> 10
|
||||
:octal -> 8
|
||||
:hex -> 16
|
||||
end
|
||||
end
|
||||
|
||||
defp prepend_prefix(value, :decimal), do: value
|
||||
defp prepend_prefix(value, base) do
|
||||
prefix = case base do
|
||||
:binary -> "0b"
|
||||
:octal -> "0o"
|
||||
:hex -> "0x"
|
||||
end
|
||||
prefix <> value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -29,15 +29,30 @@ defmodule Inspect.Opts do
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false.
|
||||
|
||||
* `:width` - defaults to the 80 characters.
|
||||
* `:width` - defaults to the 80 characters, used when pretty is true or
|
||||
when printing to IO devices.
|
||||
|
||||
* `:base` - print integers as :binary, :octal, :decimal, or :hex, defaults
|
||||
to :decimal
|
||||
|
||||
"""
|
||||
|
||||
defstruct structs: true :: boolean,
|
||||
binaries: :infer :: :infer | :as_binaries | :as_strings,
|
||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
||||
limit: 50 :: pos_integer,
|
||||
width: 80 :: pos_integer | :infinity,
|
||||
pretty: false :: boolean
|
||||
defstruct structs: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
width: 80,
|
||||
base: :decimal,
|
||||
pretty: false
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
structs: boolean,
|
||||
binaries: :infer | :as_binaries | :as_strings,
|
||||
char_lists: :infer | :as_lists | :as_char_lists,
|
||||
limit: pos_integer,
|
||||
width: pos_integer | :infinity,
|
||||
base: :decimal | :binary | :hex | :octal,
|
||||
pretty: boolean}
|
||||
end
|
||||
|
||||
defmodule Inspect.Algebra do
|
||||
@@ -58,8 +73,8 @@ defmodule Inspect.Algebra do
|
||||
elements together and render them:
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"foo"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["foo"]
|
||||
|
||||
The functions `nest/2`, `space/2` and `line/2` help you put the
|
||||
document together into a rigid structure. However, the document
|
||||
@@ -69,15 +84,15 @@ defmodule Inspect.Algebra do
|
||||
render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["a", " ", "b"]
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
iex> Inspect.Algebra.format(doc, 10)
|
||||
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
|
||||
|
||||
Finally, this module also contains Elixir related functions, a bit
|
||||
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
|
||||
@@ -170,10 +185,29 @@ defmodule Inspect.Algebra do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
stacktrace = System.stacktrace
|
||||
|
||||
# Because we try to raise a nice error message in case
|
||||
# we can't inspect a struct, there is a chance the error
|
||||
# message itself relies on the struct being printed, so
|
||||
# we need to trap the inspected messages to guarantee
|
||||
# we won't try to render any failed instruct when building
|
||||
# the error message.
|
||||
if Process.get(:inspect_trap) do
|
||||
Inspect.Map.inspect(map, opts)
|
||||
else
|
||||
try do
|
||||
Process.put(:inspect_trap, true)
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
formatted = IO.iodata_to_binary(format(res, :infinity))
|
||||
reraise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{formatted}",
|
||||
stacktrace
|
||||
after
|
||||
Process.delete(:inspect_trap)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
@@ -201,9 +235,9 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"TastelessArtosis"
|
||||
iex> doc = Inspect.Algebra.concat "hello", "world"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["hello", "world"]
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons
|
||||
@@ -227,8 +261,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"hello\n world"
|
||||
iex> Inspect.Algebra.format(doc, 5)
|
||||
["hello", "\n ", "world"]
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@@ -252,15 +286,15 @@ defmodule Inspect.Algebra do
|
||||
Let's glue two docs together with a break and then render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["a", " ", "b"]
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
iex> Inspect.Algebra.format(doc, 10)
|
||||
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break
|
||||
@@ -303,10 +337,10 @@ defmodule Inspect.Algebra do
|
||||
...> "B"
|
||||
...> )
|
||||
...> ))
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hello, A B"
|
||||
iex> Inspect.Algebra.pretty(doc, 6)
|
||||
"Hello,\nA B"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hello,", " ", "A", " ", "B"]
|
||||
iex> Inspect.Algebra.format(doc, 6)
|
||||
["Hello,", "\n", "A", " ", "B"]
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
@@ -320,8 +354,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes Wadler"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", " ", "Wadler"]
|
||||
|
||||
"""
|
||||
@spec space(t, t) :: doc_cons
|
||||
@@ -333,8 +367,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes\nWadler"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["Hughes", "\n", "Wadler"]
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons
|
||||
@@ -350,8 +384,8 @@ defmodule Inspect.Algebra do
|
||||
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
|
||||
...> Inspect.Algebra.concat [x, "!", y]
|
||||
...> end)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"A!B"
|
||||
iex> Inspect.Algebra.format(doc, 80)
|
||||
["A", "!", "B"]
|
||||
|
||||
"""
|
||||
@spec folddoc([t], ((t, t) -> t)) :: t
|
||||
@@ -371,8 +405,8 @@ defmodule Inspect.Algebra do
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
|
||||
iex> Inspect.Algebra.pretty(doc, 3)
|
||||
"[a\n b]"
|
||||
iex> Inspect.Algebra.format(doc, 3)
|
||||
["[", "a", "\n ", "b", "]"]
|
||||
|
||||
"""
|
||||
@spec surround(binary, t, binary) :: t
|
||||
@@ -381,55 +415,63 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
Maps and glues a collection of items.
|
||||
|
||||
It uses the given left and right as surrounding and a separator for
|
||||
each item. A limit can be passed which, once reached, stops gluing
|
||||
and outputs "..." instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
|
||||
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
|
||||
...> %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
|
||||
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
|
||||
@spec surround_many(binary, [any], binary, Inspect.Opts.t, (term, Inspect.Opts.t -> t), binary) :: t
|
||||
def surround_many(left, docs, right, opts, fun, separator \\ @surround_separator) do
|
||||
do_surround_many(left, docs, right, opts.limit, opts, fun, separator)
|
||||
end
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
defp do_surround_many(left, [], right, _, _opts, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun, sep) do
|
||||
surround(left, surround_many(docs, limit, fun, sep), right)
|
||||
defp do_surround_many(left, docs, right, limit, _opts, fun, sep) do
|
||||
surround(left, do_surround_many(docs, limit, _opts, fun, sep), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun, _sep) do
|
||||
defp do_surround_many(_, 0, _opts, _fun, _sep) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun, _sep) do
|
||||
fun.(h)
|
||||
defp do_surround_many([h], limit, opts, fun, _sep) do
|
||||
fun.(h, %{opts | limit: limit})
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
|
||||
defp do_surround_many([h|t], limit, opts, fun, sep) when is_list(t) do
|
||||
limit = decrement(limit)
|
||||
glue(
|
||||
concat(fun.(h), sep),
|
||||
surround_many(t, decrement(limit), fun, sep)
|
||||
concat(fun.(h, %{opts | limit: limit}), sep),
|
||||
do_surround_many(t, limit, opts, fun, sep)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun, _sep) do
|
||||
defp do_surround_many([h|t], limit, opts, fun, _sep) do
|
||||
limit = decrement(limit)
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
concat(fun.(h, %{opts | limit: limit}), @tail_separator),
|
||||
fun.(t, %{opts | limit: limit})
|
||||
)
|
||||
end
|
||||
|
||||
@@ -437,50 +479,45 @@ defmodule Inspect.Algebra do
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc """
|
||||
The pretty printing function.
|
||||
The formatting function.
|
||||
|
||||
Takes the maximum width and a document to print as its arguments
|
||||
and returns the string representation of the best layout for the
|
||||
and returns an IO data representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
|
||||
render(sdoc)
|
||||
@spec format(t, non_neg_integer | :infinity) :: iodata
|
||||
def format(d, w) do
|
||||
format(w, 0, [{0, default_mode(w), doc_group(d)}])
|
||||
end
|
||||
|
||||
defp default_mode(:infinity), do: :flat
|
||||
defp default_mode(_), do: :break
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# Record representing the document mode to be rendered: flat or broken
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{integer, mode, t}]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
defp fits?(w, _) when w < 0, do: false
|
||||
defp fits?(_, []), do: true
|
||||
defp fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
defp fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
defp fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
defp fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
defp fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
defp fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
defp fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
defp fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
defp format(_, _, []), do: []
|
||||
defp format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
defp format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
defp format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
defp format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
defp format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
defp format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
defp format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
@@ -492,10 +529,4 @@ defmodule Inspect.Algebra do
|
||||
|
||||
defp indent(0), do: @newline
|
||||
defp indent(i), do: @newline <> :binary.copy(" ", i)
|
||||
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
IO.iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,9 +9,10 @@ defmodule Integer do
|
||||
Determines if an integer is odd.
|
||||
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
defmacro is_odd(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@@ -19,9 +20,10 @@ defmodule Integer do
|
||||
Determines if an integer is even.
|
||||
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
|
||||
Allowed in guard clauses.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
defmacro is_even(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@@ -99,7 +101,7 @@ defmodule Integer do
|
||||
"64"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, pos_integer) :: String.t
|
||||
@spec to_string(integer, 2..36) :: String.t
|
||||
def to_string(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
@@ -115,7 +117,7 @@ defmodule Integer do
|
||||
'7'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer) :: list
|
||||
@spec to_char_list(integer) :: char_list
|
||||
def to_char_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
@@ -132,7 +134,7 @@ defmodule Integer do
|
||||
'3FF'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer, pos_integer) :: list
|
||||
@spec to_char_list(integer, 2..36) :: char_list
|
||||
def to_char_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
|
||||
+61
-20
@@ -43,8 +43,10 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
Reads `count` characters from the IO device, a whole
|
||||
`:line` or the whole device with `:all`.
|
||||
|
||||
It returns:
|
||||
|
||||
* `data` - the input characters
|
||||
|
||||
@@ -53,10 +55,17 @@ defmodule IO do
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
|
||||
If `:all` is given, `:eof` is never returned, but an
|
||||
empty string in case the device has reached EOF.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
@spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :all) do
|
||||
do_read_all(map_dev(device), "")
|
||||
end
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), '')
|
||||
end
|
||||
@@ -65,9 +74,19 @@ defmodule IO do
|
||||
:io.get_chars(map_dev(device), '', count)
|
||||
end
|
||||
|
||||
defp do_read_all(mapped_dev, acc) do
|
||||
case :io.get_line(mapped_dev, "") do
|
||||
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
|
||||
:eof -> acc
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
Reads `count` characters from the IO device, a whole
|
||||
`:line` or the whole device with `:all`.
|
||||
|
||||
It returns:
|
||||
|
||||
* `data` - the input characters
|
||||
|
||||
@@ -76,10 +95,20 @@ defmodule IO do
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
|
||||
If `:all` is given, `:eof` is never returned, but an
|
||||
empty string in case the device has reached EOF.
|
||||
|
||||
Note: do not use this function on IO devices in unicode mode
|
||||
as it will return the wrong result.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
@spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :all) do
|
||||
do_binread_all(map_dev(device), "")
|
||||
end
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{:ok, data} -> data
|
||||
@@ -94,6 +123,15 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
@read_all_size 4096
|
||||
defp do_binread_all(mapped_dev, acc) do
|
||||
case :file.read(mapped_dev, @read_all_size) do
|
||||
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
|
||||
:eof -> acc
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
|
||||
@@ -119,6 +157,9 @@ defmodule IO do
|
||||
as a binary, no unicode conversion happens.
|
||||
|
||||
Check `write/2` for more information.
|
||||
|
||||
Note: do not use this function on IO devices in unicode mode
|
||||
as it will return the wrong result.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
|
||||
@@ -139,17 +180,13 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device.
|
||||
|
||||
It sets by default pretty printing to true and returns
|
||||
the item itself.
|
||||
|
||||
Note this function does not use the IO device width
|
||||
because some IO devices does not implement the
|
||||
appropriate functions. Setting the width must be done
|
||||
explicitly by passing the `:width` option.
|
||||
It enables pretty printing by default with width of
|
||||
80 characters. The width can be changed by explicitly
|
||||
passing the `:width` option.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.inspect Process.list
|
||||
IO.inspect Process.list, width: 40
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
@@ -162,8 +199,9 @@ defmodule IO do
|
||||
"""
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
opts = struct(Inspect.Opts, opts)
|
||||
iodata = Inspect.Algebra.format(Inspect.Algebra.to_doc(item, opts), opts.width)
|
||||
puts device, iodata
|
||||
item
|
||||
end
|
||||
|
||||
@@ -263,6 +301,9 @@ defmodule IO do
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
|
||||
Finally, do not use this function on IO devices in unicode
|
||||
mode as it will return the wrong result.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
@@ -354,11 +395,11 @@ defmodule IO do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -366,11 +407,11 @@ defmodule IO do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{:halt, device}
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{data, device}
|
||||
{[data], device}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+49
-68
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(unquote(Atom.to_char_list(name))) do
|
||||
defp format_sequence(unquote(name)) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
@@ -24,19 +24,21 @@ defmodule IO.ANSI do
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
|
||||
@doc """
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
@typep ansicode :: atom()
|
||||
@typep ansilist :: maybe_improper_list(char() | ansicode() | binary() | ansilist(), binary() | ansicode() | [])
|
||||
@type ansidata :: ansilist() | ansicode() | binary()
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be displayed as intended. This is checked by sending a message to
|
||||
the group leader. In case the group leader does not support the message,
|
||||
it will likely lead to a timeout (and a slow down on execution time).
|
||||
@doc """
|
||||
Checks if ANSI coloring is supported and enabled on this machine.
|
||||
|
||||
This function simply reads the configuration value for
|
||||
`:ansi_enabled` in the `:elixir` application. The value is by
|
||||
default false unless Elixir can detect during startup that
|
||||
both `stdout` and `stderr` are terminals.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
!match?({:win32, _}, :os.type()) and
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
@spec enabled? :: boolean
|
||||
def enabled? do
|
||||
Application.get_env(:elixir, :ansi_enabled, false)
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -130,99 +132,78 @@ defmodule IO.ANSI do
|
||||
@doc "Clear screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
defp escape_sequence(other) do
|
||||
defp format_sequence(other) do
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
Formats a chardata-like argument by converting named ANSI sequences into actual
|
||||
ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
It will also append a `%{reset}` to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/2`.
|
||||
It will also append an `IO.ANSI.reset` to the chardata when a conversion is
|
||||
performed. If you don't want this behaviour, use `format_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
|
||||
[[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"]
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
end
|
||||
def format(chardata, emit \\ enabled?) when is_boolean(emit) do
|
||||
do_format(chardata, [], [], emit, :maybe)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
Formats a chardata-like argument by converting named ANSI sequences into actual
|
||||
ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
The named sequences are represented by atoms.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
By default checks if ANSI is enabled using the `enabled?/0` function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
iex> IO.ANSI.format_fragment([:bright, 'Word'], true)
|
||||
[[[[[[] | "\e[1m"], 87], 111], 114], 100]
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
def format_fragment(chardata, emit \\ enabled?) when is_boolean(emit) do
|
||||
do_format(chardata, [], [], emit, false)
|
||||
end
|
||||
|
||||
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
sequences =
|
||||
buffer
|
||||
|> Enum.reverse()
|
||||
|> :string.tokens(',')
|
||||
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|
||||
|> Enum.reverse()
|
||||
|
||||
if emit and sequences != [] do
|
||||
do_escape(t, emit, true, nil, sequences ++ acc)
|
||||
else
|
||||
do_escape(t, emit, emitted, nil, acc)
|
||||
end
|
||||
defp do_format([term | rest], rem, acc, emit, append_reset) do
|
||||
do_format(term, [rest | rem], acc, emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
do_escape(t, emit, emitted, [h|buffer], acc)
|
||||
defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do
|
||||
do_format([], rem, [acc | format_sequence(term)], true, !!append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = IO.iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, "missing } for escape fragment #{buffer}"
|
||||
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
|
||||
do_format([], rem, acc, false, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, [], acc)
|
||||
defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do
|
||||
do_format([], rem, [acc | [term]], emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, nil, [h|acc])
|
||||
defp do_format([], [next | rest], acc, emit, append_reset) do
|
||||
do_format(next, rest, acc, emit, append_reset)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
defp do_format([], [], acc, true, true) do
|
||||
[acc | IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp do_format([], [], acc, _emit, _append_reset) do
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
+295
-149
@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_table_heading` - style for table headings
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
def default_options do
|
||||
[enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline",
|
||||
width: 80]
|
||||
[enabled: true,
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan, :bright],
|
||||
doc_headings: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow],
|
||||
doc_underline: [:underline],
|
||||
width: 80]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a set of options
|
||||
In addition to the printing string, takes a set of options
|
||||
defined in `default_options/1`.
|
||||
"""
|
||||
def print(doc, options \\ []) do
|
||||
@@ -56,52 +59,63 @@ defmodule IO.ANSI.Docs do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", options)
|
||||
|> process([], "", options)
|
||||
end
|
||||
|
||||
defp process([], _indent, _options), do: nil
|
||||
defp process([], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
end
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, options) do
|
||||
defp process(["# " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h1(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, options) do
|
||||
defp process(["## " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h2(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, options) do
|
||||
defp process(["### " <> heading | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
write_h3(String.strip(heading), indent, options)
|
||||
process(rest, indent, options)
|
||||
process(rest, [], "", options)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, options) do
|
||||
process(rest, indent, options)
|
||||
defp process(["" | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, options) do
|
||||
defp process([" " <> line | rest], text, indent, options) do
|
||||
write_text(text, indent, options)
|
||||
process_code(rest, [line], indent, options)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false, options)
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_spaces(" " <> line, acc) do
|
||||
strip_spaces(line, acc + 1)
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{rest, acc}
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, options) do
|
||||
@@ -110,91 +124,81 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp write_h2(heading, options) do
|
||||
write(:doc_headings, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, options) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, options) do
|
||||
IO.write indent <> "• "
|
||||
{contents, rest, done} = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, options)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, options)
|
||||
defp process_list(entry, line, rest, count, indent, options) do
|
||||
# The first list always win some extra padding
|
||||
if indent == "", do: entry = " " <> entry
|
||||
new_indent = indent <> String.duplicate(" ", String.length(entry))
|
||||
|
||||
{contents, rest, done} = process_list_next(rest, count, byte_size(new_indent), [])
|
||||
process(contents, [indent <> entry <> line, :no_wrap], new_indent, options)
|
||||
|
||||
if done, do: newline_after_block()
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
#
|
||||
# * Continuation of the list
|
||||
# * A nested list
|
||||
# * The end of the list
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
:done -> {Enum.reverse(acc), [line|rest], false}
|
||||
chopped -> process_list_next(rest, count, false, [chopped|acc])
|
||||
defp process_list_next([line | rest], count, max, acc) do
|
||||
{stripped, next_count} = strip_spaces(line, 0, max)
|
||||
case process_list_next_kind(stripped, rest, count, next_count) do
|
||||
:next -> process_list_next(rest, count, max, [stripped | acc])
|
||||
:done -> {Enum.reverse(acc), [line | rest], true}
|
||||
:list -> {Enum.reverse(acc), [line | rest], false}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
|
||||
{Enum.reverse(acc), rest, false}
|
||||
defp process_list_next([], _count, _max, acc) do
|
||||
{Enum.reverse(acc), [], true}
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
process_list_next(rest, count, true, [""|acc])
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{Enum.reverse(acc), rest, done}
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
defp list_next(line, 0), do: chop(line, 2)
|
||||
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
|
||||
defp list_next(line, _acc), do: line
|
||||
|
||||
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
|
||||
defp chop(line, _acc), do: line
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
process(doc, indent, options)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true, options)
|
||||
process_list(item, rest, count, indent, options)
|
||||
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 ->
|
||||
:list
|
||||
{<<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 ->
|
||||
:list
|
||||
{"", [" " <> _ | _]} ->
|
||||
:next
|
||||
{"", _} ->
|
||||
:done
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, options)
|
||||
:next
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, options) do
|
||||
process_text(rest, [line | para], indent, from_list, options)
|
||||
## Text
|
||||
|
||||
defp write_text(text, indent, options) do
|
||||
case Enum.reverse(text) do
|
||||
[:no_wrap|rest] -> write_text(rest, indent, options, true)
|
||||
rest -> write_text(rest, indent, options, false)
|
||||
end
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, options) do
|
||||
defp write_text([], _indent, _options, _no_wrap) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, options, no_wrap) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> handle_inline(options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, no_wrap)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
unless no_wrap, do: newline_after_block()
|
||||
end
|
||||
|
||||
## Code blocks
|
||||
@@ -204,28 +208,124 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, options) do
|
||||
defp process_code(["", " " <> line | rest], code, indent, options) do
|
||||
process_code(rest, [line, "" | code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, options) do
|
||||
defp process_code([" " <> line | rest], code, indent, options) do
|
||||
process_code(rest, [line|code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
process(rest, indent, options)
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, options) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
|
||||
newline_after_block
|
||||
end
|
||||
|
||||
## Tables
|
||||
|
||||
defp process_table(lines, indent, options) do
|
||||
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
|
||||
table_lines(table, options)
|
||||
newline_after_block
|
||||
process(rest, [], indent, options)
|
||||
end
|
||||
|
||||
defp table_lines(lines, options) do
|
||||
lines = Enum.map(lines, &split_into_columns(&1, options))
|
||||
count = Enum.map(lines, &length/1) |> Enum.max
|
||||
lines = Enum.map(lines, &pad_to_number_of_columns(&1, count))
|
||||
|
||||
widths = for line <- lines, do:
|
||||
(for {_col, length} <- line, do: length)
|
||||
|
||||
col_widths = Enum.reduce(widths,
|
||||
List.duplicate(0, count),
|
||||
&max_column_widths/2)
|
||||
|
||||
render_table(lines, col_widths, options)
|
||||
end
|
||||
|
||||
defp split_into_columns(line, options) do
|
||||
line
|
||||
|> String.strip(?|)
|
||||
|> String.strip()
|
||||
|> String.split(~r/\s\|\s/)
|
||||
|> Enum.map(&render_column(&1, options))
|
||||
end
|
||||
|
||||
defp render_column(col, options) do
|
||||
col = col
|
||||
|> String.replace(~r/\\ \|/x, "|")
|
||||
|> handle_links
|
||||
|> handle_inline(options)
|
||||
{col, length_without_escape(col, 0)}
|
||||
end
|
||||
|
||||
defp pad_to_number_of_columns(cols, col_count),
|
||||
do: cols ++ List.duplicate({"", 0}, col_count - length(cols))
|
||||
|
||||
defp max_column_widths(cols, widths),
|
||||
do: Enum.zip(cols, widths) |> Enum.map(fn {a,b} -> max(a,b) end)
|
||||
|
||||
# If second line is heading separator, use the heading style on the first
|
||||
defp render_table([first, second | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
if table_header?(second) do
|
||||
draw_table_row(combined, options, :heading)
|
||||
render_table(rest, widths, options)
|
||||
else
|
||||
draw_table_row(combined, options)
|
||||
render_table([second | rest], widths, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp render_table([first | rest], widths, options) do
|
||||
combined = Enum.zip(first, widths)
|
||||
draw_table_row(combined, options)
|
||||
render_table(rest, widths, options)
|
||||
end
|
||||
|
||||
defp render_table([], _, _),
|
||||
do: nil
|
||||
|
||||
defp table_header?(row), do:
|
||||
Enum.all?(row, fn {col, _} -> col =~ ~r/^:?-+:?$/ end)
|
||||
|
||||
defp draw_table_row(cols_and_widths, options, heading \\ false) do
|
||||
columns =
|
||||
Enum.map_join(cols_and_widths, " | ", fn {{col, length}, width} ->
|
||||
col <> String.duplicate(" ", width - length)
|
||||
end)
|
||||
|
||||
if heading do
|
||||
write(:doc_table_heading, columns, options)
|
||||
else
|
||||
IO.puts columns
|
||||
end
|
||||
end
|
||||
|
||||
defp is_table_line?(line) do
|
||||
Regex.match?(~r'''
|
||||
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
||||
|
|
||||
(\s \| \s)
|
||||
'''x, line)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp strip_spaces(" " <> line, acc, max) when acc < max,
|
||||
do: strip_spaces(line, acc + 1, max)
|
||||
defp strip_spaces(rest, acc, _max),
|
||||
do: {rest, acc}
|
||||
|
||||
defp write(style, string, options) do
|
||||
IO.puts color(style, options) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
IO.puts [color(style, options), string, IO.ANSI.reset]
|
||||
end
|
||||
|
||||
defp write_with_wrap([], _available, _indent, _first) do
|
||||
@@ -270,7 +370,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
@@ -283,10 +383,8 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
true -> Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ -> text
|
||||
end
|
||||
end
|
||||
|
||||
@@ -294,61 +392,104 @@ defmodule IO.ANSI.Docs do
|
||||
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
@single [?`, ?_, ?*]
|
||||
|
||||
# ` does not require space in between
|
||||
@spaced [?_, ?*]
|
||||
# We have four entries: **, *, _ and `.
|
||||
#
|
||||
# The first three behave the same while the last one is simpler
|
||||
# when it comes to delimiters. But, since the first has two
|
||||
# characters, we need to handle 3 cases:
|
||||
#
|
||||
# 1. **
|
||||
# 2. _ and *
|
||||
# 3. `
|
||||
#
|
||||
# Where the first two should have the same code but match differently.
|
||||
@single [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
|
||||
# Characters that can mark the beginning or the end of a word.
|
||||
# Only support the most common ones at this moment.
|
||||
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&, ?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
|
||||
|
||||
# Inline start
|
||||
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
|
||||
handle_inline(rest, ?d, ["**"], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
|
||||
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
|
||||
defp handle_inline(rest, options) do
|
||||
handle_inline(rest, nil, [], [], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
|
||||
# Inline delimiters
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
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
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
# An escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
when not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
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) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
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)
|
||||
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
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single 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
|
||||
handle_inline(rest, mark, [char|buffer], acc, options)
|
||||
@@ -363,14 +504,19 @@ defmodule IO.ANSI.Docs do
|
||||
[color_for(h, options)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
defp color_for("_", colors), do: color(:doc_underline, colors)
|
||||
defp color_for("*", colors), do: color(:doc_bold, colors)
|
||||
defp color_for("**", colors), do: color(:doc_bold, colors)
|
||||
defp color_for(mark, colors) do
|
||||
case mark do
|
||||
"`" -> color(:doc_inline_code, colors)
|
||||
"_" -> color(:doc_underline, colors)
|
||||
"*" -> color(:doc_bold, colors)
|
||||
"**" -> color(:doc_bold, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp color(style, colors) do
|
||||
color = colors[style]
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
IO.ANSI.format_fragment(color, colors[:enabled])
|
||||
end
|
||||
|
||||
defp newline_after_block, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
@@ -22,6 +22,8 @@ defmodule IO.Stream do
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc false
|
||||
def __build__(device, raw, line_or_bytes) do
|
||||
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
||||
@@ -55,7 +57,7 @@ defmodule IO.Stream do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
Stream.resource(fn -> device end, next_fun, &(&1)).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
|
||||
+296
-250
@@ -150,9 +150,9 @@ defmodule Kernel do
|
||||
3
|
||||
|
||||
"""
|
||||
@spec byte_size(bitstring) :: non_neg_integer
|
||||
def byte_size(bitstring) do
|
||||
:erlang.byte_size(bitstring)
|
||||
@spec byte_size(binary) :: non_neg_integer
|
||||
def byte_size(binary) do
|
||||
:erlang.byte_size(binary)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -183,9 +183,56 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
When a process reaches its end, by default it exits with
|
||||
reason `:normal`. You can also call it explicitly if you
|
||||
want to terminate a process but not signal any failure:
|
||||
|
||||
exit(:normal)
|
||||
|
||||
In case something goes wrong, you can also use `exit/1` with
|
||||
a different reason:
|
||||
|
||||
exit(:seems_bad)
|
||||
|
||||
If the reason is not `:normal`, all linked process to the
|
||||
exited process will crash (unless they are trapping exits).
|
||||
|
||||
## OTP exits
|
||||
|
||||
Exits are used by OTP to determine if a process exited abnormally
|
||||
or not. The following exits are considered "normal":
|
||||
|
||||
* `exit(:normal)`
|
||||
* `exit(:shutdown)`
|
||||
* `exit({:shutdown, term})`
|
||||
|
||||
Exiting with any other reason is considered abnormal and treated
|
||||
as a crash. This means the default supervisor behaviour kicks in,
|
||||
error reports are emitted, etc.
|
||||
|
||||
This behaviour is relied on in many different places. For example,
|
||||
`ExUnit` uses `exit(:shutdown)` when exiting the test process to
|
||||
signal linked processes, supervision trees and so on to politely
|
||||
shutdown too.
|
||||
|
||||
## CLI exits
|
||||
|
||||
Building on top of the exit signals mentioned above, if the
|
||||
process started by the command line exits with any of the three
|
||||
reasons above, its exit is considered normal and the Operating
|
||||
System process will exit with status 0.
|
||||
|
||||
It is, however, possible to customize the Operating System exit
|
||||
signal by invoking:
|
||||
|
||||
exit({:shutdown, integer})
|
||||
|
||||
This will cause the OS process to exit with the status given by
|
||||
`integer` while signaling all linked OTP processes to politely
|
||||
shutdown.
|
||||
|
||||
Any other exit reason will cause the OS process to exit with
|
||||
status `1` and linked OTP processes to crash.
|
||||
"""
|
||||
@spec exit(term) :: no_return
|
||||
def exit(reason) do
|
||||
@@ -838,11 +885,6 @@ defmodule Kernel do
|
||||
:erlang.--(left, right)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def left xor right do
|
||||
:erlang.xor(left, right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Boolean not. Argument must be a boolean.
|
||||
|
||||
@@ -1344,7 +1386,14 @@ defmodule Kernel do
|
||||
iex> "abcd" =~ "ad"
|
||||
false
|
||||
|
||||
iex> "abcd" =~ ""
|
||||
true
|
||||
|
||||
"""
|
||||
|
||||
@spec (String.t =~ (String.t | Regex.t)) :: boolean
|
||||
def left =~ "" when is_binary(left), do: true
|
||||
|
||||
def left =~ right when is_binary(left) and is_binary(right) do
|
||||
:binary.match(left, right) != :nomatch
|
||||
end
|
||||
@@ -1372,14 +1421,26 @@ defmodule Kernel do
|
||||
iex> inspect [1, 2, 3, 4, 5], limit: 3
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> inspect("josé" <> <<0>>)
|
||||
"<<106, 111, 115, 195, 169, 0>>"
|
||||
iex> inspect("olá" <> <<0>>)
|
||||
"<<111, 108, 195, 161, 0>>"
|
||||
|
||||
iex> inspect("josé" <> <<0>>, binaries: :as_strings)
|
||||
"\"josé\\000\""
|
||||
iex> inspect("olá" <> <<0>>, binaries: :as_strings)
|
||||
"\"olá\\0\""
|
||||
|
||||
iex> inspect("josé", binaries: :as_binaries)
|
||||
"<<106, 111, 115, 195, 169>>"
|
||||
iex> inspect("olá", binaries: :as_binaries)
|
||||
"<<111, 108, 195, 161>>"
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect(100, base: :octal)
|
||||
"0o144"
|
||||
|
||||
iex> inspect(100, base: :hex)
|
||||
"0x64"
|
||||
|
||||
Note that the inspect protocol does not necessarily return a valid
|
||||
representation of an Elixir term. In such cases, the inspected result
|
||||
@@ -1396,7 +1457,9 @@ defmodule Kernel do
|
||||
true -> opts.width
|
||||
false -> :infinity
|
||||
end
|
||||
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit)
|
||||
IO.iodata_to_binary(
|
||||
Inspect.Algebra.format(Inspect.Algebra.to_doc(arg, opts), limit)
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1415,18 +1478,18 @@ defmodule Kernel do
|
||||
## Example
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "jose"
|
||||
defstruct name: "john"
|
||||
end
|
||||
|
||||
struct(User)
|
||||
#=> %User{name: "jose"}
|
||||
#=> %User{name: "john"}
|
||||
|
||||
opts = [name: "eric"]
|
||||
opts = [name: "meg"]
|
||||
user = struct(User, opts)
|
||||
#=> %User{name: "eric"}
|
||||
#=> %User{name: "meg"}
|
||||
|
||||
struct(user, unknown: "value")
|
||||
#=> %User{name: "eric"}
|
||||
#=> %User{name: "meg"}
|
||||
|
||||
"""
|
||||
@spec struct(module | map, Enum.t) :: map
|
||||
@@ -1453,47 +1516,77 @@ defmodule Kernel do
|
||||
Gets a value from a nested structure.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
according to the given `keys`.
|
||||
according to the given `keys`, unless the `key` is a
|
||||
function.
|
||||
|
||||
If a key is a function, the function will be invoked
|
||||
passing three arguments, the operation (`:get`), the
|
||||
data to be accessed, and a function to be invoked next.
|
||||
|
||||
This means `get_in/2` can be extended to provide
|
||||
custom lookups. The downside is that functions cannot be
|
||||
stored as keys in the accessed data structures.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_in(users, ["josé", :age])
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, ["john", :age])
|
||||
27
|
||||
|
||||
In case any of entries in the middle returns `nil`, `nil` will be returned
|
||||
as per the Access protocol:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, ["unknown", :age])
|
||||
nil
|
||||
|
||||
When one of the keys is a function, the function is invoked.
|
||||
In the example below, we use a function to get all the maps
|
||||
inside a list:
|
||||
|
||||
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
|
||||
iex> all = fn :get, data, next -> Enum.map(data, next) end
|
||||
iex> get_in(users, [all, :age])
|
||||
[27, 23]
|
||||
|
||||
If the previous value before invoking the function is nil,
|
||||
the function *will* receive nil as a value and must handle it
|
||||
accordingly.
|
||||
"""
|
||||
@spec get_in(Access.t, nonempty_list(term)) :: term
|
||||
def get_in(data, keys)
|
||||
def get_in(nil, list) when is_list(list), do: nil
|
||||
def get_in(data, [h]), do: Access.get(data, h)
|
||||
def get_in(data, [h|t]), do: get_in(Access.get(data, h), t)
|
||||
|
||||
def get_in(data, [h]) when is_function(h),
|
||||
do: h.(:get, data, &(&1))
|
||||
def get_in(data, [h|t]) when is_function(h),
|
||||
do: h.(:get, data, &get_in(&1, t))
|
||||
|
||||
def get_in(nil, [_]),
|
||||
do: nil
|
||||
def get_in(nil, [_|t]),
|
||||
do: get_in(nil, t)
|
||||
|
||||
def get_in(data, [h]),
|
||||
do: Access.get(data, h)
|
||||
def get_in(data, [h|t]),
|
||||
do: get_in(Access.get(data, h), t)
|
||||
|
||||
@doc """
|
||||
Puts a value in a nested structure.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
according to the given `keys`.
|
||||
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`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users, ["josé", :age], 28)
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
|
||||
In case any of entries in the middle returns `nil`, a map is dynamically
|
||||
created:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users, ["dave", :age], 13)
|
||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users, ["john", :age], 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
In case any of entries in the middle returns `nil`,
|
||||
an error will be raised when trying to access it next.
|
||||
"""
|
||||
@spec put_in(Access.t, nonempty_list(term), term) :: Access.t
|
||||
def put_in(data, keys, value) do
|
||||
@@ -1504,21 +1597,18 @@ defmodule Kernel do
|
||||
Updates a key in a nested structure.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
according to the given `keys`.
|
||||
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`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users, ["josé", :age], &(&1 + 1))
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
|
||||
In case any of entries in the middle returns `nil`, a map is dynamically
|
||||
created:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users, ["dave", :age], &((&1 || 0) + 1))
|
||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 1}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> update_in(users, ["john", :age], &(&1 + 1))
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
In case any of entries in the middle returns `nil`,
|
||||
an error will be raised when trying to access it next.
|
||||
"""
|
||||
@spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t
|
||||
def update_in(data, keys, fun) do
|
||||
@@ -1528,9 +1618,20 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Gets a value and updates a nested structure.
|
||||
|
||||
It expects a tuple to be returned, containing the value retrieved
|
||||
and the update one. Uses the `Access` protocol to traverse the
|
||||
structures according to the given `keys`.
|
||||
It expects a tuple to be returned, containing the value
|
||||
retrieved and the update one.
|
||||
|
||||
Uses the `Access` protocol to traverse the structures
|
||||
according to the given `keys`, unless the `key` is a
|
||||
function.
|
||||
|
||||
If a key is a function, the function will be invoked
|
||||
passing three arguments, the operation (`:get_and_update`),
|
||||
the data to be accessed, and a function to be invoked next.
|
||||
|
||||
This means `get_and_update_in/3` can be extended to provide
|
||||
custom lookups. The downside is that functions cannot be stored
|
||||
as keys in the accessed data structures.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1539,27 +1640,38 @@ defmodule Kernel do
|
||||
update it at the same time. For example, it could be used to increase
|
||||
the age of a user by one and return the previous age in one pass:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1})
|
||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1})
|
||||
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
|
||||
|
||||
In case any of entries in the middle returns `nil`, a map is dynamically
|
||||
created:
|
||||
When one of the keys is a function, the function is invoked.
|
||||
In the example below, we use a function to get and increment all
|
||||
ages inside a list:
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_and_update_in(users, ["dave", :age], &{&1, 13})
|
||||
{nil, %{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}}
|
||||
iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}]
|
||||
iex> all = fn :get_and_update, data, next ->
|
||||
...> Enum.map(data, next) |> :lists.unzip
|
||||
...> end
|
||||
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
|
||||
{[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]}
|
||||
|
||||
If the previous value before invoking the function is nil,
|
||||
the function *will* receive `nil` as a value and must handle it
|
||||
accordingly (be it by failing or providing a sane default).
|
||||
"""
|
||||
@spec get_and_update_in(Access.t, nonempty_list(term),
|
||||
(term -> {get, term})) :: {get, Access.t} when get: var
|
||||
def get_and_update_in(data, keys, fun)
|
||||
|
||||
def get_and_update_in(nil, list, fun), do: get_and_update_in(%{}, list, fun)
|
||||
def get_and_update_in(data, [h], fun), do: Access.get_and_update(data, h, fun)
|
||||
def get_and_update_in(data, [h|t], fun) do
|
||||
Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
|
||||
end
|
||||
def get_and_update_in(data, [h], fun) when is_function(h),
|
||||
do: h.(:get_and_update, data, fun)
|
||||
def get_and_update_in(data, [h|t], fun) when is_function(h),
|
||||
do: h.(:get_and_update, data, &get_and_update_in(&1, t, fun))
|
||||
|
||||
def get_and_update_in(data, [h], fun),
|
||||
do: Access.get_and_update(data, h, fun)
|
||||
def get_and_update_in(data, [h|t], fun),
|
||||
do: Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
|
||||
|
||||
@doc """
|
||||
Puts a value in a nested structure via the given `path`.
|
||||
@@ -1579,13 +1691,13 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users["josé"][:age], 28)
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users["john"][:age], 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> put_in(users["josé"].age, 28)
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in(users["john"].age, 28)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
"""
|
||||
defmacro put_in(path, value) do
|
||||
@@ -1612,13 +1724,13 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users["josé"][:age], &(&1 + 1))
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> update_in(users["john"][:age], &(&1 + 1))
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> update_in(users["josé"].age, &(&1 + 1))
|
||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> update_in(users["john"].age, &(&1 + 1))
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
"""
|
||||
defmacro update_in(path, fun) do
|
||||
@@ -1644,9 +1756,9 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||
iex> get_and_update_in(users["josé"][:age], &{&1, &1 + 1})
|
||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_and_update_in(users["john"].age, &{&1, &1 + 1})
|
||||
{27, %{"john" => %{age: 28}, "meg" => %{age: 23}}}
|
||||
|
||||
## Paths
|
||||
|
||||
@@ -1661,15 +1773,15 @@ defmodule Kernel do
|
||||
|
||||
Here are some valid paths:
|
||||
|
||||
users["josé"][:age]
|
||||
users["josé"].age
|
||||
User.all["josé"].age
|
||||
all_users()["josé"].age
|
||||
users["john"][:age]
|
||||
users["john"].age
|
||||
User.all["john"].age
|
||||
all_users()["john"].age
|
||||
|
||||
Here are some invalid ones:
|
||||
|
||||
# Does a remote call after the initial value
|
||||
users["josé"].do_something(arg1, arg2)
|
||||
users["john"].do_something(arg1, arg2)
|
||||
|
||||
# Does not access any field
|
||||
users
|
||||
@@ -1690,7 +1802,7 @@ defmodule Kernel do
|
||||
defp nest_get_and_update_in(h, [{:access, key}|t], fun) do
|
||||
quote do
|
||||
Access.get_and_update(
|
||||
case(unquote(h), do: (nil -> %{}; o -> o)),
|
||||
unquote(h),
|
||||
unquote(key),
|
||||
unquote(nest_get_and_update_in(t, fun))
|
||||
)
|
||||
@@ -1708,13 +1820,15 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp unnest({{:., _, [expr, key]}, _, []}, acc, kind)
|
||||
when is_tuple(expr) and elem(expr, 0) != :__aliases__ and elem(expr, 0) != :__MODULE__ do
|
||||
when is_tuple(expr) and
|
||||
:erlang.element(1, expr) != :__aliases__ and
|
||||
:erlang.element(1, expr) != :__MODULE__ do
|
||||
unnest(expr, [{:map, key}|acc], kind)
|
||||
end
|
||||
|
||||
defp unnest(other, [], kind) do
|
||||
raise ArgumentError,
|
||||
"expected expression given to #{kind} to access at least one field, got: #{Macro.to_string other}"
|
||||
"expected expression given to #{kind} to access at least one element, got: #{Macro.to_string other}"
|
||||
end
|
||||
|
||||
defp unnest(other, acc, kind) do
|
||||
@@ -1723,14 +1837,14 @@ defmodule Kernel do
|
||||
false ->
|
||||
raise ArgumentError,
|
||||
"expression given to #{kind} must start with a variable, local or remote call " <>
|
||||
"and be followed by field access, got: #{Macro.to_string other}"
|
||||
"and be followed by an element access, got: #{Macro.to_string other}"
|
||||
end
|
||||
end
|
||||
|
||||
defp proper_start?({{:., _, [expr, _]}, _, _args})
|
||||
when is_atom(expr)
|
||||
when elem(expr, 0) == :__aliases__
|
||||
when elem(expr, 0) == :__MODULE__, do: true
|
||||
when :erlang.element(1, expr) == :__aliases__
|
||||
when :erlang.element(1, expr) == :__MODULE__, do: true
|
||||
|
||||
defp proper_start?({atom, _, _args})
|
||||
when is_atom(atom), do: true
|
||||
@@ -1776,20 +1890,20 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> nil?(1)
|
||||
iex> is_nil(1)
|
||||
false
|
||||
|
||||
iex> nil?(nil)
|
||||
iex> is_nil(nil)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro nil?(x) do
|
||||
defmacro is_nil(x) do
|
||||
quote do: unquote(x) == nil
|
||||
end
|
||||
|
||||
@doc """
|
||||
A convenient macro that checks if the right side matches
|
||||
the left side. The left side is allowed to be a match pattern.
|
||||
A convenience macro that checks if the right side (an expression)
|
||||
matches the left side (a pattern).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1894,10 +2008,7 @@ defmodule Kernel do
|
||||
|
||||
case is_list(args) and length(args) == 1 and typespec(name) do
|
||||
false ->
|
||||
case name == :typedoc and not bootstraped?(Kernel.Typespec) do
|
||||
true -> nil
|
||||
false -> do_at(args, name, function?, __CALLER__)
|
||||
end
|
||||
do_at(args, name, function?, __CALLER__)
|
||||
macro ->
|
||||
case bootstraped?(Kernel.Typespec) do
|
||||
false -> nil
|
||||
@@ -1911,7 +2022,7 @@ defmodule Kernel do
|
||||
defp do_at([arg], name, function?, env) do
|
||||
case function? do
|
||||
true ->
|
||||
raise ArgumentError, "cannot dynamically set attribute @#{name} inside function"
|
||||
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
|
||||
false ->
|
||||
case name do
|
||||
:behavior ->
|
||||
@@ -1932,7 +2043,14 @@ defmodule Kernel do
|
||||
case function? do
|
||||
true ->
|
||||
attr = Module.get_attribute(env.module, name, stack)
|
||||
:erlang.element(1, :elixir_quote.escape(attr, false))
|
||||
try do
|
||||
:elixir_quote.escape(attr, false)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "cannot inject attribute @#{name} into function/macro because " <> Exception.message(e)
|
||||
else
|
||||
{val, _} -> val
|
||||
end
|
||||
false ->
|
||||
escaped = case stack do
|
||||
[] -> []
|
||||
@@ -1948,7 +2066,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
opts = :erlang.element(tuple_size(entry), entry)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
@@ -1964,8 +2082,9 @@ defmodule Kernel do
|
||||
defp typespec(_), do: false
|
||||
|
||||
@doc """
|
||||
Returns the binding as a keyword list where the variable name
|
||||
is the key and the variable value is the value.
|
||||
Returns the binding for the given context as a keyword list.
|
||||
|
||||
The variable name is the key and the variable value is the value.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1976,59 +2095,18 @@ defmodule Kernel do
|
||||
iex> binding()
|
||||
[x: 2]
|
||||
|
||||
"""
|
||||
defmacro binding() do
|
||||
do_binding(nil, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of atoms at compilation time and returns the
|
||||
binding of the given variables as a keyword list where the
|
||||
variable name is the key and the variable value is the value.
|
||||
|
||||
In case a variable in the list does not exist in the binding,
|
||||
it is not included in the returned result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> x = 1
|
||||
iex> binding([:x, :y])
|
||||
[x: 1]
|
||||
|
||||
"""
|
||||
defmacro binding(list) when is_list(list) do
|
||||
do_binding(list, nil, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
defmacro binding(context) when is_atom(context) do
|
||||
do_binding(nil, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of atoms at compilation time and returns the
|
||||
binding of the given variables in the given context as a keyword
|
||||
list where the variable name is the key and the variable value
|
||||
is the value.
|
||||
|
||||
In case a variable in the list does not exist in the binding,
|
||||
it is not included in the returned result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> var!(x, :foo) = 1
|
||||
iex> binding([:x, :y])
|
||||
iex> binding(:foo)
|
||||
[]
|
||||
iex> binding([:x, :y], :foo)
|
||||
iex> var!(x, :foo) = 1
|
||||
1
|
||||
iex> binding(:foo)
|
||||
[x: 1]
|
||||
|
||||
"""
|
||||
defmacro binding(list, context) when is_list(list) and is_atom(context) do
|
||||
do_binding(list, context, __CALLER__.vars, Macro.Env.in_match?(__CALLER__))
|
||||
end
|
||||
|
||||
defp do_binding(list, context, vars, in_match) do
|
||||
for {v, c} <- vars, c == context, list == nil or :lists.member(v, list) do
|
||||
{v, wrap_binding(in_match, {v, [], c})}
|
||||
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, [], c})}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2310,7 +2388,7 @@ defmodule Kernel do
|
||||
it is not loaded. Check `Code.ensure_loaded/1` for more
|
||||
information.
|
||||
"""
|
||||
@spec function_exported?(atom | tuple, atom, integer) :: boolean
|
||||
@spec function_exported?(atom | tuple, atom, arity) :: boolean
|
||||
def function_exported?(module, function, arity) do
|
||||
:erlang.function_exported(module, function, arity)
|
||||
end
|
||||
@@ -2561,7 +2639,7 @@ defmodule Kernel do
|
||||
|
||||
# Generate the alias for this module definition
|
||||
{new, old} = module_nesting(env.module, full)
|
||||
meta = [defined: full, context: true] ++ alias_meta(alias)
|
||||
meta = [defined: full, context: env.module] ++ alias_meta(alias)
|
||||
|
||||
{full, {:alias, meta, [old, [as: new, warn: false]]}}
|
||||
false ->
|
||||
@@ -2819,97 +2897,59 @@ defmodule Kernel do
|
||||
|
||||
## Types
|
||||
|
||||
`defstruct` automatically generates a type `t` unless one exists.
|
||||
The following definition:
|
||||
It is recommended to define types for structs, by convention this type
|
||||
is called `t`. To define a struct in a type the struct literal syntax
|
||||
is used:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "José" :: String.t,
|
||||
age: 25 :: integer
|
||||
defstruct name: "john", age: 25
|
||||
@type t :: %User{name: String.t, age: integer}
|
||||
end
|
||||
|
||||
Generates a type as follows:
|
||||
It is recommended to only use the struct syntax when defining the struct's
|
||||
type. When referring to another struct use `User.t`, not `%User{}`. Fields
|
||||
in the struct not included in the type defaults to `term`.
|
||||
|
||||
@type t :: %User{name: String.t, age: integer}
|
||||
|
||||
In case a struct does not declare a field type, it defaults to `term`.
|
||||
Private structs that are not used outside its module should use the private
|
||||
type attribute `@typep`. Public structs whose internal structure is private
|
||||
to the local module (you are not allowed to pattern match it or directly
|
||||
access fields) should use the `@opaque` attribute. Structs whose internal
|
||||
structure is public should use `@type`.
|
||||
"""
|
||||
defmacro defstruct(kv) do
|
||||
{fields, types} = split_fields_and_types(kv)
|
||||
defmacro defstruct(fields) do
|
||||
quote bind_quoted: [fields: fields] do
|
||||
fields = :lists.map(fn
|
||||
{key, val} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(val)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "invalid value for struct field #{key}, " <> Exception.message(e)
|
||||
else
|
||||
_ -> {key, val}
|
||||
end
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
other ->
|
||||
raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
|
||||
fields =
|
||||
quote bind_quoted: [fields: fields] do
|
||||
fields = :lists.map(fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
|
||||
|
||||
@struct :maps.put(:__struct__, __MODULE__, :maps.from_list(fields))
|
||||
|
||||
case Module.get_attribute(__MODULE__, :derive) do
|
||||
[] ->
|
||||
:ok
|
||||
derive ->
|
||||
Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
|
||||
@spec __struct__() :: t
|
||||
def __struct__() do
|
||||
@struct
|
||||
end
|
||||
case Module.get_attribute(__MODULE__, :derive) do
|
||||
[] -> :ok
|
||||
derive -> Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
|
||||
types =
|
||||
case bootstraped?(Kernel.Typespec) do
|
||||
true when types == [] ->
|
||||
quote unquote: false do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
types = :lists.map(fn {key, _} ->
|
||||
{key, quote(do: term)}
|
||||
end, fields)
|
||||
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
|
||||
end
|
||||
end
|
||||
true ->
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
|
||||
end
|
||||
end
|
||||
false ->
|
||||
nil
|
||||
@spec __struct__() :: %__MODULE__{}
|
||||
def __struct__() do
|
||||
@struct
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(fields)
|
||||
unquote(types)
|
||||
fields
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types(kv) do
|
||||
case Keyword.keyword?(kv) do
|
||||
true -> split_fields_and_types(kv, [], [])
|
||||
false -> {kv, []}
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([field|t], fields, types) do
|
||||
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([], fields, types) do
|
||||
{:lists.reverse(fields), :lists.reverse(types)}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Defines an exception.
|
||||
|
||||
@@ -2970,7 +3010,7 @@ defmodule Kernel do
|
||||
@behaviour Exception
|
||||
fields = defstruct unquote(fields)
|
||||
|
||||
@spec exception(term) :: t
|
||||
@spec exception(Keyword.t) :: Exception.t
|
||||
def exception(args) when is_list(args) do
|
||||
Kernel.struct(__struct__, args)
|
||||
end
|
||||
@@ -2978,7 +3018,7 @@ defmodule Kernel do
|
||||
defoverridable exception: 1
|
||||
|
||||
if Keyword.has_key?(fields, :message) do
|
||||
@spec message(t) :: String.t
|
||||
@spec message(Exception.t) :: String.t
|
||||
def message(exception) do
|
||||
exception.message
|
||||
end
|
||||
@@ -3309,7 +3349,7 @@ defmodule Kernel do
|
||||
|
||||
## Sigils
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~S. It simply returns a string
|
||||
without escaping characters and without interpolations.
|
||||
|
||||
@@ -3318,15 +3358,15 @@ defmodule Kernel do
|
||||
iex> ~S(foo)
|
||||
"foo"
|
||||
|
||||
iex> ~S(f\#{o}o)
|
||||
"f\\\#{o}o"
|
||||
iex> ~S(f#{o}o)
|
||||
"f\#{o}o"
|
||||
|
||||
"""
|
||||
defmacro sigil_S(string, []) do
|
||||
string
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~s. It returns a string as if it was double quoted
|
||||
string, unescaping characters and replacing interpolations.
|
||||
|
||||
@@ -3335,15 +3375,18 @@ defmodule Kernel do
|
||||
iex> ~s(foo)
|
||||
"foo"
|
||||
|
||||
iex> ~s(f\#{:o}o)
|
||||
iex> ~s(f#{:o}o)
|
||||
"foo"
|
||||
|
||||
iex> ~s(f\#{:o}o)
|
||||
"f\#{:o}o"
|
||||
|
||||
"""
|
||||
defmacro sigil_s({:<<>>, line, pieces}, []) do
|
||||
{:<<>>, line, Macro.unescape_tokens(pieces)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~C. It simply returns a char list
|
||||
without escaping characters and without interpolations.
|
||||
|
||||
@@ -3352,15 +3395,15 @@ defmodule Kernel do
|
||||
iex> ~C(foo)
|
||||
'foo'
|
||||
|
||||
iex> ~C(f\#{o}o)
|
||||
'f\\\#{o}o'
|
||||
iex> ~C(f#{o}o)
|
||||
'f\#{o}o'
|
||||
|
||||
"""
|
||||
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
|
||||
String.to_char_list(string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~c. It returns a char list as if it were a single
|
||||
quoted string, unescaping characters and replacing interpolations.
|
||||
|
||||
@@ -3369,9 +3412,12 @@ defmodule Kernel do
|
||||
iex> ~c(foo)
|
||||
'foo'
|
||||
|
||||
iex> ~c(f\#{:o}o)
|
||||
iex> ~c(f#{:o}o)
|
||||
'foo'
|
||||
|
||||
iex> ~c(f\#{:o}o)
|
||||
'f\#{:o}o'
|
||||
|
||||
"""
|
||||
|
||||
# We can skip the runtime conversion if we are
|
||||
@@ -3405,13 +3451,13 @@ defmodule Kernel do
|
||||
quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~R. It returns a Regex pattern without escaping
|
||||
nor interpreting interpolations.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match?(~R(f\#{1,3}o), "f\#o")
|
||||
iex> Regex.match?(~R(f#{1,3}o), "f#o")
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -3420,7 +3466,7 @@ defmodule Kernel do
|
||||
Macro.escape(regex)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~w. It returns a list of "words" split by whitespace.
|
||||
|
||||
## Modifiers
|
||||
@@ -3431,7 +3477,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> ~w(foo \#{:bar} baz)
|
||||
iex> ~w(foo #{:bar} baz)
|
||||
["foo", "bar", "baz"]
|
||||
|
||||
iex> ~w(--source test/enum_test.exs)
|
||||
@@ -3451,7 +3497,7 @@ defmodule Kernel do
|
||||
split_words(binary, modifiers)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil ~W. It returns a list of "words" split by whitespace
|
||||
without escaping nor interpreting interpolations.
|
||||
|
||||
@@ -3463,8 +3509,8 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> ~W(foo \#{bar} baz)
|
||||
["foo", "\\\#{bar}", "baz"]
|
||||
iex> ~W(foo #{bar} baz)
|
||||
["foo", "\#{bar}", "baz"]
|
||||
|
||||
"""
|
||||
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
@blank_config %{commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false}
|
||||
pa: [], pz: [], verbose_compile: false}
|
||||
|
||||
@doc """
|
||||
This is the API invoked by Elixir boot process.
|
||||
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
|
||||
argv = for arg <- argv, do: IO.chardata_to_string(arg)
|
||||
|
||||
{config, argv} = parse_argv(argv)
|
||||
:elixir_code_server.cast({:paths, config.pa, config.pz})
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
run fn _ ->
|
||||
errors = process_commands(config)
|
||||
|
||||
if errors != [] do
|
||||
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt \\ true) do
|
||||
try do
|
||||
fun.()
|
||||
if halt do
|
||||
at_exit(0)
|
||||
System.halt(0)
|
||||
end
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
at_exit(reason)
|
||||
System.halt(reason)
|
||||
:exit, :normal ->
|
||||
at_exit(0)
|
||||
System.halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
System.halt(1)
|
||||
res = exec_fun(fun, {:ok, 0})
|
||||
if elem(res, 0) == :shutdown or halt do
|
||||
{_, int} = at_exit(res)
|
||||
System.halt(int)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses ARGV returning the CLI config and trailing args.
|
||||
"""
|
||||
@doc false
|
||||
def parse_argv(argv) do
|
||||
parse_argv(argv, @blank_config)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Process commands according to the parsed config from `parse_argv/1`.
|
||||
Returns all errors.
|
||||
"""
|
||||
@doc false
|
||||
def process_commands(config) do
|
||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
defp at_exit(res) do
|
||||
hooks = :elixir_config.get_and_put(:at_exit, [])
|
||||
res = Enum.reduce(hooks, res, &exec_fun/2)
|
||||
if hooks == [], do: res, else: at_exit(res)
|
||||
end
|
||||
|
||||
for hook <- hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
catch
|
||||
kind, reason ->
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
spawn_monitor fn ->
|
||||
try do
|
||||
fun.(elem(res, 1))
|
||||
catch
|
||||
:exit, {:shutdown, int} when is_integer(int) ->
|
||||
send parent, {self, {:shutdown, int}}
|
||||
exit({:shutdown, int})
|
||||
:exit, reason
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
|
||||
send parent, {self, {:shutdown, 0}}
|
||||
exit(reason)
|
||||
kind, reason ->
|
||||
stack = System.stacktrace
|
||||
print_error(kind, reason, stack)
|
||||
send parent, {self, {:shutdown, 1}}
|
||||
:erlang.raise(kind, reason, stack)
|
||||
else
|
||||
_ ->
|
||||
send parent, {self, res}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If an at_exit callback adds a
|
||||
# new hook we need to invoke it.
|
||||
unless hooks == [], do: at_exit(status)
|
||||
receive do
|
||||
{^pid, res} ->
|
||||
:erlang.demonitor(ref, [:flush])
|
||||
res
|
||||
{:DOWN, ^ref, _, _, other} ->
|
||||
print_error({:EXIT, pid}, other, [])
|
||||
{:shutdown, 1}
|
||||
end
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
@@ -103,7 +113,8 @@ defmodule Kernel.CLI do
|
||||
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
|
||||
:elixir_expand, :elixir_lexical]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
@@ -129,13 +140,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_shared(["-pa", h|t], config) do
|
||||
add_code_path(h, &Code.prepend_path/1)
|
||||
parse_shared t, config
|
||||
paths = expand_code_path(h)
|
||||
Enum.each(paths, &:code.add_patha/1)
|
||||
parse_shared t, %{config | pa: config.pa ++ paths}
|
||||
end
|
||||
|
||||
defp parse_shared(["-pz", h|t], config) do
|
||||
add_code_path(h, &Code.append_path/1)
|
||||
parse_shared t, config
|
||||
paths = expand_code_path(h)
|
||||
Enum.each(paths, &:code.add_pathz/1)
|
||||
parse_shared t, %{config | pz: config.pz ++ paths}
|
||||
end
|
||||
|
||||
defp parse_shared(["--app", h|t], config) do
|
||||
@@ -162,7 +175,7 @@ defmodule Kernel.CLI do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--werl"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
@@ -170,12 +183,11 @@ defmodule Kernel.CLI do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
|
||||
defp add_code_path(path, fun) do
|
||||
defp expand_code_path(path) do
|
||||
path = Path.expand(path)
|
||||
case Path.wildcard(path) do
|
||||
[] -> fun.(path)
|
||||
list -> Enum.each(list, fun)
|
||||
[] -> [to_char_list(path)]
|
||||
list -> Enum.map(list, &to_char_list/1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -352,7 +364,8 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
# If ensuring the dir returns an error no files will be found.
|
||||
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
case filter_multiple_patterns(patterns) do
|
||||
{:ok, []} ->
|
||||
@@ -396,7 +409,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp wrapper(fun) do
|
||||
fun.()
|
||||
_ = fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -407,8 +420,8 @@ defmodule Kernel.CLI do
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{:win32, _} ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
base = Path.rootname(exec)
|
||||
if File.regular?(base), do: base, else: exec
|
||||
_ ->
|
||||
exec
|
||||
end
|
||||
|
||||
@@ -25,11 +25,11 @@ defmodule Kernel.ErrorHandler do
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{:module, _} -> []
|
||||
{:module, _} -> :ok
|
||||
{:error, _} ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, {:waiting, module, self(), ref, module}
|
||||
send parent, {:waiting, :module, self(), ref, module}
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{^ref, :ready} -> :ok
|
||||
|
||||
@@ -18,20 +18,22 @@ defmodule Kernel.LexicalTracker do
|
||||
Returns all remotes linked to in this lexical scope.
|
||||
"""
|
||||
def remotes(arg) do
|
||||
# If the module is compiled from a function, its lexical
|
||||
# scope may be long gone, so it has no associated PID.
|
||||
if pid = to_pid(arg) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
ets = :gen_server.call(to_pid(arg), :ets, @timeout)
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the destination the lexical scope is meant to
|
||||
compile to.
|
||||
"""
|
||||
def dest(arg) do
|
||||
:gen_server.call(to_pid(arg), :dest, @timeout)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :lexical_tracker})
|
||||
val
|
||||
end
|
||||
|
||||
@@ -39,9 +41,8 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
def start_link(dest) do
|
||||
:gen_server.start_link(__MODULE__, dest, [])
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -96,62 +97,69 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
# Callbacks
|
||||
|
||||
|
||||
def init([]) do
|
||||
{:ok, :ets.new(:lexical, [:protected])}
|
||||
def init(dest) do
|
||||
{:ok, {:ets.new(:lexical, [:protected]), dest}}
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{:reply, d, d}
|
||||
@doc false
|
||||
def handle_call(:ets, _from, {d, dest}) do
|
||||
{:reply, d, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, d) do
|
||||
{:stop, {:bad_call, request}, d}
|
||||
def handle_call(:dest, _from, {d, dest}) do
|
||||
{:reply, dest, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module}, d) do
|
||||
def handle_call(request, _from, {d, dest}) do
|
||||
{:stop, {:bad_call, request}, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:remote_dispatch, module}, {d, dest}) do
|
||||
add_module(d, module)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:import_dispatch, module}, d) do
|
||||
def handle_cast({:import_dispatch, module}, {d, dest}) do
|
||||
add_dispatch(d, module, @import)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:alias_dispatch, module}, d) do
|
||||
def handle_cast({:alias_dispatch, module}, {d, dest}) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_import, module, line, warn}, d) do
|
||||
def handle_cast({:add_import, module, line, warn}, {d, dest}) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_alias, module, line, warn}, d) do
|
||||
def handle_cast({:add_alias, module, line, warn}, {d, dest}) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{:noreply, d}
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{:stop, :normal, d}
|
||||
def handle_cast(:stop, {d, dest}) do
|
||||
{:stop, :normal, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_cast(msg, d) do
|
||||
{:stop, {:bad_cast, msg}, d}
|
||||
def handle_cast(msg, {d, dest}) do
|
||||
{:stop, {:bad_cast, msg}, {d, dest}}
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{:noreply, d}
|
||||
@doc false
|
||||
def handle_info(_msg, {d, dest}) do
|
||||
{:noreply, {d, dest}}
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{:ok, d}
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
This function accepts the following options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
@@ -23,57 +23,61 @@ defmodule Kernel.ParallelCompiler do
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
* `:dest` - the destination directory for the beam files. When using `files/2`,
|
||||
this information is only used to properly annotate the beam files before
|
||||
they are loaded into memory. If you want a file to actually be writen to
|
||||
`dest`, use `files_to_path/3` instead.
|
||||
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks \\ [])
|
||||
def files(files, options \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
def files(files, options) when is_list(options) do
|
||||
spawn_compilers(files, nil, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
def files_to_path(files, path, options \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
def files_to_path(files, path, options) when is_binary(path) and is_list(options) do
|
||||
spawn_compilers(files, path, options)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
defp spawn_compilers(files, path, options) do
|
||||
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
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
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok -> result
|
||||
:error -> exit(1)
|
||||
:error -> exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(t, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp spawn_compilers([h|t], original, output, options, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{pid, ref} =
|
||||
@@ -87,43 +91,43 @@ defmodule Kernel.ParallelCompiler do
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
exit(try do
|
||||
if output do
|
||||
_ = if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
:elixir_compiler.file(h, Keyword.get(options, :dest))
|
||||
end
|
||||
{:compiled, h}
|
||||
{:shutdown, h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
spawn_compilers(t, original, output, options, waiting,
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
defp spawn_compilers([], _original, _output, _options, [], [], _schedulers, result) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
defp spawn_compilers([], original, output, options, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
@@ -131,11 +135,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
if callback = Keyword.get(options, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
@@ -147,7 +151,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
spawn_compilers(available ++ entries, original, output, options,
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
@@ -160,10 +164,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
spawn_compilers(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
{:DOWN, _down_ref, :process, down_pid, {:shutdown, file}} ->
|
||||
if callback = Keyword.get(options, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
@@ -172,21 +176,27 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
spawn_compilers(new_entries, original, output, options, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, options, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, reason, entries, waiting, queued) do
|
||||
if file = find_failure(ref, queued) do
|
||||
print_failure(file, reason)
|
||||
|
||||
if all_missing?(entries, waiting, queued) do
|
||||
collect_failures(queued, length(queued) - 1)
|
||||
end
|
||||
exit(1)
|
||||
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
Process.exit(child, :kill)
|
||||
end
|
||||
|
||||
exit({:shutdown, 1})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,7 +207,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp print_failure(_file, {:compiled, _}) do
|
||||
defp print_failure(_file, {:shutdown, _}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -211,7 +221,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts Exception.format(:exit, reason, [])
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
@elixir_internals [:elixir, :elixir_exp, :elixir_compiler, :elixir_module, :elixir_translator,
|
||||
:elixir_expand, :elixir_lexical]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
|
||||
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{name: "josé", age: 27}
|
||||
%{name: "john", age: 27}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,7 +122,7 @@ defmodule Kernel.SpecialForms do
|
||||
`Kernel.defstruct/1` macro:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "josé", age: 27
|
||||
defstruct name: "john", age: 27
|
||||
end
|
||||
|
||||
Now a struct can be created as follows:
|
||||
@@ -132,13 +132,13 @@ defmodule Kernel.SpecialForms do
|
||||
Underneath a struct is just a map with a `__struct__` field
|
||||
pointing to the `User` module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
%User{} == %{__struct__: User, name: "john", age: 27}
|
||||
|
||||
A struct also validates that the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the `User` struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
%User{full_name: "john doe"}
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
@@ -172,10 +172,22 @@ defmodule Kernel.SpecialForms do
|
||||
iex> << 1, 2, 3 >>
|
||||
<< 1, 2, 3 >>
|
||||
|
||||
## Bitstring types
|
||||
## Types
|
||||
|
||||
A bitstring is made of many segments. Each segment has a
|
||||
type, which defaults to integer:
|
||||
A bitstring is made of many segments and each segment has a
|
||||
type. There are 9 types used in bitstrings:
|
||||
|
||||
- `integer`
|
||||
- `float`
|
||||
- `bits` (alias for bitstring)
|
||||
- `bitstring`
|
||||
- `binary`
|
||||
- `bytes` (alias for binary)
|
||||
- `utf8`
|
||||
- `utf16`
|
||||
- `utf32`
|
||||
|
||||
When no type is specified, the default is `integer`:
|
||||
|
||||
iex> <<1, 2, 3>>
|
||||
<<1, 2, 3>>
|
||||
@@ -186,14 +198,7 @@ defmodule Kernel.SpecialForms do
|
||||
iex> <<0, "foo">>
|
||||
<<0, 102, 111, 111>>
|
||||
|
||||
Any other type needs to be explicitly tagged. For example,
|
||||
in order to store a float type in the binary, one has to do:
|
||||
|
||||
iex> <<3.14 :: float>>
|
||||
<<64, 9, 30, 184, 81, 235, 133, 31>>
|
||||
|
||||
This also means that variables need to be explicitly tagged,
|
||||
otherwise Elixir defaults to integer:
|
||||
Variables or any other type need to be explicitly tagged:
|
||||
|
||||
iex> rest = "oo"
|
||||
iex> <<102, rest>>
|
||||
@@ -201,100 +206,161 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
We can solve this by explicitly tagging it as a binary:
|
||||
|
||||
<<102, rest :: binary>>
|
||||
|
||||
The type can be integer, float, bitstring/bits, binary/bytes,
|
||||
utf8, utf16 or utf32, e.g.:
|
||||
|
||||
<<102 :: float, rest :: binary>>
|
||||
|
||||
An integer can be any arbitrary precision integer. A float is an
|
||||
IEEE 754 binary32 or binary64 floating point number. A bitstring
|
||||
is an arbitrary series of bits. A binary is a special case of
|
||||
bitstring that has a total size divisible by 8.
|
||||
iex> rest = "oo"
|
||||
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>>
|
||||
<<0,102,0,111,0,111>>
|
||||
<<0, 102, 0, 111, 0, 111>>
|
||||
iex> <<"foo" :: utf32>>
|
||||
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
|
||||
|
||||
The bits type is an alias for bitstring. The bytes type is an
|
||||
alias for binary.
|
||||
## Options
|
||||
|
||||
The signedness can also be given as signed or unsigned. The
|
||||
signedness only matters for matching and relevant only for
|
||||
integers. If unspecified, it defaults to unsigned. Example:
|
||||
Many options can be given by using `-` as separator. Order is
|
||||
arbitrary, so the following are all equivalent:
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156,102,111,111>>
|
||||
<<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>>
|
||||
|
||||
This match would have failed if we did not specify that the
|
||||
value -100 is signed. If we're matching into a variable instead
|
||||
of a value, the signedness won't be checked; rather, the number
|
||||
will simply be interpreted as having the given (or implied)
|
||||
signedness, e.g.:
|
||||
### Unit and Size
|
||||
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
iex> val
|
||||
156
|
||||
The length of the match is equal to the `unit` (a number of bits) times the
|
||||
`size` (the number of repeated segnments of length `unit`).
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
Type | Default Unit
|
||||
--------- | ------------
|
||||
`integer` | 1 bit
|
||||
`float` | 1 bit
|
||||
`binary` | 8 bits
|
||||
|
||||
The endianness of a segment can be big, little or native (the
|
||||
latter meaning it will be resolved at VM load time). Passing
|
||||
many options can be done by giving a list:
|
||||
Sizes for types are a bit more nuanced. The default size for integers is 8.
|
||||
|
||||
<<102 :: [integer, native], rest :: binary>>
|
||||
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
|
||||
corresponding to [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)
|
||||
binary32 and binary64, respectively.
|
||||
|
||||
Or:
|
||||
For binaries, the default is the size of the binary. Only the last binary in a
|
||||
match can use the default size. All others must have their size specified
|
||||
explicitly, even if the match is unambiguous. For example:
|
||||
|
||||
<<102 :: [unsigned, big, integer], rest :: binary>>
|
||||
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
"Frank the Walrus"
|
||||
iex> {name, species}
|
||||
{"Frank", "Walrus"}
|
||||
|
||||
And so on.
|
||||
Failing to specify the size for the non-last causes compilation to fail:
|
||||
|
||||
Endianness only makes sense for integers and some UTF code
|
||||
point types (utf16 and utf32).
|
||||
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
** (CompileError): a binary field without size is only allowed at the end of a binary pattern
|
||||
|
||||
Finally, we can also specify size and unit for each segment. The
|
||||
unit is multiplied by the size to give the effective size of
|
||||
the segment in bits. The default unit for integers, floats,
|
||||
and bitstrings is 1. For binaries, it is 8.
|
||||
#### Shortcut Syntax
|
||||
|
||||
Since integers are default, the default unit is 1. The example below
|
||||
matches because the string "foo" takes 24 bits and we match it
|
||||
against a segment of 24 bits, 8 of which are taken by the integer
|
||||
102 and the remaining 16 bits are specified on the rest.
|
||||
Size and unit can also be specified using a syntax shortcut
|
||||
when passing integer values:
|
||||
|
||||
iex> <<102, _rest :: size(16)>> = "foo"
|
||||
"foo"
|
||||
|
||||
We can also match by specifying size and unit explicitly:
|
||||
|
||||
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
|
||||
"foo"
|
||||
|
||||
However, if we expect a size of 32, it won't match:
|
||||
|
||||
iex> <<102, _rest :: size(32)>> = "foo"
|
||||
** (MatchError) no match of right hand side value: "foo"
|
||||
|
||||
Size and unit are not applicable to utf8, utf16, and utf32.
|
||||
|
||||
The default size for integers is 8. For floats, it is 64. For
|
||||
binaries, it is the size of the binary. Only the last binary
|
||||
in a binary match can use the default size (all others must
|
||||
have their size specified explicitly).
|
||||
|
||||
Size can also be specified using a syntax shortcut. Instead of
|
||||
writing `size(8)`, one can write just `8` and it will be interpreted
|
||||
as `size(8)`
|
||||
|
||||
iex> << 1 :: 3 >> == << 1 :: size(3) >>
|
||||
iex> x = 1
|
||||
iex> << x :: 8 >> == << x :: size(8) >>
|
||||
true
|
||||
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||
true
|
||||
|
||||
For floats, `size * unit` must result in 32 or 64, corresponding
|
||||
to binary32 and binary64, respectively.
|
||||
This syntax reflects the fact the effective size is given by
|
||||
multiplying the size by the unit.
|
||||
|
||||
### Modifiers
|
||||
|
||||
Some types have associated modifiers to clear up ambiguity in byte
|
||||
representation.
|
||||
|
||||
Modifier | Relevant Type(s)
|
||||
-------------------- | ----------------
|
||||
`signed` | `integer`
|
||||
`unsigned` (default) | `integer`
|
||||
`little` | `integer`, `utf16`, `utf32`
|
||||
`big` (default) | `integer`, `utf16`, `utf32`
|
||||
`native` | `integer`, `utf16`, `utf32`
|
||||
|
||||
### Sign
|
||||
|
||||
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
|
||||
|
||||
iex> <<int::integer>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
156
|
||||
iex> <<int::integer-signed>> = <<-100>>
|
||||
<<156>>
|
||||
iex> int
|
||||
-100
|
||||
|
||||
`signed` and `unsigned` are only used for matching binaries (see below) and
|
||||
are only used for integers.
|
||||
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156, 102, 111, 111>>
|
||||
|
||||
### Endianness
|
||||
|
||||
Elixir has three options for endianness: `big`, `little`, and `native`.
|
||||
The default is `big`. `native` is determined by the VM at startup.
|
||||
|
||||
iex> <<number::little-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
256
|
||||
iex> <<number::big-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
1
|
||||
iex> <<number::native-integer-size(16)>> = <<0, 1>>
|
||||
<<0, 1>>
|
||||
iex> number
|
||||
256
|
||||
|
||||
## Binary/Bitstring Matching
|
||||
|
||||
Binary matching is a powerful feature in Elixir that is useful for extracting
|
||||
information from binaries as well as pattern matching.
|
||||
|
||||
Binary matching can be used by itself to extract information from binaries:
|
||||
|
||||
iex> <<"Hello, ", place::binary>> = "Hello, World"
|
||||
"Hello, World"
|
||||
iex> place
|
||||
"World"
|
||||
|
||||
Or as a part of function definitions to pattern match:
|
||||
|
||||
defmodule ImageTyper
|
||||
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
|
||||
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
|
||||
@jpg_signature <<255::size(8), 216::size(8)>>
|
||||
|
||||
def type(<<@png_signature, rest::binary>>), do: :png
|
||||
def type(<<@jpg_signature, rest::binary>>), do: :jpg
|
||||
def type(_), do :unknown
|
||||
end
|
||||
|
||||
### Performance & Optimizations
|
||||
|
||||
The Erlang compiler can provide a number of optimizations on binary creation
|
||||
and matching. To see optimization output, set the `bin_opt_info` compiler
|
||||
option:
|
||||
|
||||
ERL_COMPILER_OPTIONS=bin_opt_info mix compile
|
||||
|
||||
To learn more about specific optimizations and performance considerations,
|
||||
check out
|
||||
[Erlang's Efficiency Guide on handling binaries](http://www.erlang.org/doc/efficiency_guide/binaryhandling.html).
|
||||
"""
|
||||
defmacro unquote(:<<>>)(args)
|
||||
|
||||
@@ -340,28 +406,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
|
||||
This choice was done so every time single- or double-quotes are used, we have
|
||||
a remote call irregardless of the quote contents. This decision is also reflected
|
||||
a remote call regardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
|
||||
## Runtime (dynamic) behaviour
|
||||
|
||||
The result returned by `.` is always specified by the right-side:
|
||||
|
||||
iex> x = String
|
||||
iex> x.downcase("FOO")
|
||||
"foo"
|
||||
iex> x.Sample
|
||||
String.Sample
|
||||
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
@@ -1109,7 +1156,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
Imagine the situation you have a variable `value` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
@@ -1171,10 +1218,10 @@ 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: "jose", admin: "john", user: "eric"] do
|
||||
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
|
||||
...> String.upcase(name)
|
||||
...> end
|
||||
["JOSE", "ERIC"]
|
||||
["JOHN", "MEG"]
|
||||
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring streams:
|
||||
@@ -1324,22 +1371,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1. The head element of aliases can be any term.
|
||||
1. The head element of aliases can be any term that must expand to
|
||||
an atom at compilation time.
|
||||
|
||||
2. The tail elements of aliases are guaranteed to always be atoms.
|
||||
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
|
||||
|
||||
4. When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
@@ -1513,9 +1551,9 @@ defmodule Kernel.SpecialForms do
|
||||
The catch clause can be used to catch throws values and exits.
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
exit(:shutdown)
|
||||
catch
|
||||
:exit, 1 -> IO.puts "Exited with 1"
|
||||
:exit, :shutdown -> IO.puts "Exited with shutdown reason"
|
||||
end
|
||||
|
||||
try do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
|
||||
| Fun
|
||||
| Integer
|
||||
| List
|
||||
| Map
|
||||
| Tuple
|
||||
| Union
|
||||
| UserDefined # Described in section "Defining a type"
|
||||
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
|
||||
| nonempty_list(Type) # proper non-empty list
|
||||
| [] # empty list
|
||||
| [Type] # shorthand for list(Type)
|
||||
| [...] # shorthand for nonempty_list()
|
||||
| [Type, ...] # shorthand for nonempty_list(Type)
|
||||
| [Keyword]
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
Map :: map() # map of any size
|
||||
| %{} # map of any size
|
||||
| %Struct{} # struct (see defstruct/1)
|
||||
| %Struct{Keyword}
|
||||
| %{Keyword}
|
||||
| %{Pairs}
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| {TList}
|
||||
| record(Atom) # record (see Record)
|
||||
| record(Atom, Keyword)
|
||||
|
||||
Keyword :: ElixirAtom: Type
|
||||
| ElixirAtom: Type, Keyword
|
||||
|
||||
Pairs :: Type => Type
|
||||
| Type => Type, Pairs
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
|
||||
Union :: Type1 | Type2
|
||||
Union :: Type | Type
|
||||
|
||||
### Bit strings
|
||||
|
||||
@@ -104,36 +122,38 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`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`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
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`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` \| `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
@@ -488,9 +508,9 @@ defmodule Kernel.Typespec do
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, nil}, _]}),
|
||||
def type_to_signature({:::, _, [{name, _, context}, _]}) when is_atom(name) and is_atom(context),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}),
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}) when is_atom(name),
|
||||
do: {name, length(args)}
|
||||
|
||||
## Macro callbacks
|
||||
@@ -536,7 +556,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
if not export and doc do
|
||||
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n")
|
||||
"@typedoc's are always discarded for private types")
|
||||
end
|
||||
|
||||
{{kind, {name, arity}, type}, caller.line, export, doc}
|
||||
@@ -624,6 +644,10 @@ defmodule Kernel.Typespec do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:user_type, line, name, args}) do
|
||||
typespec_to_ast({:type, line, name, args})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :tuple, :any}) do
|
||||
{:tuple, [line: line], []}
|
||||
end
|
||||
@@ -640,13 +664,30 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :list, args}) do
|
||||
for arg <- args, do: typespec_to_ast(arg)
|
||||
defp typespec_to_ast({:type, line, :list, []}) do
|
||||
{:list, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :list, [arg]}) do
|
||||
[typespec_to_ast(arg)]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
|
||||
[{:..., [line: line], nil}]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
|
||||
[typespec_to_ast(arg), {:..., [line: line], nil}]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
fields = Enum.map fields, fn
|
||||
# OTP 18
|
||||
{:type, _, :map_field_assoc, [k, v]} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
# OTP 17
|
||||
{:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
end
|
||||
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
@@ -690,7 +731,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :range, [left, right]}) do
|
||||
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||
{:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, nil, []}) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, name, args}) do
|
||||
@@ -747,9 +792,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-size(1), rest :: binary>> ->
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
<<c :: binary-size(1), rest :: binary>> ->
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
@@ -785,19 +830,79 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = :lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end, fields)
|
||||
fields =
|
||||
if :erlang.system_info(:otp_release) >= '18' do
|
||||
: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
|
||||
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
|
||||
module = Macro.expand(name, caller)
|
||||
|
||||
struct =
|
||||
if module == caller.module do
|
||||
Module.get_attribute(module, :struct) ||
|
||||
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
|
||||
else
|
||||
module.__struct__
|
||||
end
|
||||
|
||||
struct =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, quote do: term()}
|
||||
end, Map.to_list(struct))
|
||||
|
||||
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(struct, field) do
|
||||
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
|
||||
end
|
||||
end, fields)
|
||||
|
||||
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
|
||||
typespec({:%{}, meta, fields}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle records
|
||||
defp typespec({:record, meta, [atom]}, vars, caller) do
|
||||
typespec({:record, meta, [atom, []]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
|
||||
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
||||
keyword when is_list(keyword) ->
|
||||
keyword =
|
||||
:lists.map(fn {field, _} ->
|
||||
{field, quote do: term()}
|
||||
end, keyword)
|
||||
|
||||
:lists.foreach(fn {field, _} ->
|
||||
unless Keyword.has_key?(keyword, field) do
|
||||
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
|
||||
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}")
|
||||
end
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :range, args}
|
||||
end
|
||||
|
||||
# Handle special forms
|
||||
@@ -866,11 +971,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
|
||||
:elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :string, arguments}
|
||||
{:type, line(meta), type, arguments}
|
||||
end
|
||||
|
||||
defp typespec({:char_list, _meta, []}, vars, caller) do
|
||||
@@ -881,9 +986,21 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:fun, meta, args}, vars, caller) do
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :fun, args}
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), name, arguments}
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
@@ -899,15 +1016,19 @@ defmodule Kernel.Typespec do
|
||||
typespec({nil, [], []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
|
||||
typespec({:nonempty_list, [], []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
|
||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec], vars, caller) do
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
|
||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
defp typespec(list, vars, caller) when is_list(list) do
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
@@ -915,6 +1036,10 @@ defmodule Kernel.Typespec do
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(other, _vars, caller) do
|
||||
compile_error(caller, "unexpected expression in typespec: #{Macro.to_string other}")
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
@@ -941,7 +1066,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
|
||||
defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
|
||||
{:type, line(meta), :any}
|
||||
end
|
||||
|
||||
|
||||
+46
-33
@@ -9,19 +9,19 @@ defmodule Keyword do
|
||||
behave exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
For example, `Keyword.get/3` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` ensure all
|
||||
Similarly, `Keyword.put/3` and `Keyword.delete/3` ensure all
|
||||
duplicated entries for a given key are removed when invoked.
|
||||
|
||||
A handful of functions exist to handle duplicated keys, in
|
||||
particular, `from_enum` allows creating a new keywords without
|
||||
removing duplicated keys, `get_values` returns all values for
|
||||
a given key and `delete_first` deletes just one of the existing
|
||||
particular, `Enum.into/2` allows creating new keywords without
|
||||
removing duplicated keys, `get_values/2` returns all values for
|
||||
a given key and `delete_first/2` deletes just one of the existing
|
||||
entries.
|
||||
|
||||
Since a keyword list is simply a list, all the operations defined
|
||||
in `Enum` and `List` can also be applied.
|
||||
in `Enum` and `List` can be applied.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@@ -34,7 +34,7 @@ defmodule Keyword do
|
||||
@type t(value) :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not.
|
||||
Checks if the given argument is a keyword list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
@@ -56,7 +56,7 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [])`,
|
||||
Unlike `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
@@ -76,7 +76,7 @@ defmodule Keyword do
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [], fun)`,
|
||||
Unlike `Enum.into(enumerable, [], fun)`,
|
||||
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
@@ -96,7 +96,7 @@ defmodule Keyword do
|
||||
@doc """
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If `key` does not exist, return 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.
|
||||
@@ -124,6 +124,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
|
||||
If the `key` does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
@@ -135,7 +136,7 @@ defmodule Keyword do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: {:ok, value}
|
||||
@spec fetch(t, key) :: {:ok, value} | :error
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{^key, value} -> {:ok, value}
|
||||
@@ -144,8 +145,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific `key`. If `key` does not exist,
|
||||
a `KeyError` is raised.
|
||||
Fetches the value for specific `key`.
|
||||
|
||||
If `key` does not exist, a `KeyError` is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -184,8 +186,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys from the keyword list. Duplicated
|
||||
keys appear duplicated in the final list of keys.
|
||||
Returns all keys from the keyword list.
|
||||
|
||||
Duplicated keys appear duplicated in the final list of keys.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -217,6 +220,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the keyword list for a `key` with `value`.
|
||||
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -238,8 +242,9 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Deletes the entries in the keyword list for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
Use `delete_first/2` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
|
||||
## Examples
|
||||
@@ -261,6 +266,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -319,7 +325,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two keywords are equal. I.e. they contain
|
||||
Checks if two keywords are equal.
|
||||
|
||||
Two keywords are considered to be equal if they contain
|
||||
the same keys and those keys contain the same values.
|
||||
|
||||
## Examples
|
||||
@@ -334,8 +342,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the one given as second argument wins.
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the one given in the second argument wins.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -350,8 +359,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the given function is invoked to solve conflicts.
|
||||
Merges two keyword lists into one.
|
||||
|
||||
If they have duplicated keys, the given function is invoked to solve conflicts.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -392,10 +402,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, raises `KeyError`.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
If there are duplicated keys, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
@@ -425,10 +436,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
Updates the `key` with the given function.
|
||||
|
||||
If the `key` does not exist, inserts the given `initial` value.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
If there are duplicated keys, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
@@ -455,8 +467,9 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Takes all entries corresponding to the given keys and extracts them into a
|
||||
separate keyword list. Returns a tuple with the new list and the old list
|
||||
with removed keys.
|
||||
separate keyword list.
|
||||
|
||||
Returns a tuple with the new list and the old list with removed keys.
|
||||
|
||||
Keys for which there are no entires in the keyword list are ignored.
|
||||
|
||||
@@ -508,7 +521,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
Drops the given keys from the keyword list.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
@@ -531,7 +544,7 @@ defmodule Keyword do
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without `key`.
|
||||
|
||||
All duplicated entries are removed. See `pop_first/3` for
|
||||
All duplicated keys are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
|
||||
## Examples
|
||||
@@ -558,10 +571,10 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without that particular ocurrence
|
||||
list as well as the keyword list without that particular occurrence
|
||||
of `key`.
|
||||
|
||||
Duplicated entries are not removed.
|
||||
Duplicated keys are not removed.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+5
-21
@@ -247,7 +247,7 @@ defmodule List do
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -307,6 +307,8 @@ defmodule List do
|
||||
@doc """
|
||||
Zips corresponding elements from each list in `list_of_lists`.
|
||||
|
||||
The zipping finishes as soon as any list terminates.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
||||
@@ -322,24 +324,6 @@ defmodule List do
|
||||
do_zip(list_of_lists, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unzips the given list of lists or tuples into separate lists and returns a
|
||||
list of lists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.unzip([{1, 2}, {3, 4}])
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &Tuple.to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with `value` inserted at the specified `index`.
|
||||
Note that `index` is capped at the list length. Negative indices
|
||||
@@ -528,7 +512,7 @@ defmodule List do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list, non_neg_integer) :: integer
|
||||
@spec to_integer(char_list, 2..36) :: integer
|
||||
def to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
@@ -566,7 +550,7 @@ defmodule List do
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec to_string(:unicode.char_list) :: String.t
|
||||
@spec to_string(:unicode.charlist) :: String.t
|
||||
def to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
|
||||
+85
-25
@@ -16,7 +16,8 @@ defmodule Macro do
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in,
|
||||
:and, :or, :when, :in,
|
||||
:~>>, :<<~, :~>, :<~, :<~>, :<|>,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -35,11 +36,12 @@ defmodule Macro do
|
||||
::: -> {:right, 60}
|
||||
:| -> {:right, 70}
|
||||
:= -> {:right, 90}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:||, :|||, :or] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<, :<=, :>=, :>] -> {:left, 160}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:left, 170}
|
||||
o when o in [:|>, :<<<, :>>>, :<~, :~>,
|
||||
:<<~, :~>>, :<~>, :<|>] -> {:left, 170}
|
||||
:in -> {:left, 180}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
@@ -74,7 +76,7 @@ defmodule Macro do
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
bad_pipe(expr, call_args)
|
||||
end
|
||||
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
@@ -86,7 +88,12 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
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.()"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -300,6 +307,40 @@ defmodule Macro do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates the given expressions are valid quoted expressions.
|
||||
|
||||
Check the `type:Macro.t` for the specification of a valid
|
||||
quoted expression.
|
||||
"""
|
||||
@spec validate(term) :: :ok | {:error, term}
|
||||
def validate(expr) do
|
||||
find_invalid(expr) || :ok
|
||||
end
|
||||
|
||||
defp find_invalid({left, right}), do:
|
||||
find_invalid(left) || find_invalid(right)
|
||||
|
||||
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
|
||||
find_invalid(left) || find_invalid(right)
|
||||
|
||||
defp find_invalid(list) when is_list(list), do:
|
||||
Enum.find_value(list, &find_invalid/1)
|
||||
|
||||
defp find_invalid(pid) when is_pid(pid), do: nil
|
||||
defp find_invalid(atom) when is_atom(atom), do: nil
|
||||
defp find_invalid(num) when is_number(num), do: nil
|
||||
defp find_invalid(bin) when is_binary(bin), do: nil
|
||||
|
||||
defp find_invalid(fun) when is_function(fun) do
|
||||
unless :erlang.fun_info(fun, :env) == {:env, []} and
|
||||
:erlang.fun_info(fun, :type) == {:type, :external} do
|
||||
{:error, fun}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_invalid(other), do: {:error, other}
|
||||
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
@@ -307,9 +348,9 @@ defmodule Macro do
|
||||
single- and double-quoted strings. Check `unescape_string/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
|
||||
also escaped according to the latin1 set they represent.
|
||||
In this setup, Elixir will escape the following: `\0`, `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Hexadecimals
|
||||
are also supported via `\xNN` and `\x{NN...}` syntax.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
@@ -340,6 +381,7 @@ defmodule Macro do
|
||||
representing the codepoint of the character it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?0), do: ?0
|
||||
def unescape_map(?a), do: ?\a
|
||||
def unescape_map(?b), do: ?\b
|
||||
def unescape_map(?d), do: ?\d
|
||||
@@ -350,20 +392,14 @@ defmodule Macro do
|
||||
def unescape_map(?s), do: ?\s
|
||||
def unescape_map(?t), do: ?\t
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(?x), do: true
|
||||
def unescape_map(e), do: e
|
||||
|
||||
If the `unescape_map` function returns `false`. The char is
|
||||
not escaped and `\` is kept in the char list.
|
||||
|
||||
## Octals
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns `false` for `?0`.
|
||||
|
||||
## Hex
|
||||
|
||||
Hexadecimals will by default be escaped unless the map function
|
||||
returns `false` for `?x`.
|
||||
Hexadecimals will be escaped if the map function returns `true`
|
||||
for `?x`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -525,11 +561,15 @@ defmodule Macro do
|
||||
|
||||
# All other calls
|
||||
def to_string({target, _, args} = ast, fun) when is_list(args) do
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
end)
|
||||
if sigil = sigil_call(ast, fun) do
|
||||
sigil
|
||||
else
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
@@ -565,6 +605,26 @@ 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, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
|
||||
sigil =
|
||||
case Atom.to_string(func) do
|
||||
<<"sigil_", name>> ->
|
||||
"~" <> <<name>> <>
|
||||
fun.(string, inspect(string, [])) <>
|
||||
sigil_args(args, fun)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
fun.(ast, sigil)
|
||||
end
|
||||
|
||||
defp sigil_call(_other, _fun) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp sigil_args([], _fun), do: ""
|
||||
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
@@ -773,7 +833,7 @@ defmodule Macro do
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{receiver, true}
|
||||
aliases ->
|
||||
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
aliases = :lists.map(&elem(do_expand_once(&1, env), 0), aliases)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
@@ -838,7 +898,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
|
||||
env, extra)
|
||||
env, extra, true)
|
||||
|
||||
case expand do
|
||||
{:ok, receiver, quoted} ->
|
||||
@@ -879,7 +939,7 @@ defmodule Macro do
|
||||
be expanded.
|
||||
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
`expand_once/2` for more information and examples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({tree, true}, env)
|
||||
|
||||
@@ -4,7 +4,22 @@ defmodule Macro.Env do
|
||||
|
||||
The current environment can be accessed at any time as
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
accessed as `__CALLER__`. It contains the following fields:
|
||||
accessed as `__CALLER__`.
|
||||
|
||||
An instance of `Macro.Env` must not be modified by hand. If you need to
|
||||
create a custom environment to pass to `Code.eval_quoted/3`, use the
|
||||
following trick:
|
||||
|
||||
def make_custom_env do
|
||||
import SomeModule, only: [some_function: 2]
|
||||
alias A.B.C
|
||||
__ENV__
|
||||
end
|
||||
|
||||
You may then call `make_custom_env()` to get a struct with the desired
|
||||
imports and aliases included.
|
||||
|
||||
It contains the following fields:
|
||||
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
@@ -29,7 +44,7 @@ defmodule Macro.Env do
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: {atom, non_neg_integer}
|
||||
@type name_arity :: {atom, arity}
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{module, module}]
|
||||
@@ -42,10 +57,10 @@ defmodule Macro.Env do
|
||||
@type vars :: [{atom, atom | non_neg_integer}]
|
||||
@type export_vars :: vars | nil
|
||||
@type lexical_tracker :: pid
|
||||
@type local :: module | nil
|
||||
@type local :: atom | nil
|
||||
|
||||
@type t :: %{__struct__: __MODULE__,
|
||||
module: module,
|
||||
module: atom,
|
||||
file: file,
|
||||
line: line,
|
||||
function: name_arity | nil,
|
||||
@@ -105,9 +120,9 @@ defmodule Macro.Env do
|
||||
"""
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(env.module) ->
|
||||
is_nil(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
is_nil(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{name, arity} = env.function
|
||||
|
||||
@@ -38,5 +38,32 @@ defmodule Map do
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a struct to map.
|
||||
|
||||
It accepts the struct module or a struct itself and
|
||||
simply removes the `__struct__` field from the struct.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule User do
|
||||
defstruct [:name]
|
||||
end
|
||||
|
||||
Map.from_struct(User)
|
||||
#=> %{name: nil}
|
||||
|
||||
Map.from_struct(%User{name: "john"})
|
||||
#=> %{name: "john"}
|
||||
|
||||
"""
|
||||
def from_struct(struct) when is_atom(struct) do
|
||||
:maps.remove(:__struct__, struct.__struct__)
|
||||
end
|
||||
|
||||
def from_struct(%{__struct__: _} = struct) do
|
||||
:maps.remove(:__struct__, struct)
|
||||
end
|
||||
|
||||
def equal?(%{} = map1, %{} = map2), do: map1 === map2
|
||||
end
|
||||
|
||||
+75
-89
@@ -85,6 +85,9 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
Several uses of `@compile` will accumulate instead of overriding
|
||||
previous ones.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@@ -476,19 +479,6 @@ defmodule Module do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an anonymous function from the given module, function
|
||||
and arity. The module and function are not verified to exist.
|
||||
|
||||
iex> fun = Module.function(Kernel, :is_atom, 1)
|
||||
iex> fun.(:hello)
|
||||
true
|
||||
|
||||
"""
|
||||
def function(mod, fun, arity) do
|
||||
:erlang.make_fun(mod, fun, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
@@ -514,23 +504,23 @@ defmodule Module do
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
table = data_table_for(module)
|
||||
|
||||
{signature, _} = :lists.mapfoldl fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end, 1, signature
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
case :ets.lookup(table, {:doc, tuple}) do
|
||||
[] ->
|
||||
:ets.insert(table, {tuple, line, kind, signature, doc})
|
||||
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
|
||||
:ok
|
||||
[{tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
doc_tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
if(is_nil(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
end
|
||||
@@ -622,7 +612,7 @@ defmodule Module do
|
||||
"""
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
table = defs_table_for(module)
|
||||
:ets.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@@ -642,7 +632,7 @@ defmodule Module do
|
||||
"""
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
table = defs_table_for(module)
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{_, ^kind, _, _, _, _, _}] -> true
|
||||
_ -> false
|
||||
@@ -662,8 +652,8 @@ defmodule Module do
|
||||
"""
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
|
||||
table = defs_table_for(module)
|
||||
:lists.concat :ets.match(table, {:'$1', :_, :_, :_, :_, :_, :_})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -681,8 +671,8 @@ defmodule Module do
|
||||
"""
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
table = defs_table_for(module)
|
||||
:lists.concat :ets.match(table, {:'$1', kind, :_, :_, :_, :_, :_})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -694,7 +684,7 @@ defmodule Module do
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
|
||||
for tuple <- tuples do
|
||||
:lists.foreach(fn tuple ->
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{name, arity} = tuple
|
||||
@@ -702,27 +692,26 @@ defmodule Module do
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
|
||||
neighbours = if loaded?(Module.LocalsTracker) do
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
else
|
||||
neighbours = if :elixir_compiler.get_opt(:internal) do
|
||||
[]
|
||||
else
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
old = :elixir_def_overridable.overridable(module)
|
||||
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
|
||||
{count + 1, clause, neighbours, false}
|
||||
end, {1, clause, neighbours, false}, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
:elixir_def_overridable.overridable(module, merged)
|
||||
end
|
||||
end
|
||||
end, tuples)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -741,7 +730,7 @@ defmodule Module do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
|
||||
new =
|
||||
if :lists.member(key, acc) do
|
||||
@@ -766,15 +755,9 @@ defmodule Module do
|
||||
|
||||
@foo
|
||||
|
||||
Expands to:
|
||||
Expands close to:
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument may be given to indicate a stacktrace
|
||||
to be emitted when the attribute was not previously defined.
|
||||
The default value for `warn` is nil for direct calls but the `@foo`
|
||||
macro sets it to the proper stacktrace automatically, warning
|
||||
every time `@foo` is used but not set previously.
|
||||
Module.get_attribute(__MODULE__, :foo)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -788,37 +771,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
|
||||
def get_attribute(module, key, warn \\ nil) when
|
||||
is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
@spec get_attribute(atom, atom) :: term
|
||||
def get_attribute(module, key) do
|
||||
get_attribute(module, key, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -832,10 +787,12 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec delete_attribute(atom, atom) :: :ok
|
||||
def delete_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:delete_attribute, module)
|
||||
table = data_table_for(module)
|
||||
:ets.delete(table, key)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -874,13 +831,13 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, {:__persisted_attributes, [new|old]})
|
||||
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
|
||||
:ets.insert(table, {{:elixir, :persisted_attributes}, [new|old]})
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, {:__acc_attributes, [new|old]})
|
||||
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
:ets.insert(table, {{:elixir, :acc_attributes}, [new|old]})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -911,7 +868,9 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, :private_doc} ->
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
:elixir_errors.warn line, env.file,
|
||||
"function #{name}/#{arity} is private, " <>
|
||||
"@doc's are always discarded for private functions"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -933,6 +892,38 @@ defmodule Module do
|
||||
:ets.insert(table, {key, new})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
|
||||
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access"
|
||||
nil
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
||||
@@ -940,7 +931,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
Code.ensure_compiled(atom)
|
||||
# Attempt to compile behaviour but ignore failure (will warn later)
|
||||
_ = Code.ensure_compiled(atom)
|
||||
atom
|
||||
end
|
||||
|
||||
@@ -963,15 +955,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp data_table_for(module) do
|
||||
module
|
||||
:elixir_module.data_table(module)
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
:elixir_def.table(module)
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
:elixir_module.docs_table(module)
|
||||
defp defs_table_for(module) do
|
||||
:elixir_module.defs_table(module)
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
@@ -979,6 +967,4 @@ defmodule Module do
|
||||
raise ArgumentError,
|
||||
"could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp loaded?(module), do: is_tuple :code.is_loaded(module)
|
||||
end
|
||||
|
||||
@@ -65,22 +65,25 @@ defmodule Module.LocalsTracker do
|
||||
"""
|
||||
@spec reachable(ref) :: [local]
|
||||
def reachable(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
reduce_reachable(d, :local, [])
|
||||
reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
|
||||
end
|
||||
|
||||
defp reachable_from(d, starting) do
|
||||
:sets.to_list(reduce_reachable(d, starting, :sets.new))
|
||||
end
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
|
||||
remaining = :sets.subtract(neighbours, vertices)
|
||||
vertices = :sets.union(neighbours, vertices)
|
||||
:sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{_, val}] = :ets.lookup(table, :__locals_tracker)
|
||||
[{_, val}] = :ets.lookup(table, {:elixir, :locals_tracker})
|
||||
val
|
||||
end
|
||||
|
||||
@@ -89,8 +92,7 @@ defmodule Module.LocalsTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
:gen_server.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
# Adds a definition into the tracker. A public
|
||||
@@ -134,9 +136,7 @@ defmodule Module.LocalsTracker do
|
||||
# Reattach a previously yanked node
|
||||
@doc false
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, {:reattach, tuple, neighbours})
|
||||
:gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@@ -156,12 +156,42 @@ defmodule Module.LocalsTracker do
|
||||
# given also accounting the expected amount of default
|
||||
# clauses a private function have.
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
||||
def collect_unused_locals(ref, private) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
{unreachable(d, private), collect_warnings(d, private)}
|
||||
end
|
||||
|
||||
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
|
||||
defp unreachable(d, private) do
|
||||
unreachable = for {tuple, _, _} <- private, do: tuple
|
||||
|
||||
private =
|
||||
for {tuple, :defp, _} <- private do
|
||||
neighbours = :digraph.in_neighbours(d, tuple)
|
||||
neighbours = for {_, _} = t <- neighbours, do: t
|
||||
{tuple, :sets.from_list(neighbours)}
|
||||
end
|
||||
|
||||
reduce_unreachable(private, [], :sets.from_list(unreachable))
|
||||
end
|
||||
|
||||
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
|
||||
if :sets.is_subset(callers, unreachable) do
|
||||
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
|
||||
else
|
||||
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
|
||||
end
|
||||
end
|
||||
|
||||
defp reduce_unreachable([], _acc, unreachable) do
|
||||
:sets.to_list(unreachable)
|
||||
end
|
||||
|
||||
defp collect_warnings(d, private) do
|
||||
reachable = reachable_from(d, :local)
|
||||
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
@@ -169,7 +199,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
|
||||
defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
|
||||
{name, arity} = tuple
|
||||
min = arity - default
|
||||
max = arity
|
||||
@@ -211,6 +241,7 @@ defmodule Module.LocalsTracker do
|
||||
{:ok, {d, []}}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call({:cache_env, env}, _from, {d, cache}) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
@@ -227,10 +258,9 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_call({:yank, local}, _from, {d, _} = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{:reply, {in_vertices, out_vertices}, state}
|
||||
:digraph.del_edges(d, :digraph.out_edges(d, local))
|
||||
{:reply, {[], out_vertices}, state}
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, {d, _} = state) do
|
||||
@@ -241,6 +271,7 @@ defmodule Module.LocalsTracker do
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
@@ -268,9 +299,17 @@ defmodule Module.LocalsTracker do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
||||
def handle_cast({:reattach, _kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh do
|
||||
:digraph.add_vertex(d, from)
|
||||
replace_edge!(d, from, tuple)
|
||||
end
|
||||
|
||||
for to <- out_neigh do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, tuple, to)
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@@ -282,10 +321,12 @@ defmodule Module.LocalsTracker do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
|
||||
if function != nil do
|
||||
replace_edge!(d, function, tuple)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_add_local(d, from, to) do
|
||||
@@ -317,14 +360,9 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
_ = unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
|
||||
defp replace_edge(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
:digraph.add_edge(d, from, to)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ defmodule Node do
|
||||
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
|
||||
local node is not alive.
|
||||
"""
|
||||
@spec stop() :: :ok | {:error, term}
|
||||
@spec stop() :: :ok | {:error, :not_allowed | :not_found}
|
||||
def stop() do
|
||||
:net_kernel.stop()
|
||||
end
|
||||
|
||||
+122
-59
@@ -45,7 +45,7 @@ defmodule OptionParser do
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error.
|
||||
|
||||
* `:switches` - configure some switches. Switches that does not
|
||||
* `:switches` - defines some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed.
|
||||
|
||||
Note only `:strict` or `:switches` may be given at once.
|
||||
@@ -59,7 +59,7 @@ 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 specfied in the strict case,
|
||||
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).
|
||||
|
||||
@@ -91,14 +91,8 @@ defmodule OptionParser do
|
||||
|
||||
## Negation switches
|
||||
|
||||
All switches starting with `--no-` are considered to be booleans and never
|
||||
parse the next value:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{[no_op: true], ["path/to/file"], []}
|
||||
|
||||
However, in case the base switch exists, it sets that particular switch to
|
||||
false:
|
||||
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
||||
which will set the option to false:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
@@ -113,8 +107,7 @@ defmodule OptionParser do
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
config = compile_config(opts, true)
|
||||
do_parse(argv, config, [], [], [])
|
||||
do_parse(argv, compile_config(opts), [], [], [], true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -134,37 +127,36 @@ defmodule OptionParser do
|
||||
"""
|
||||
@spec parse_head(argv, options) :: {parsed, argv, errors}
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
config = compile_config(opts, false)
|
||||
do_parse(argv, config, [], [], [])
|
||||
do_parse(argv, compile_config(opts), [], [], [], false)
|
||||
end
|
||||
|
||||
defp do_parse([], _config, opts, args, invalid) do
|
||||
defp do_parse([], _config, opts, args, invalid, _all?) do
|
||||
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
|
||||
defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
|
||||
case next(argv, aliases, switches, strict) do
|
||||
{:ok, option, value, rest} ->
|
||||
# the option exist and it was successfully parsed
|
||||
kinds = List.wrap Keyword.get(switches, option)
|
||||
new_opts = do_store_option(opts, option, value, kinds)
|
||||
do_parse(rest, config, new_opts, args, invalid)
|
||||
do_parse(rest, config, new_opts, args, invalid, all?)
|
||||
|
||||
{:invalid, option, value, rest} ->
|
||||
# the option exist but it has wrong value
|
||||
do_parse(rest, config, opts, args, [{option, value}|invalid])
|
||||
do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
|
||||
|
||||
{:undefined, option, _value, rest} ->
|
||||
# the option does not exist (for strict cases)
|
||||
do_parse(rest, config, opts, args, [{option, nil}|invalid])
|
||||
do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
|
||||
|
||||
{:error, ["--"|rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg|rest]=remaining_args} ->
|
||||
# there is no option
|
||||
if all do
|
||||
do_parse(rest, config, opts, [arg|args], invalid)
|
||||
if all? do
|
||||
do_parse(rest, config, opts, [arg|args], invalid, all?)
|
||||
else
|
||||
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
|
||||
end
|
||||
@@ -193,12 +185,12 @@ defmodule OptionParser do
|
||||
|
||||
@spec next(argv, options) ::
|
||||
{:ok, key :: atom, value :: term, argv} |
|
||||
{:invalid, key :: atom, value :: term, argv} |
|
||||
{:undefined, key :: atom, value :: term, argv} |
|
||||
{:invalid, String.t, String.t | nil, argv} |
|
||||
{:undefined, String.t, String.t | nil, argv} |
|
||||
{:error, argv}
|
||||
|
||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
{aliases, switches, strict, _} = compile_config(opts, true)
|
||||
{aliases, switches, strict} = compile_config(opts)
|
||||
next(argv, aliases, switches, strict)
|
||||
end
|
||||
|
||||
@@ -221,7 +213,7 @@ defmodule OptionParser do
|
||||
defp next(["-" <> option|rest], aliases, switches, strict) do
|
||||
{option, value} = split_option(option)
|
||||
opt_name_bin = "-" <> option
|
||||
tagged = tag_option(option, value, switches, aliases)
|
||||
tagged = tag_option(option, switches, aliases)
|
||||
|
||||
if strict and not option_defined?(tagged, switches) do
|
||||
{:undefined, opt_name_bin, value, rest}
|
||||
@@ -239,9 +231,95 @@ defmodule OptionParser do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a key-value enumerable and convert it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
and all other values are converted using `to_string/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.to_argv([foo_bar: "baz"])
|
||||
["--foo-bar", "baz"]
|
||||
|
||||
iex> OptionParser.to_argv([bool: true, bool: false, discarded: nil])
|
||||
["--bool", "--no-bool"]
|
||||
|
||||
"""
|
||||
@spec to_argv(Enumerable.t) :: argv
|
||||
def to_argv(enum) do
|
||||
Enum.flat_map(enum, fn
|
||||
{_key, nil} -> []
|
||||
{key, true} -> [to_switch(key)]
|
||||
{key, false} -> [to_switch(key, "--no-")]
|
||||
{key, value} -> [to_switch(key), to_string(value)]
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_switch(key, prefix \\ "--") when is_atom(key) do
|
||||
prefix <> String.replace(Atom.to_string(key), "_", "-")
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Splits a string into argv chunks.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.split("foo bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> OptionParser.split("foo \"bar baz\"")
|
||||
["foo", "bar baz"]
|
||||
"""
|
||||
@spec split(String.t) :: argv
|
||||
def split(string) do
|
||||
do_split(strip_leading_spaces(string), "", [], nil)
|
||||
end
|
||||
|
||||
# If we have a escaped quote, simply remove the escape
|
||||
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 [?", ?'],
|
||||
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),
|
||||
do: do_split(t, buffer, acc, nil)
|
||||
|
||||
# If we have a 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, ?', ?"],
|
||||
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),
|
||||
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
|
||||
|
||||
# All other characters are moved to buffer
|
||||
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
|
||||
do_split(t, <<buffer::binary, h>>, acc, quote)
|
||||
end
|
||||
|
||||
# Finish the string expecting a nil marker
|
||||
defp do_split(<<>>, "", acc, nil),
|
||||
do: Enum.reverse(acc)
|
||||
|
||||
defp do_split(<<>>, buffer, acc, nil),
|
||||
do: Enum.reverse([buffer|acc])
|
||||
|
||||
# Otherwise raise
|
||||
defp do_split(<<>>, _, _acc, marker) do
|
||||
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
|
||||
end
|
||||
|
||||
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
|
||||
defp strip_leading_spaces(t), do: t
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_config(opts, all) do
|
||||
defp compile_config(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
|
||||
{switches, strict} = cond do
|
||||
@@ -253,7 +331,7 @@ defmodule OptionParser do
|
||||
{[], false}
|
||||
end
|
||||
|
||||
{aliases, switches, strict, all}
|
||||
{aliases, switches, strict}
|
||||
end
|
||||
|
||||
defp validate_option(value, kinds) do
|
||||
@@ -296,11 +374,11 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
get_negated(option, value, switches)
|
||||
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
|
||||
get_negated(option, switches)
|
||||
end
|
||||
|
||||
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
|
||||
defp tag_option(option, _switches, aliases) when is_binary(option) do
|
||||
opt = get_option(option)
|
||||
if alias = aliases[opt] do
|
||||
{:default, alias}
|
||||
@@ -325,23 +403,14 @@ defmodule OptionParser do
|
||||
{nil, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, nil, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{option, kinds, false}
|
||||
kinds == [] ->
|
||||
{option, kinds, true}
|
||||
true ->
|
||||
{reverse_negated(option), [:invalid], nil}
|
||||
defp normalize_option({:negated, option}, value, switches) do
|
||||
if value do
|
||||
{option, [:invalid], value}
|
||||
else
|
||||
{option, List.wrap(switches[option]), false}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
end
|
||||
@@ -396,24 +465,18 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp reverse_negated(negated) do
|
||||
String.to_atom("no_" <> Atom.to_string(negated))
|
||||
end
|
||||
|
||||
defp get_negated("no-" <> rest = option, value, switches) do
|
||||
if negated = get_option(rest) do
|
||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
||||
negated
|
||||
else
|
||||
get_option(option)
|
||||
end
|
||||
{:negated, option}
|
||||
else
|
||||
:unknown
|
||||
defp get_negated("no-" <> rest = original, switches) do
|
||||
cond do
|
||||
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
|
||||
{:negated, negated}
|
||||
option = get_option(original) ->
|
||||
{:default, option}
|
||||
true ->
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp get_negated(rest, _value, _switches) do
|
||||
defp get_negated(rest, _switches) do
|
||||
if option = get_option(rest) do
|
||||
{:default, option}
|
||||
else
|
||||
|
||||
+129
-77
@@ -60,40 +60,70 @@ defmodule Path do
|
||||
def absname(path, relative_to) do
|
||||
path = IO.chardata_to_string(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
cond do
|
||||
path == "/" ->
|
||||
path
|
||||
:binary.last(path) == ?/ ->
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
true ->
|
||||
path
|
||||
end
|
||||
:relative -> absname_join(relative_to, path)
|
||||
:absolute -> absname_join([path])
|
||||
:volumerelative ->
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
|
||||
## Absolute path on current drive
|
||||
# Absolute path on current drive
|
||||
defp absname_vr(["/"|rest], [volume|_], _relative),
|
||||
do: join([volume|rest])
|
||||
do: absname_join([volume|rest])
|
||||
|
||||
## Relative to current directory on current drive.
|
||||
# Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
|
||||
do: absname(join(rest), relative)
|
||||
do: absname(absname_join(rest), relative)
|
||||
|
||||
## Relative to current directory on another drive.
|
||||
# Relative to current directory on another drive.
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
absname(absname_join(name), cwd)
|
||||
end
|
||||
|
||||
# Joins a list
|
||||
defp absname_join([name1, name2|rest]), do:
|
||||
absname_join([absname_join(name1, name2)|rest])
|
||||
defp absname_join([name]), do:
|
||||
do_absname_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
|
||||
# Joins two paths
|
||||
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:
|
||||
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:
|
||||
do_absname_join(rest, relativename, [?/|result], os_type)
|
||||
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))
|
||||
defp do_absname_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_absname_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_absname_join(<<>>, relativename, [?/|result], os_type), 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:
|
||||
do_absname_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp reverse_maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp reverse_maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp reverse_maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Converts the path to an absolute one and expands
|
||||
any `.` and `..` characters and a leading `~`.
|
||||
@@ -106,7 +136,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t) :: binary
|
||||
def expand(path) do
|
||||
normalize absname(expand_home(path), System.cwd!)
|
||||
expand_dot absname(expand_home(path), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -133,7 +163,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec expand(t, t) :: binary
|
||||
def expand(path, relative_to) do
|
||||
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
expand_dot absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -157,10 +187,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec type(t) :: :absolute | :relative | :volumerelative
|
||||
def type(name) when is_list(name) or is_binary(name) do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(0)
|
||||
pathtype(name, major_os_type) |> elem(0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -182,10 +209,20 @@ defmodule Path do
|
||||
"""
|
||||
@spec relative(t) :: binary
|
||||
def relative(name) do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1) |> IO.chardata_to_string
|
||||
relative(name, major_os_type())
|
||||
end
|
||||
|
||||
defp relative(name, os_type) do
|
||||
pathtype(name, os_type)
|
||||
|> elem(1)
|
||||
|> IO.chardata_to_string
|
||||
end
|
||||
|
||||
defp pathtype(name, os_type) do
|
||||
case os_type do
|
||||
:win32 -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
@@ -390,7 +427,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string with one or more path components joined by the path separator.
|
||||
Joins a list of strings.
|
||||
|
||||
This function should be used to convert a list of strings to a path.
|
||||
Note that any trailing slash is removed on join.
|
||||
@@ -411,11 +448,14 @@ defmodule Path do
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]), do:
|
||||
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
name
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
|
||||
The right path will always be expanded to its relative format
|
||||
and any trailing slash is removed on join.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Path.join("foo", "bar")
|
||||
@@ -423,44 +463,34 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
def join(left, right) do
|
||||
left = IO.chardata_to_string(left)
|
||||
os_type = major_os_type()
|
||||
do_join(left, right, os_type) |> remove_dirsep(os_type)
|
||||
end
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
defp do_join("", right, os_type), do: relative(right, os_type)
|
||||
defp do_join(left, "", _os_type), do: left
|
||||
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
defp remove_dirsep("", _os_type), do: ""
|
||||
defp remove_dirsep(bin, os_type) do
|
||||
last = :binary.last(bin)
|
||||
if last == ?/ or (last == ?\\ and os_type == :win32) do
|
||||
binary_part(bin, 0, byte_size(bin) - 1)
|
||||
else
|
||||
bin
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
@doc ~S"""
|
||||
Splits the path into a list at the path separator.
|
||||
|
||||
If an empty string is given, returns an empty list.
|
||||
|
||||
On Windows, path is split on both "\" and "/" separators
|
||||
and the driver letter, if there is one, is always returned
|
||||
in lowercase.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -486,6 +516,11 @@ defmodule Path do
|
||||
defmodule Wildcard do
|
||||
@moduledoc false
|
||||
|
||||
def read_link_info(file) do
|
||||
call({:read_link_info, file})
|
||||
end
|
||||
|
||||
# For compatibility with buggy Erlang 17.1.
|
||||
def read_file_info(file) do
|
||||
call({:read_link_info, file})
|
||||
end
|
||||
@@ -558,7 +593,7 @@ defmodule Path do
|
||||
|> Enum.map(&IO.chardata_to_string/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
# expand_dot the given path by expanding "..", "." and "~".
|
||||
|
||||
defp chardata_to_list(chardata) do
|
||||
case :unicode.characters_to_list(chardata) do
|
||||
@@ -575,34 +610,51 @@ defmodule Path do
|
||||
|
||||
defp expand_home(type) do
|
||||
case IO.chardata_to_string(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
"~" <> rest -> resolve_home(rest)
|
||||
rest -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
defp resolve_home(""), do: System.user_home!
|
||||
|
||||
defp normalize([".."|t], ["/"|_] = acc) do
|
||||
normalize t, acc
|
||||
defp resolve_home(rest) do
|
||||
case {rest, major_os_type} do
|
||||
{"\\" <> _, :win32} ->
|
||||
System.user_home! <> rest
|
||||
{"/" <> _, _} ->
|
||||
System.user_home! <> rest
|
||||
_ -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
|
||||
normalize t, acc
|
||||
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(path),
|
||||
do: expand_dot(:binary.split(path, "/", [:global]), [])
|
||||
|
||||
defp expand_dot([".."|t], [_, _|acc]) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
defp expand_dot(["."|t], acc) do
|
||||
expand_dot t, acc
|
||||
end
|
||||
|
||||
defp normalize(["."|t], acc) do
|
||||
normalize t, acc
|
||||
defp expand_dot([h|t], acc) do
|
||||
expand_dot t, ["/", h|acc]
|
||||
end
|
||||
|
||||
defp normalize([h|t], acc) do
|
||||
normalize t, [h|acc]
|
||||
defp expand_dot([], ["/"|acc]) do
|
||||
IO.iodata_to_binary(:lists.reverse(acc))
|
||||
end
|
||||
|
||||
defp normalize([], acc) do
|
||||
join :lists.reverse(acc)
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def open(name, settings) do
|
||||
:erlang.open_port(name, settings)
|
||||
@@ -12,6 +14,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def close(port) do
|
||||
:erlang.port_close(port)
|
||||
@@ -19,6 +23,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def command(port, data, options \\ []) do
|
||||
:erlang.port_command(port, data, options)
|
||||
@@ -26,6 +32,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def connect(port, pid) do
|
||||
:erlang.port_connect(port, pid)
|
||||
@@ -33,6 +41,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def control(port, operation, data) do
|
||||
:erlang.port_control(port, operation, data)
|
||||
@@ -40,6 +50,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def call(port, operation, data) do
|
||||
:erlang.port_call(port, operation, data)
|
||||
@@ -47,6 +59,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def info(port) do
|
||||
:erlang.port_info(port)
|
||||
@@ -54,6 +68,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def info(port, item) do
|
||||
:erlang.port_info(port, item)
|
||||
@@ -61,6 +77,8 @@ defmodule Port do
|
||||
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#ports-0.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
def list do
|
||||
:erlang.ports
|
||||
|
||||
+24
-24
@@ -34,7 +34,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for the given key.
|
||||
Returns the value for the given `key`.
|
||||
"""
|
||||
@spec get(term) :: term
|
||||
@spec get(term, default :: term) :: term
|
||||
@@ -64,7 +64,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the given key from the dictionary.
|
||||
Deletes the given `key` from the dictionary.
|
||||
"""
|
||||
@spec delete(term) :: term | nil
|
||||
def delete(key) do
|
||||
@@ -79,11 +79,11 @@ defmodule Process do
|
||||
1. If pid is not trapping exits, pid will exit with the given reason.
|
||||
|
||||
2. If pid is trapping exits, the exit signal is transformed into a message
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid.
|
||||
`{:EXIT, from, reason}` and delivered to the message queue of pid.
|
||||
|
||||
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
|
||||
exits, the exit signal is transformed into a message {:EXIT, from,
|
||||
:normal} and delivered to its message queue.
|
||||
exits, the exit signal is transformed into a message `{:EXIT, from,
|
||||
:normal}` and delivered to its message queue.
|
||||
|
||||
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
|
||||
an untrappable exit signal is sent to pid which will unconditionally
|
||||
@@ -129,10 +129,10 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends `msg` to `dest` after `time` millisecons.
|
||||
Sends `msg` to `dest` after `time` milliseconds.
|
||||
|
||||
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it is supposed to be the name of a registered process
|
||||
If `dest` is a pid, it must be the pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it must be the name of a registered process
|
||||
which is looked up at the time of delivery. No error is given if the name does
|
||||
not refer to a process.
|
||||
|
||||
@@ -208,8 +208,8 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
If monitor_ref is a reference which the calling process
|
||||
obtained by calling monitor/1, this monitoring is turned off.
|
||||
If `monitor_ref` is a reference which the calling process
|
||||
obtained by calling `monitor/1`, this monitoring is turned off.
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
||||
@@ -227,7 +227,7 @@ defmodule Process do
|
||||
processes currently existing on the local node.
|
||||
|
||||
Note that a process that is exiting, exists but is not alive, i.e.,
|
||||
alive?/1 will return false for a process that is exiting,
|
||||
`alive?/1` will return `false` for a process that is exiting,
|
||||
but its process identifier will be part of the result returned.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
|
||||
@@ -252,7 +252,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Removes the link, if there is one, between the calling process and
|
||||
the process or port referred to by `pid`. Returns true and does not
|
||||
the process or port referred to by `pid`. Returns `true` and does not
|
||||
fail, even if there is no link or `id` does not exist
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||
@@ -265,13 +265,13 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Associates the name with a pid or a port identifier. name, which must
|
||||
Associates the name with a pid or a port identifier. `name`, which must
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
||||
is no longer alive, (check with `alive?/1`) or if the name is
|
||||
already registered (check with `registered?/1`).
|
||||
already registered (check with `whereis/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) when not name in [nil, false, true] do
|
||||
@@ -290,7 +290,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Returns the pid or port identifier with the registered name.
|
||||
Returns nil if the name is not registered.
|
||||
Returns `nil` if the name is not registered.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
|
||||
"""
|
||||
@@ -309,7 +309,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
|
||||
started from a certain shell should have another group leader than `:init`.
|
||||
started from a certain shell should have a group leader other than `:init`.
|
||||
"""
|
||||
@spec group_leader(pid, leader :: pid) :: true
|
||||
def group_leader(pid, leader) do
|
||||
@@ -317,7 +317,7 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of names which have been registered using register/2.
|
||||
Returns a list of names which have been registered using `register/2`.
|
||||
"""
|
||||
@spec registered :: [atom]
|
||||
def registered do
|
||||
@@ -339,19 +339,19 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets certain flags for the process Pid, in the same manner as flag/2.
|
||||
Returns the old value of the flag. The allowed values for Flag are
|
||||
only a subset of those allowed in flag/2, namely: save_calls.
|
||||
Sets certain flags for the process `pid`, in the same manner as `flag/2`.
|
||||
Returns the old value of the flag. The allowed values for `flag` are
|
||||
only a subset of those allowed in `flag/2`, namely: `save_calls`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
|
||||
"""
|
||||
@spec flag(pid, process_flag, term) :: term
|
||||
@spec flag(pid, :save_calls, non_neg_integer) :: non_neg_integer
|
||||
def flag(pid, flag, value) do
|
||||
:erlang.process_flag(pid, flag, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid or nil if the process
|
||||
Returns information about the process identified by `pid` or `nil` if the process
|
||||
is not alive.
|
||||
Use this only for debugging information.
|
||||
|
||||
@@ -363,8 +363,8 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid
|
||||
or nil if the process is not alive.
|
||||
Returns information about the process identified by `pid`
|
||||
or `nil` if the process is not alive.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
||||
"""
|
||||
|
||||
+48
-25
@@ -19,11 +19,12 @@ defmodule Protocol do
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = :lists.map(fn _ -> quote(do: term) end,
|
||||
:lists.seq(2, arity))
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
call_args = :lists.map(fn i -> {String.to_atom(<<?x, i + 64>>), [], __MODULE__} end,
|
||||
:lists.seq(2, arity))
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
quote do
|
||||
@@ -85,11 +86,13 @@ defmodule Protocol do
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_impl!(module, module) :: :ok | no_return
|
||||
def assert_impl!(protocol, impl) do
|
||||
assert_impl!(protocol, impl, "")
|
||||
def assert_impl!(protocol, base) do
|
||||
assert_impl!(protocol, base, "")
|
||||
end
|
||||
|
||||
defp assert_impl!(protocol, impl, extra) do
|
||||
defp assert_impl!(protocol, base, extra) do
|
||||
impl = Module.concat(protocol, base)
|
||||
|
||||
case Code.ensure_compiled(impl) do
|
||||
{:module, ^impl} -> :ok
|
||||
_ -> raise ArgumentError,
|
||||
@@ -287,7 +290,7 @@ defmodule Protocol do
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
atom when is_atom(atom) -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
@@ -426,7 +429,7 @@ defmodule Protocol do
|
||||
|
||||
defp after_defprotocol do
|
||||
quote bind_quoted: [builtin: builtin] do
|
||||
@spec impl_for(term) :: module | nil
|
||||
@spec impl_for(term) :: atom() | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
@@ -438,7 +441,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- builtin do
|
||||
:lists.foreach(fn {guard, mod} ->
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
@@ -447,9 +450,9 @@ defmodule Protocol do
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
end, builtin)
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
@spec impl_for!(term) :: atom() | no_return()
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
@@ -494,12 +497,19 @@ defmodule Protocol do
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
@spec __protocol__(atom) :: term
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
@spec __protocol__(:name) :: __MODULE__
|
||||
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __functions_spec__([]),
|
||||
do: []
|
||||
def __functions_spec__([h|t]),
|
||||
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
|
||||
|
||||
@doc false
|
||||
def __impl__(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
@@ -510,6 +520,20 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
# Unquote the implementation just later
|
||||
# when all variables will already be injected
|
||||
# into the module body.
|
||||
__impl__ =
|
||||
quote unquote: false do
|
||||
@doc false
|
||||
@spec __impl__(:for) :: unquote(for)
|
||||
@spec __impl__(:target) :: __MODULE__
|
||||
@spec __impl__(:protocol) :: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
end
|
||||
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
@@ -527,11 +551,7 @@ defmodule Protocol do
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(atom) :: term
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
unquote(__impl__)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -557,14 +577,14 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
impl = Module.concat(protocol, Map)
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
assert_impl!(protocol, impl, extra)
|
||||
assert_impl!(protocol, Map, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
args = [for, struct, opts]
|
||||
impl = Module.concat(protocol, Map)
|
||||
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
||||
mod, fun, args ->
|
||||
@@ -576,7 +596,9 @@ defmodule Protocol do
|
||||
@impl [protocol: unquote(protocol), for: unquote(for)]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(atom) :: term
|
||||
@spec __impl__(:target) :: unquote(impl)
|
||||
@spec __impl__(:protocol) :: unquote(protocol)
|
||||
@spec __impl__(:for) :: unquote(for)
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
@@ -588,16 +610,17 @@ defmodule Protocol do
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
found =
|
||||
for {:spec, expr, caller} <- specs,
|
||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
:lists.map(fn {:spec, expr, caller} ->
|
||||
if Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
end, specs)
|
||||
|
||||
found != []
|
||||
:lists.any(& &1 == true, found)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
|
||||
A Range are represented internally as a struct. However,
|
||||
A Range is represented internally as a struct. However,
|
||||
the most common form of creating and matching on ranges
|
||||
is via the `../2` macro, auto-imported from Kernel:
|
||||
|
||||
@@ -18,7 +18,8 @@ defmodule Range do
|
||||
|
||||
defstruct first: nil, last: nil
|
||||
|
||||
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
|
||||
@type t :: %Range{}
|
||||
@type t(first, last) :: %Range{first: first, last: last}
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
|
||||
+73
-20
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.record? {User, "jose", 27}
|
||||
iex> Record.is_record {User, "john", 27}
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -20,6 +20,20 @@ defmodule Record do
|
||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||
records while `extract/2` can be used to extract records from Erlang
|
||||
files.
|
||||
|
||||
## Types
|
||||
|
||||
Types can be defined for tuples with the `record/2` macro (only available
|
||||
in typespecs). Like with the generated record macros it will expand to
|
||||
a tuple.
|
||||
|
||||
defmodule MyModule do
|
||||
require Record
|
||||
Record.defrecord :user name: "john", age: 25
|
||||
|
||||
@type user :: record(:user, name: String.t, age: integer)
|
||||
# expands to: `@type user :: {:user, String.t, integer}`
|
||||
end
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -49,12 +63,12 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record, User)
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.is_record(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro record?(data, kind) do
|
||||
defmacro is_record(data, kind) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -77,15 +91,15 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record)
|
||||
iex> record = {User, "john", 27}
|
||||
iex> Record.is_record(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
iex> Record.record?(tuple)
|
||||
iex> Record.is_record(tuple)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
defmacro is_record(data) do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
@@ -111,26 +125,31 @@ defmodule Record do
|
||||
## Examples
|
||||
|
||||
defmodule User do
|
||||
Record.defrecord :user, [name: "José", age: "25"]
|
||||
require Record
|
||||
Record.defrecord :user, [name: "meg", age: "25"]
|
||||
end
|
||||
|
||||
In the example above, a set of macros named `user` but with different
|
||||
arities will be defined to manipulate the underlying record:
|
||||
|
||||
# To create records
|
||||
user() #=> {:user, "José", 25}
|
||||
user(age: 26) #=> {:user, "José", 26}
|
||||
record = user() #=> {:user, "meg", 25}
|
||||
record = user(age: 26) #=> {:user, "meg", 26}
|
||||
|
||||
# To get a field from the record
|
||||
user(record, :name) #=> "José"
|
||||
user(record, :name) #=> "meg"
|
||||
|
||||
# To update the record
|
||||
user(record, age: 26) #=> {:user, "José", 26}
|
||||
user(record, age: 26) #=> {:user, "meg", 26}
|
||||
|
||||
# Convert a record to a keyword list
|
||||
user(record) #=> [name: "meg", age: 26]
|
||||
|
||||
By default, Elixir uses the record name as the first element of
|
||||
the tuple (the tag). But it can be changed to something else:
|
||||
|
||||
defmodule User do
|
||||
require Record
|
||||
Record.defrecord :user, User, name: nil
|
||||
end
|
||||
|
||||
@@ -141,7 +160,7 @@ defmodule Record do
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.__fields__(:defrecord, kv)
|
||||
fields = Record.__fields__(:defrecord, kv)
|
||||
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -159,7 +178,7 @@ defmodule Record do
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.__fields__(:defrecordp, kv)
|
||||
fields = Record.__fields__(:defrecordp, kv)
|
||||
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -175,9 +194,19 @@ defmodule Record do
|
||||
@doc false
|
||||
def __fields__(type, fields) do
|
||||
:lists.map(fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
{key, val} when is_atom(key) ->
|
||||
try do
|
||||
Macro.escape(val)
|
||||
rescue
|
||||
e in [ArgumentError] ->
|
||||
raise ArgumentError, "invalid value for record field #{key}, " <> Exception.message(e)
|
||||
else
|
||||
val -> {key, val}
|
||||
end
|
||||
key when is_atom(key) ->
|
||||
{key, nil}
|
||||
other ->
|
||||
raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
end
|
||||
|
||||
@@ -190,8 +219,15 @@ defmodule Record do
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
case Macro.expand(args, caller) do
|
||||
{:{}, _, [^atom|list]} when length(list) == length(fields) ->
|
||||
record = List.to_tuple([atom|list])
|
||||
Macro.escape(Record.__keyword__(atom, fields, record))
|
||||
{^atom, arg} when length(fields) == 1 ->
|
||||
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
|
||||
_ ->
|
||||
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -279,4 +315,21 @@ defmodule Record do
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
# Returns a keyword list of the record
|
||||
@doc false
|
||||
def __keyword__(atom, fields, record) do
|
||||
if is_record(record, atom) do
|
||||
[_tag|values] = Tuple.to_list(record)
|
||||
join_keyword(fields, values, [])
|
||||
else
|
||||
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
defp join_keyword([{field, _default}|fields], [value|values], acc),
|
||||
do: join_keyword(fields, values, [{field, value}| acc])
|
||||
defp join_keyword([], [], acc),
|
||||
do: :lists.reverse(acc)
|
||||
end
|
||||
|
||||
@@ -45,10 +45,11 @@ defmodule Record.Extractor do
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
# includes record and other preprocessor modules. This is done
|
||||
# by using Erlang's epp_dodger.
|
||||
# includes record but with macros and other attributes expanded,
|
||||
# such as `-include(...)` and `-include_lib(...)`. This is done
|
||||
# by using Erlang's epp.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
case :epp.parse_file(file, []) do
|
||||
{:ok, form} ->
|
||||
form
|
||||
other ->
|
||||
@@ -80,11 +81,11 @@ defmodule Record.Extractor do
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ {:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ cons ]} ]} ]
|
||||
[{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [cons]}]}]
|
||||
|
||||
{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
|
||||
{:clause, 0, [], [], [record_ast]}]} = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
|
||||
+92
-38
@@ -22,8 +22,9 @@ defmodule Regex do
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
|
||||
valid unicode strings to be given on match
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
|
||||
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
|
||||
It expects valid unicode strings to be given on match
|
||||
|
||||
* `caseless` (i) - add case insensitivity
|
||||
|
||||
@@ -75,7 +76,9 @@ defmodule Regex do
|
||||
|
||||
"""
|
||||
|
||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
||||
defstruct re_pattern: nil, source: "", opts: ""
|
||||
|
||||
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||
|
||||
defmodule CompileError do
|
||||
defexception message: "regex could not be compiled"
|
||||
@@ -104,7 +107,7 @@ defmodule Regex do
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
case translate_options(options, []) do
|
||||
{:error, rest} ->
|
||||
{:error, {:invalid_option, rest}}
|
||||
|
||||
@@ -130,6 +133,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: t
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{:ok, regex} -> regex
|
||||
@@ -149,6 +153,7 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, String.t) :: boolean
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
@@ -165,6 +170,8 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec regex?(t) :: true
|
||||
@spec regex?(any) :: false
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@@ -190,6 +197,7 @@ defmodule Regex do
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
@spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}]
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -220,6 +228,7 @@ defmodule Regex do
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec named_captures(t, String.t, [term]) :: map | nil
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
@@ -230,6 +239,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
@spec re_pattern(t) :: term
|
||||
def re_pattern(%Regex{re_pattern: compiled}) do
|
||||
compiled
|
||||
end
|
||||
@@ -243,6 +253,7 @@ defmodule Regex do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@spec source(t) :: String.t
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
@@ -256,6 +267,7 @@ defmodule Regex do
|
||||
"m"
|
||||
|
||||
"""
|
||||
@spec opts(t) :: String.t
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
@@ -269,6 +281,7 @@ defmodule Regex do
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
@spec names(t) :: [String.t]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
@@ -298,6 +311,7 @@ defmodule Regex do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec scan(t, String.t, [term]) :: [[String.t]]
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
@@ -318,12 +332,17 @@ defmodule Regex do
|
||||
## Options
|
||||
|
||||
* `:parts` - when specified, splits the string into the given number of
|
||||
parts. If not specified, `:parts` is defaulted to `:infinity`, which will
|
||||
parts. If not specified, `:parts` defaults to `:infinity`, which will
|
||||
split the string into the maximum number of parts possible based on the
|
||||
given pattern.
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result.
|
||||
|
||||
* `:on` - specifies which captures and order to split the string
|
||||
on. Check the moduledoc for `Regex` to see the possible capture
|
||||
values. Defaults to `:first` which means captures inside the
|
||||
Regex does not affect the split result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c")
|
||||
@@ -338,27 +357,61 @@ defmodule Regex do
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
iex> Regex.split(~r/a(?<second>b)c/, "abc")
|
||||
["", ""]
|
||||
|
||||
iex> Regex.split(~r/a(?<second>b)c/, "abc", on: [:second])
|
||||
["a", "c"]
|
||||
|
||||
"""
|
||||
|
||||
@spec split(t, String.t, [term]) :: [String.t]
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
opts = [return: :binary, parts: zero_to_infinity(parts)]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
def split(%Regex{}, "", _opts), do: [""]
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
|
||||
on = Keyword.get(opts, :on, :first)
|
||||
case :re.run(string, compiled, [:global, capture: on]) do
|
||||
{:match, matches} ->
|
||||
do_split(matches, string, 0,
|
||||
parts_to_index(Keyword.get(opts, :parts, :infinity)),
|
||||
Keyword.get(opts, :trim, false))
|
||||
:match ->
|
||||
[string]
|
||||
:nomatch ->
|
||||
[string]
|
||||
end
|
||||
end
|
||||
|
||||
defp zero_to_infinity(0), do: :infinity
|
||||
defp zero_to_infinity(n), do: n
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp do_split(_, string, offset, _counter, true) when byte_size(string) <= offset,
|
||||
do: []
|
||||
|
||||
defp do_split(_, string, offset, 1, _trim),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([], string, offset, _counter, _trim),
|
||||
do: [binary_part(string, offset, byte_size(string) - offset)]
|
||||
|
||||
defp do_split([[{pos, _}|h]|t], string, offset, counter, trim) when pos - offset < 0,
|
||||
do: do_split([h|t], string, offset, counter, trim)
|
||||
|
||||
defp do_split([[]|t], string, offset, counter, trim),
|
||||
do: do_split(t, string, offset, counter, trim)
|
||||
|
||||
defp do_split([[{pos, length}|h]|t], string, offset, counter, trim) do
|
||||
new_offset = pos + length
|
||||
keep = pos - offset
|
||||
|
||||
if keep == 0 and (length == 0 or trim) do
|
||||
do_split([h|t], string, new_offset, counter, trim)
|
||||
else
|
||||
<<_::binary-size(offset), part::binary-size(keep), _::binary>> = string
|
||||
[part|do_split([h|t], string, new_offset, counter - 1, trim)]
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
@@ -366,8 +419,8 @@ defmodule Regex do
|
||||
|
||||
The replacement can be either a string or a function. The string
|
||||
is used as a replacement for every match and it allows specific
|
||||
captures to be accessed via `\N`, where `N` is the capture. In
|
||||
case `\0` is used, the whole match is inserted.
|
||||
captures to be accessed via `\N` or `\g{N}`, where `N` is the
|
||||
capture. In case `\0` is used, the whole match is inserted.
|
||||
|
||||
When the replacement is a function, the function may have arity
|
||||
N where each argument maps to a capture, with the first argument
|
||||
@@ -397,6 +450,7 @@ defmodule Regex do
|
||||
"[b][d]"
|
||||
|
||||
"""
|
||||
@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
|
||||
@@ -425,6 +479,11 @@ 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)
|
||||
[List.to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
@@ -471,12 +530,12 @@ defmodule Regex do
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||
length = mpos - pos
|
||||
<<untouched :: [size(length), binary], 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, [[{mpos, length} | _] = head | tail]) when mpos == pos 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
|
||||
@@ -496,7 +555,7 @@ defmodule Regex do
|
||||
cond do
|
||||
is_binary(part) ->
|
||||
part
|
||||
part > tuple_size(indexes) ->
|
||||
part >= tuple_size(indexes) ->
|
||||
""
|
||||
true ->
|
||||
get_index(string, elem(indexes, part))
|
||||
@@ -509,7 +568,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
|
||||
|
||||
@@ -559,18 +618,13 @@ defmodule Regex do
|
||||
|
||||
# Private Helpers
|
||||
|
||||
defp translate_options(<<?g, t :: binary>>) do
|
||||
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: {:error, rest}
|
||||
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(<<?r, 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(<<>>, acc), do: acc
|
||||
defp translate_options(rest, _acc), do: {:error, rest}
|
||||
end
|
||||
|
||||
@@ -198,7 +198,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
target1.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
|
||||
|
||||
+180
-59
@@ -89,12 +89,14 @@ defmodule Stream do
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
@type acc :: any
|
||||
@type acc :: any
|
||||
@type element :: any
|
||||
@type index :: non_neg_integer
|
||||
@type index :: non_neg_integer
|
||||
@type default :: any
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
# Require Stream.Reducers and its callbacks
|
||||
require Stream.Reducers, as: R
|
||||
@@ -154,18 +156,22 @@ defmodule Stream do
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
fn(f1) -> &do_chunk(&1, n, pad, f1) end
|
||||
if is_nil(pad) do
|
||||
lazy enum, {[], 0}, fn(f1) -> R.chunk(n, step, limit, f1) end
|
||||
else
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
&do_chunk(&1, n, pad, &2)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
end
|
||||
defp do_chunk(acc(_, {_, 0}, _) = acc, _, _, _) do
|
||||
{:cont, acc}
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t), n, pad, f1) do
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -184,7 +190,7 @@ defmodule Stream do
|
||||
def chunk_by(enum, fun) do
|
||||
lazy enum, nil,
|
||||
fn(f1) -> R.chunk_by(fun, f1) end,
|
||||
fn(f1) -> &do_chunk_by(&1, f1) end
|
||||
&do_chunk_by(&1, &2)
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
|
||||
@@ -336,6 +342,25 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a value every `n` milliseconds.
|
||||
|
||||
The values emitted are an increasing counter starting at `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.interval(10) |> Enum.take(10)
|
||||
[0,1,2,3,4,5,6,7,8,9]
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t
|
||||
def interval(n) do
|
||||
unfold 0, fn (count) ->
|
||||
:timer.sleep(n)
|
||||
{count, count + 1}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
@@ -427,7 +452,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec run(Enumerable.t) :: :ok
|
||||
def run(stream) do
|
||||
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||
_ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -493,11 +518,11 @@ defmodule Stream do
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
def take(enum, n) when n > 0 and is_integer(n) do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
end
|
||||
|
||||
def take(enum, n) when n < 0 do
|
||||
def take(enum, n) when n < 0 and is_integer(n) do
|
||||
&do_take(enum, abs(n), &1, &2)
|
||||
end
|
||||
|
||||
@@ -561,6 +586,22 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that emits a single value after `n` milliseconds.
|
||||
|
||||
The value emitted is `0`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.timer(10) |> Enum.to_list
|
||||
[0]
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t
|
||||
def timer(n) do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -575,7 +616,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
`Stream.transform/3` is a useful as it can be used as basis to implement
|
||||
`Stream.transform/3` is useful as it can be used as the basis to implement
|
||||
many of the functions defined in this module. For example, we can implement
|
||||
`Stream.take(enum, n)` as follows:
|
||||
|
||||
@@ -616,12 +657,14 @@ defmodule Stream do
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
{list, user_acc} when is_list(list) ->
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
{:halt, _user_acc} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, elem(inner_acc, 1)}
|
||||
{other, user_acc} ->
|
||||
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
|
||||
do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
|
||||
&Enumerable.reduce(other, &1, inner))
|
||||
end
|
||||
{reason, _} ->
|
||||
{reason, elem(inner_acc, 1)}
|
||||
@@ -633,8 +676,9 @@ defmodule Stream do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
@@ -646,29 +690,33 @@ defmodule Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
|
||||
defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
reduce.({op, [:outer|inner_acc]})
|
||||
catch
|
||||
{:stream_transform, h} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, h}
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{_, acc} ->
|
||||
# Only take into account outer halts when the op is not halt itself.
|
||||
# Otherwise, we were the ones wishing to halt, so we should just stop.
|
||||
{:halted, [:outer|acc]} when op != :halt ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
{:halted, [_|acc]} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, acc}
|
||||
{:done, [_|acc]} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:suspended, [_|acc], c} ->
|
||||
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_each(x, acc, f) do
|
||||
defp do_transform_each(x, [:outer|acc], f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, h} -> throw({:stream_transform, h})
|
||||
{_, _} = o -> o
|
||||
{:halt, res} -> {:halt, [:inner|res]}
|
||||
{op, res} -> {op, [:outer|res]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -840,6 +888,15 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable)
|
||||
|
||||
def cycle(enumerable) when is_list(enumerable) do
|
||||
unfold {enumerable, enumerable}, fn
|
||||
{source, [h | t]} -> {h, {source, t}}
|
||||
{source = [h | t], []} -> {h, {source, t}}
|
||||
end
|
||||
end
|
||||
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
@@ -927,14 +984,15 @@ defmodule Stream do
|
||||
@doc """
|
||||
Emits a sequence of values for the given resource.
|
||||
|
||||
Similar to `unfold/2` but the initial value is computed lazily via
|
||||
`start_fun` and executes an `after_fun` at the end of enumeration
|
||||
(both in cases of success and failure).
|
||||
Similar to `transform/2` but the initial accumulated value is
|
||||
computed lazily via `start_fun` and executes an `after_fun` at
|
||||
the end of enumeration (both in cases of success and failure).
|
||||
|
||||
Successive values are generated by calling `next_fun` with the
|
||||
previous accumulator (the initial value being the result returned
|
||||
by `start_fun`) and it must return a tuple with the current and
|
||||
next accumulator. The enumeration finishes if it returns `nil`.
|
||||
by `start_fun`) and it must return a tuple containing a list
|
||||
of items to be emitted and the next accumulator. The enumeration
|
||||
finishes if it returns `{:halt, acc}`.
|
||||
|
||||
As the name says, this function is useful to stream values from
|
||||
resources.
|
||||
@@ -944,8 +1002,8 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
data when is_binary(data) -> {[data], file}
|
||||
_ -> {:halt, file}
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
@@ -967,9 +1025,11 @@ defmodule Stream do
|
||||
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
# Optimize the most common cases
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
{[], next_acc} -> {:opt, {:cont, acc}, next_acc}
|
||||
{[v], next_acc} -> {:opt, fun.(v, acc), next_acc}
|
||||
{_, _} = other -> other
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -977,11 +1037,62 @@ defmodule Stream do
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
{:opt, acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
{:halt, next_acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{list, next_acc} when is_list(list) ->
|
||||
do_list_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.List.reduce(list, &1, fun))
|
||||
{enum, next_acc} ->
|
||||
inner = &do_resource_each(&1, &2, fun)
|
||||
do_enum_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun,
|
||||
&Enumerable.reduce(enum, &1, inner))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_list_resource(next_acc, next_fun, acc, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:done, acc} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, acc} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_enum_resource(next_acc, next_fun, {op, acc}, fun, after_fun, reduce) do
|
||||
try do
|
||||
reduce.({op, [:outer|acc]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:halted, [:outer|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:halted, [:inner|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:halt, acc}, fun, after_fun)
|
||||
{:done, [_|acc]} ->
|
||||
do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun)
|
||||
{:suspended, [_|acc], c} ->
|
||||
{:suspended, acc, &do_enum_resource(next_acc, next_fun, &1, fun, after_fun, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_resource_each(x, [:outer|acc], f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, res} -> {:halt, [:inner|res]}
|
||||
{op, res} -> {op, [:outer|res]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1022,12 +1133,12 @@ defmodule Stream do
|
||||
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(%Stream{funs: funs} = lazy, fun),
|
||||
defp lazy(%Stream{done: nil, funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: %Stream{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
|
||||
@@ -1066,19 +1177,29 @@ defimpl Enumerable, for: Stream do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
{:halted, accs} ->
|
||||
do_done {:halted, accs}, done
|
||||
{:done, accs} ->
|
||||
do_done {:done, accs}, done
|
||||
end
|
||||
end
|
||||
|
||||
defp do_done({reason, [acc|_]}, nil), do: {reason, acc}
|
||||
defp do_done({reason, [acc|t]}, {done, fun}) do
|
||||
[h|_] = Enum.reverse(t)
|
||||
case done.([acc, h], fun) do
|
||||
{:cont, [acc|_]} -> {reason, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Stream do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(%{enum: enum, funs: funs}, opts) do
|
||||
inner = [enum: enum, funs: Enum.reverse(funs)]
|
||||
concat ["#Stream<", to_doc(inner, opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,10 +132,16 @@ defmodule Stream.Reducers do
|
||||
defmacro take(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{:halt, orig}
|
||||
case n do
|
||||
0 ->
|
||||
{:halt, orig}
|
||||
1 ->
|
||||
case cont_with_acc(unquote(f), entry, h, n-1, t) do
|
||||
{:cont, acc} -> {:halt, acc}
|
||||
reason -> reason
|
||||
end
|
||||
_ ->
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+210
-114
@@ -4,28 +4,12 @@ defmodule String do
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.3.0. For example,
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
In addition to this module, Elixir provides more low-level
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0.As per the standard, a codepoint is
|
||||
an Unicode Character, which may be represented by one or more
|
||||
bytes. For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
@@ -66,34 +50,110 @@ defmodule String do
|
||||
This 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
|
||||
the whole string considering the proper Unicode codepoints.
|
||||
|
||||
For example, `String.length/1` is going to take longer as
|
||||
the input grows. On the other hand, `byte_size/1` always runs
|
||||
in constant time (i.e. regardless of the input size).
|
||||
|
||||
This means often there are performance costs in using the
|
||||
functions in this module, compared to the more low-level
|
||||
operations that work directly with binaries:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
There are many situations where using the `String` module can
|
||||
be avoided in favor of binary functions or pattern matching.
|
||||
For example, imagine you have a string `prefix` and you want to
|
||||
remove this prefix from another string named `full`.
|
||||
|
||||
One may be tempted to write:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base, String.length(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Although the function above works, it performs poorly. To
|
||||
calculate the length of the string, we need to traverse it
|
||||
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:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = String.length(prefix)
|
||||
...> String.slice(full, base..-1)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
While this is much better (we don't traverse `full` twice),
|
||||
it could still be improved. In this case, since we want to
|
||||
extract a substring from a string, we can use `byte_size/1`
|
||||
and `binary_part/3` as there is no chance we will slice in
|
||||
the middle of a codepoint made of more than one byte:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> binary_part(full, base, byte_size(full) - base)
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
Or simply used pattern matching:
|
||||
|
||||
iex> take_prefix = fn full, prefix ->
|
||||
...> base = byte_size(prefix)
|
||||
...> <<_ :: binary-size(base), rest :: binary>> = full
|
||||
...> rest
|
||||
...> end
|
||||
...> take_prefix.("Mr. John", "Mr. ")
|
||||
"John"
|
||||
|
||||
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.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
codepoint. One may use the `?` special macro:
|
||||
codepoint. One may use the `?` construct:
|
||||
|
||||
iex> ?j
|
||||
106
|
||||
iex> ?o
|
||||
111
|
||||
|
||||
iex> ?é
|
||||
233
|
||||
iex> ?á
|
||||
225
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
iex> << eacute :: utf8 >> = "á"
|
||||
iex> eacute
|
||||
233
|
||||
225
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
a string by their hexadecimal code:
|
||||
|
||||
"jos\x{0065}\x{0301}" #=>
|
||||
"josé"
|
||||
"ol\x{0061}\x{0301}" #=>
|
||||
"olá"
|
||||
|
||||
## Self-synchronization
|
||||
|
||||
@@ -129,7 +189,7 @@ defmodule String do
|
||||
@spec printable?(t) :: boolean
|
||||
|
||||
def printable?(<< h :: utf8, t :: binary >>)
|
||||
when h in ?\040..?\176
|
||||
when h in 0x20..0x7E
|
||||
when h in 0xA0..0xD7FF
|
||||
when h in 0xE000..0xFFFD
|
||||
when h in 0x10000..0x10FFFF do
|
||||
@@ -227,40 +287,53 @@ defmodule String do
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options \\ [])
|
||||
def split(string, pattern, options \\ [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
def split(string, "", options) do
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
split_codepoints(string, parts_to_index(parts), Keyword.get(options, :trim, false))
|
||||
end
|
||||
|
||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
def split(string, pattern, options) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
Regex.split(pattern, string, options)
|
||||
else
|
||||
splits =
|
||||
case Keyword.get(options, :parts, :infinity) do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
_ ->
|
||||
:binary.split(binary, pattern, [:global])
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
trim = Keyword.get(options, :trim, false)
|
||||
if parts == :infinity and trim == false do
|
||||
:binary.split(string, pattern, [:global])
|
||||
else
|
||||
splits
|
||||
split_parts(string, pattern, parts_to_index(parts), trim)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp split_parts("", _pattern, _num), do: [""]
|
||||
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
case :binary.split(binary, pattern) do
|
||||
[head] -> Enum.reverse([head|parts])
|
||||
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
|
||||
defp parts_to_index(:infinity), do: 0
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp split_codepoints(binary, 1, _trim), do: [binary]
|
||||
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
|
||||
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
|
||||
defp split_codepoints(<<h, t :: binary>>, count, trim),
|
||||
do: [<<h>>|split_codepoints(t, count - 1, trim)]
|
||||
defp split_codepoints(<<>>, _, true), do: []
|
||||
defp split_codepoints(<<>>, _, false), do: [""]
|
||||
|
||||
defp split_parts("", _pattern, _num, true), do: []
|
||||
defp split_parts("", _pattern, _num, _trim), do: [""]
|
||||
defp split_parts(string, _pattern, 1, _trim), do: [string]
|
||||
defp split_parts(string, pattern, num, trim) do
|
||||
case :binary.split(string, pattern) do
|
||||
[""] when trim ->
|
||||
[]
|
||||
[head] ->
|
||||
[head]
|
||||
[head, tail] ->
|
||||
if trim and head == "" do
|
||||
split_parts(tail, pattern, num, trim)
|
||||
else
|
||||
[head|split_parts(tail, pattern, num-1, trim)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -322,8 +395,8 @@ defmodule String do
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
iex> String.upcase("olá")
|
||||
"OLÁ"
|
||||
|
||||
"""
|
||||
@spec upcase(t) :: t
|
||||
@@ -340,8 +413,8 @@ defmodule String do
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
iex> String.downcase("OLÁ")
|
||||
"olá"
|
||||
|
||||
"""
|
||||
@spec downcase(t) :: t
|
||||
@@ -364,8 +437,8 @@ defmodule String do
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
iex> String.capitalize("olá")
|
||||
"Olá"
|
||||
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
@@ -405,7 +478,7 @@ defmodule String do
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) when char in 0..127 do
|
||||
if :binary.last(string) == char do
|
||||
do_rstrip(string, "", char)
|
||||
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
|
||||
else
|
||||
string
|
||||
end
|
||||
@@ -577,13 +650,14 @@ defmodule String do
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
The pattern can also be a regex. In those cases, one can give `\N` or
|
||||
`\g{N}` in the `replacement` string to access a specific capture in the
|
||||
regex:
|
||||
|
||||
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
|
||||
Notice we had to escape the escape character `\`. By giving `&`,
|
||||
Notice we had to escape the escape character `\`. By giving `\0`,
|
||||
one can inject the whole matched pattern in the replacement string.
|
||||
|
||||
When strings are used as a pattern, a developer can also use the
|
||||
@@ -599,8 +673,8 @@ defmodule String do
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
@spec replace(t, t | Regex.t, t) :: t
|
||||
@spec replace(t, t | Regex.t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
@@ -672,8 +746,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("olá")
|
||||
["o", "l", "á"]
|
||||
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
@@ -699,8 +773,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.next_codepoint("josé")
|
||||
{"j", "osé"}
|
||||
iex> String.next_codepoint("olá")
|
||||
{"o", "lá"}
|
||||
|
||||
"""
|
||||
@compile {:inline, next_codepoint: 1}
|
||||
@@ -786,10 +860,10 @@ defmodule String do
|
||||
## Examples
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
|
||||
["abc\000"]
|
||||
["abc\0"]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
|
||||
["abc\000", <<0x0ffff::utf8>>]
|
||||
["abc\0", <<0x0ffff::utf8>>]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
|
||||
["abc", <<0, 0x0ffff::utf8>>]
|
||||
@@ -849,8 +923,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.next_grapheme("josé")
|
||||
{"j", "osé"}
|
||||
iex> String.next_grapheme("olá")
|
||||
{"o", "lá"}
|
||||
|
||||
"""
|
||||
@compile {:inline, next_grapheme: 1}
|
||||
@@ -974,8 +1048,13 @@ defmodule String do
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
a length given by the second.
|
||||
|
||||
If the offset is greater than string length, then it returns `""`.
|
||||
|
||||
Remember this function works with unicode codepoints and consider
|
||||
the slices to represent codepoints offsets. If you want to split
|
||||
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.slice("elixir", 1, 3)
|
||||
@@ -1010,17 +1089,29 @@ defmodule String do
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
|
||||
case do_count_bytes(next_grapheme(string), start, 0) do
|
||||
{nil, _} -> ""
|
||||
{next, start_bytes} ->
|
||||
{_, len_bytes} = do_count_bytes(next, len, 0)
|
||||
binary_part(string, start_bytes, len_bytes)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start < 0 and len >= 0 do
|
||||
real_start_pos = length(string) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
start = length(string) + start
|
||||
case start >= 0 do
|
||||
true -> slice(string, start, len)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_count_bytes(next, 0, acc), do: {next, acc}
|
||||
defp do_count_bytes(nil, _, acc), do: {nil, acc}
|
||||
|
||||
defp do_count_bytes({char, rest}, counter, acc) do
|
||||
do_count_bytes(next_grapheme(rest), counter - 1, acc + byte_size(char))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a substring from the offset given by the start of the
|
||||
range to the offset given by the end of the range.
|
||||
@@ -1028,6 +1119,14 @@ defmodule String do
|
||||
If the start of the range is not a valid offset for the given
|
||||
string or if the range is in reverse order, returns `""`.
|
||||
|
||||
If the start or end of the range are negative, the whole string
|
||||
is traversed first in order to convert the negative indexes into
|
||||
positive ones.
|
||||
|
||||
Remember this function works with unicode codepoints and consider
|
||||
the slices to represent codepoints offsets. If you want to split
|
||||
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.slice("elixir", 1..3)
|
||||
@@ -1060,54 +1159,51 @@ defmodule String do
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
def slice("", _.._), do: ""
|
||||
|
||||
def slice(string, first..-1) when first >= 0 do
|
||||
case do_count_bytes(next_grapheme(string), first, 0) do
|
||||
{nil, _} -> ""
|
||||
{_, start_bytes} -> binary_part(string, start_bytes, byte_size(string) - start_bytes)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, first..last) do
|
||||
total = length(string)
|
||||
|
||||
if first < 0 do
|
||||
first = total + first
|
||||
end
|
||||
|
||||
if last < 0 do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
if last >= first do
|
||||
slice(string, first, last - first + 1)
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
""
|
||||
def slice(string, first..last) do
|
||||
{bytes, length} = do_acc_bytes(next_grapheme(string), [], 0)
|
||||
|
||||
if first < 0, do: first = length + first
|
||||
if last < 0, do: last = length + last
|
||||
|
||||
if first < 0 or first > last or first > length do
|
||||
""
|
||||
else
|
||||
last = min(last + 1, length)
|
||||
bytes = Enum.drop(bytes, length - last)
|
||||
first = last - first
|
||||
{length_bytes, start_bytes} = Enum.split(bytes, first)
|
||||
binary_part(string, Enum.sum(start_bytes), Enum.sum(length_bytes))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
|
||||
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc)
|
||||
defp do_acc_bytes({char, rest}, bytes, length) do
|
||||
do_acc_bytes(next_grapheme(rest), [byte_size(char)|bytes], length + 1)
|
||||
end
|
||||
|
||||
defp do_slice({char, rest}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos < last_pos do
|
||||
do_slice(next_grapheme(rest), start_pos, last_pos, current_pos + 1, acc <> char)
|
||||
end
|
||||
|
||||
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
acc
|
||||
defp do_acc_bytes(nil, bytes, length) do
|
||||
{bytes, length}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1328,7 +1424,7 @@ defmodule String do
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t, pos_integer) :: integer
|
||||
@spec to_integer(String.t, 2..36) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
GenServer.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,7 +52,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
GenServer.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -68,7 +68,7 @@ defmodule StringIO do
|
||||
"""
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
GenServer.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
@@ -199,7 +199,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
|
||||
|
||||
@@ -209,7 +209,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
|
||||
|
||||
@@ -20,15 +20,15 @@ defmodule Supervisor do
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
def start_link(state) do
|
||||
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
|
||||
def start_link(state, opts) do
|
||||
GenServer.start_link(__MODULE__, state, opts)
|
||||
end
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, h}, _from, t) do
|
||||
def handle_cast({:push, h}, t) do
|
||||
{:noreply, [h|t]}
|
||||
end
|
||||
end
|
||||
@@ -38,16 +38,17 @@ defmodule Supervisor do
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# We are going to supervise the Stack server which will
|
||||
# be started with a single argument [:hello]
|
||||
# We are going to supervise the Stack server which
|
||||
# will be started with a single argument [:hello]
|
||||
# and the default name of :sup_stack.
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
worker(Stack, [[:hello], [name: :sup_stack]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
Notice that when starting the GenServer, we are registering it
|
||||
with name `:sup_stack`, which allows us to call it directly and
|
||||
get what is on the stack:
|
||||
|
||||
@@ -65,7 +66,7 @@ defmodule Supervisor do
|
||||
Let's try it:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
=ERROR REPORT====
|
||||
** (exit) exited in: GenServer.call(:sup_stack, :pop, 5000)
|
||||
|
||||
Luckily, since the server is being supervised by a supervisor, the
|
||||
supervisor will automatically start a new one, with the default stack
|
||||
@@ -132,10 +133,74 @@ defmodule Supervisor do
|
||||
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
supervisor specification to contain only one child. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Simple one for one
|
||||
|
||||
The simple one for one supervisor is useful when you want to dynamically
|
||||
start and stop supervisor children. For example, imagine you want to
|
||||
dynamically create multiple stacks. We can do so by defining a simple one
|
||||
for one supervisor:
|
||||
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# This time, we don't pass any argument because
|
||||
# the argument will be given when we start the child
|
||||
children = [
|
||||
worker(Stack, [], restart: :transient)
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one)
|
||||
|
||||
There are a couple differences here:
|
||||
|
||||
* 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
|
||||
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
|
||||
simple one for one supervisor, without being restarted. You can find
|
||||
more information about restart strategies on `Supervisor.Spec`
|
||||
|
||||
With the supervisor defined, let's dynamically start stacks:
|
||||
|
||||
{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
|
||||
GenServer.call(pid, :pop) #=> :hello
|
||||
GenServer.call(pid, :pop) #=> :world
|
||||
|
||||
{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
|
||||
GenServer.call(pid, :pop) #=> :something
|
||||
GenServer.call(pid, :pop) #=> :else
|
||||
|
||||
Supervisor.count_children(sup_pid)
|
||||
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
|
||||
|
||||
## Exit reasons
|
||||
|
||||
From the example above, you may have noticed that the transient restart
|
||||
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?
|
||||
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
|
||||
|
||||
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
|
||||
logged, there is no restart on 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
|
||||
unless trapping exits
|
||||
|
||||
## Name Registration
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
@@ -268,7 +333,9 @@ defmodule Supervisor do
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
def start_child(supervisor, child_spec_or_args) do
|
||||
call(supervisor, {:start_child, child_spec_or_args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
@@ -290,7 +357,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
def terminate_child(supervisor, pid_or_child_id) do
|
||||
call(supervisor, {:terminate_child, pid_or_child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
@@ -306,7 +375,9 @@ defmodule Supervisor do
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
def delete_child(supervisor, child_id) do
|
||||
call(supervisor, {:delete_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
@@ -335,7 +406,9 @@ defmodule Supervisor do
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
def restart_child(supervisor, child_id) do
|
||||
call(supervisor, {:restart_child, child_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
@@ -361,7 +434,9 @@ defmodule Supervisor do
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
def which_children(supervisor) do
|
||||
call(supervisor, :which_children)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a map containing count values for the supervisor.
|
||||
@@ -381,9 +456,15 @@ defmodule Supervisor do
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
%{specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer}
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
call(supervisor, :count_children) |> :maps.from_list
|
||||
end
|
||||
|
||||
@compile {:inline, call: 2}
|
||||
|
||||
defp call(supervisor, req) do
|
||||
GenServer.call(supervisor, req, :infinity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -145,12 +145,12 @@ defmodule Supervisor.Spec do
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5.
|
||||
a time frame. Defaults to 3.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to 5.
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
The `:strategy` option is required and by default maximum 3 restarts
|
||||
are allowed within 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@@ -162,7 +162,7 @@ defmodule Supervisor.Spec do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxR = Keyword.get(options, :max_restarts, 3)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
|
||||
|
||||
+143
-41
@@ -72,7 +72,7 @@ defmodule System do
|
||||
"""
|
||||
@spec argv() :: [String.t]
|
||||
def argv do
|
||||
:elixir_code_server.call :argv
|
||||
:elixir_config.get(:argv)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -83,7 +83,7 @@ defmodule System do
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(args) do
|
||||
:elixir_code_server.cast({:argv, args})
|
||||
:elixir_config.put(:argv, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,11 +94,20 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> IO.chardata_to_string(base)
|
||||
{:ok, base} -> IO.chardata_to_string(fix_drive_letter(base))
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter([l, ?:, ?/ | rest] = original) when l in ?A..?Z do
|
||||
case :os.type() do
|
||||
{:win32, _} -> [l+?a-?A, ?:, ?/ | rest]
|
||||
_ -> original
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_drive_letter(original), do: original
|
||||
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
@@ -202,59 +211,32 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register a program exit handler function.
|
||||
Registers a program exit handler function.
|
||||
|
||||
Registers a function that will be invoked
|
||||
at the end of program execution. Useful for
|
||||
invoking a hook in "script" mode.
|
||||
Registers a function that will be invoked at the end of program execution.
|
||||
Useful for invoking a hook in "script" mode.
|
||||
|
||||
The function must receive the exit status code
|
||||
as an argument.
|
||||
The handler always executes in a different process from the one it was
|
||||
registered in. As a consequence, any resources managed by the calling process
|
||||
(ETS tables, open files, etc.) won't be available by the time the handler
|
||||
function is invoked.
|
||||
|
||||
The function must receive the exit status code as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast {:at_exit, fun}
|
||||
:elixir_config.update :at_exit, &[fun|&1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Execute a system command.
|
||||
|
||||
Executes `command` in a command shell of the target OS,
|
||||
captures the standard output of the command and returns
|
||||
the result as a binary.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
Otherwise a string, correctly encoded in UTF-8, is expected.
|
||||
"""
|
||||
@spec cmd(String.t) :: String.t
|
||||
@spec cmd(char_list) :: char_list
|
||||
|
||||
def cmd(command) when is_list(command) do
|
||||
:os.cmd(command)
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
List.to_string :os.cmd(String.to_char_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locate an executable on the system.
|
||||
Locates an executable on the system.
|
||||
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows. It also considers the proper executable
|
||||
extension for each OS, so for Windows it will try to
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
|
||||
If `program` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec find_executable(binary) :: binary | nil
|
||||
@spec find_executable(char_list) :: char_list | nil
|
||||
|
||||
def find_executable(program) when is_list(program) do
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
case :os.find_executable(String.to_char_list(program)) do
|
||||
false -> nil
|
||||
@@ -387,4 +369,124 @@ defmodule System do
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(String.to_char_list(status))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Executes the given `command` with `args`.
|
||||
|
||||
`command` is expected to be an executable available in PATH
|
||||
unless an absolute path is given.
|
||||
|
||||
`args` must be a list of strings which are not expanded
|
||||
in any way. For example, this means wildcard expansion will
|
||||
not happen unless `Path.wildcard/2` is used. On Windows though,
|
||||
wildcard expansion is up to the program.
|
||||
|
||||
A set of options are also supported and described below.
|
||||
|
||||
## Options
|
||||
|
||||
* `:into` - injects the result into the given collectable, defaults to `""`
|
||||
* `:cd` - the directory to run the command in
|
||||
* `:env` - an enumerable of tuples containing environment key-value as binary
|
||||
* `:arg0` - set the command arg0
|
||||
* `:stderr_to_stdout` - redirects stderr to stdout when true
|
||||
* `:parallelism` - when true, the VM will schedule port tasks to improve
|
||||
parallelism in the system. If set to false, the VM will try to perform
|
||||
commands immediately, improving latency at the expense of parallelism.
|
||||
The default can be set on system startup by passing the "+spp" argument
|
||||
to `--erl`.
|
||||
|
||||
## Error reasons
|
||||
|
||||
If invalid arguments are given, `ArgumentError` is raised by
|
||||
`System.cmd/3`. `System.cmd/3` also expects a strict set of
|
||||
options and will raise if unknown or invalid options are given.
|
||||
|
||||
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
|
||||
detailed below:
|
||||
|
||||
* `:system_limit` - all available ports in the Erlang emulator are in use
|
||||
|
||||
* `:enomem` - there was not enough memory to create the port
|
||||
|
||||
* `:eagain` - there are no more available operating system processes
|
||||
|
||||
* `:enametoolong` - the external command given was too long
|
||||
|
||||
* `:emfile` - there are no more available file descriptors
|
||||
(for the operating system process that the Erlang emulator runs in)
|
||||
|
||||
* `:enfile` - the file table is full (for the entire operating system)
|
||||
|
||||
* `:eacces` - the command does not point to an executable file
|
||||
|
||||
* `:enoent` - the command does not point to an existing file
|
||||
|
||||
## Shell commands
|
||||
|
||||
If you desire to execute a trusted command inside a shell, with pipes,
|
||||
redirecting and so on, please check
|
||||
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
|
||||
"""
|
||||
@spec cmd(binary, [binary], Keyword.t) ::
|
||||
{Collectable.t, exit_status :: non_neg_integer}
|
||||
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
|
||||
cmd = String.to_char_list(command)
|
||||
|
||||
cmd =
|
||||
if Path.type(cmd) == :absolute do
|
||||
cmd
|
||||
else
|
||||
:os.find_executable(cmd) || :erlang.error(:enoent, [command, args, opts])
|
||||
end
|
||||
|
||||
{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
|
||||
end
|
||||
|
||||
defp do_cmd(port, acc, fun) do
|
||||
receive do
|
||||
{^port, {:data, data}} ->
|
||||
do_cmd(port, fun.(acc, {:cont, data}), fun)
|
||||
{^port, {:exit_status, status}} ->
|
||||
{fun.(acc, :done), status}
|
||||
end
|
||||
end
|
||||
|
||||
defp cmd_opts([{:into, any}|t], opts, _into),
|
||||
do: cmd_opts(t, opts, any)
|
||||
|
||||
defp cmd_opts([{:cd, bin}|t], opts, into) when is_binary(bin),
|
||||
do: cmd_opts(t, [{:cd, bin}|opts], into)
|
||||
|
||||
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
|
||||
do: cmd_opts(t, [{:arg0, bin}|opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, true}|t], opts, into),
|
||||
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
|
||||
|
||||
defp cmd_opts([{:stderr_to_stdout, false}|t], opts, into),
|
||||
do: cmd_opts(t, opts, into)
|
||||
|
||||
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
|
||||
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
|
||||
|
||||
defp cmd_opts([{:env, enum}|t], opts, into),
|
||||
do: cmd_opts(t, [{:env, validate_env(enum)}|opts], into)
|
||||
|
||||
defp cmd_opts([{key, val}|_], _opts, _into),
|
||||
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
|
||||
|
||||
defp cmd_opts([], opts, into),
|
||||
do: {into, opts}
|
||||
|
||||
defp validate_env(enum) do
|
||||
Enum.map enum, fn
|
||||
{k, v} ->
|
||||
{String.to_char_list(k), String.to_char_list(v)}
|
||||
other ->
|
||||
raise ArgumentError, "invalid environment key-value #{inspect other}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+39
-5
@@ -16,7 +16,7 @@ defmodule Task do
|
||||
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
|
||||
Besides `async/1` and `await/2`, tasks can also be
|
||||
started as part of supervision trees and dynamically spawned
|
||||
in remote nodes. We will explore all three scenarios next.
|
||||
|
||||
@@ -58,7 +58,7 @@ defmodule Task do
|
||||
that dynamically supervise tasks:
|
||||
|
||||
{:ok, pid} = Task.Supervisor.start_link()
|
||||
Task.Supervisor.async(pid, fn -> do_work() end)
|
||||
Task.Supervisor.async(pid, MyMod, :my_fun, [arg1, arg2, arg3])
|
||||
|
||||
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
|
||||
long as the supervisor is registered locally or globally:
|
||||
@@ -67,7 +67,7 @@ defmodule Task do
|
||||
Task.Supervisor.start_link(name: :tasks_sup)
|
||||
|
||||
# In the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, MyMod, :my_fun, [arg1, arg2, arg3])
|
||||
|
||||
`Task.Supervisor` is more often started in your supervision tree as:
|
||||
|
||||
@@ -77,7 +77,15 @@ defmodule Task do
|
||||
supervisor(Task.Supervisor, [[name: :tasks_sup]])
|
||||
]
|
||||
|
||||
Check `Task.Supervisor` for other operations supported by the Task supervisor.
|
||||
Note that, when working with distributed tasks, one should use the `async/3` API,
|
||||
that expects explicit module, function and arguments, instead of `async/1` that
|
||||
works with anonymous functions. That's because the anonymous function API expects
|
||||
the same module version to exist on all involved nodes. Check the `Agent` module
|
||||
documentation for more information on distributed processes, as the limitations
|
||||
described in the agents documentation apply to the whole ecosystem.
|
||||
|
||||
Finally, check `Task.Supervisor` for other operations supported by the Task
|
||||
supervisor.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -93,6 +101,8 @@ defmodule Task do
|
||||
"""
|
||||
defstruct pid: nil, ref: nil
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@doc """
|
||||
Starts a task as part of a supervision tree.
|
||||
"""
|
||||
@@ -109,6 +119,30 @@ defmodule Task do
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(fun) :: {:ok, pid}
|
||||
def start(fun) do
|
||||
start(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
be linked to the current process.
|
||||
"""
|
||||
@spec start(module, atom, [term]) :: {:ok, pid}
|
||||
def start(mod, fun, args) do
|
||||
Task.Supervised.start(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
@@ -150,7 +184,7 @@ defmodule Task do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
Awaits a task reply.
|
||||
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start(info, fun) do
|
||||
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
@@ -89,12 +93,12 @@ defmodule Task.Supervised do
|
||||
{fun, args} = get_running(mfa)
|
||||
|
||||
:error_logger.format(
|
||||
"** Task ~p terminating~n" <>
|
||||
"** Started from ~p~n" <>
|
||||
"** When function == ~p~n" <>
|
||||
"** arguments == ~p~n" <>
|
||||
"** Reason for termination == ~n" <>
|
||||
"** ~p~n", [self, get_from(info), fun, args, reason])
|
||||
'** Task ~p terminating~n' ++
|
||||
'** Started from ~p~n' ++
|
||||
'** When function == ~p~n' ++
|
||||
'** arguments == ~p~n' ++
|
||||
'** Reason for termination == ~n' ++
|
||||
'** ~p~n', [self, get_from(info), fun, args, reason])
|
||||
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
@@ -25,14 +25,23 @@ defmodule Task.Supervisor do
|
||||
described under the `Name Registration` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `: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
|
||||
a crash;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds;
|
||||
|
||||
* `:max_restarts` and `:max_seconds` - as specified in `Supervisor.Spec.supervise/2`;
|
||||
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
import Supervisor.Spec
|
||||
{restart, opts} = Keyword.pop(opts, :restart, :temporary)
|
||||
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
|
||||
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
|
||||
children = [worker(Task.Supervised, [], restart: restart, shutdown: shutdown)]
|
||||
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
|
||||
end
|
||||
|
||||
|
||||
+71
-42
@@ -7,20 +7,21 @@ defmodule URI do
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports %{
|
||||
"ftp" => 21,
|
||||
"http" => 80,
|
||||
"https" => 443,
|
||||
"ldap" => 389,
|
||||
"sftp" => 22,
|
||||
"tftp" => 69,
|
||||
}
|
||||
@schemes [
|
||||
"ftp",
|
||||
"http",
|
||||
"https",
|
||||
"ldap",
|
||||
"sftp",
|
||||
"tftp",
|
||||
]
|
||||
|
||||
Enum.each @ports, fn {scheme, port} ->
|
||||
Enum.each @schemes, fn scheme ->
|
||||
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
|
||||
def default_port(unquote(scheme)), do: unquote(port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -45,8 +46,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Map.get(dict, scheme)
|
||||
:elixir_config.get({:uri, scheme})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -57,8 +57,7 @@ defmodule URI do
|
||||
new URIs.
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
|
||||
:elixir_config.put({:uri, scheme}, port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -66,7 +65,7 @@ defmodule URI do
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`.
|
||||
keys and values are URL encoded as per `encode/2`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
@@ -96,7 +95,10 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
|
||||
case do_decode_query(q) do
|
||||
nil -> dict
|
||||
{{k, v}, q} -> decode_query(q, Dict.put(dict, k, v))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -105,19 +107,19 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map(&(&1))
|
||||
[{"foo", "1"}, {"bar", "2"}]
|
||||
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
Stream.unfold(q, &do_decoder/1)
|
||||
Stream.unfold(q, &do_decode_query/1)
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
defp do_decode_query("") do
|
||||
nil
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decode_query(q) do
|
||||
{first, next} =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> {first, rest}
|
||||
@@ -232,7 +234,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
unpercent(uri)
|
||||
unpercent(uri, "", false)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
@@ -248,23 +250,25 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_www_form(str) do
|
||||
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
|
||||
unpercent(str, "", true)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect str}"
|
||||
end
|
||||
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
|
||||
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
|
||||
end
|
||||
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
|
||||
defp unpercent(<<?%>>), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail :: binary>>) do
|
||||
<<head>> <> unpercent(tail)
|
||||
defp unpercent(<<?+, tail::binary>>, acc, spaces = true) do
|
||||
unpercent(tail, <<acc::binary, ?\s>>, spaces)
|
||||
end
|
||||
|
||||
defp unpercent(<<>>), do: <<>>
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail::binary>>, acc, spaces) do
|
||||
unpercent(tail, <<acc::binary, bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>>, spaces)
|
||||
end
|
||||
defp unpercent(<<?%, _::binary>>, _acc, _spaces), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail::binary>>, acc, spaces) do
|
||||
unpercent(tail, <<acc::binary, head>>, spaces)
|
||||
end
|
||||
defp unpercent(<<>>, acc, _spaces), do: acc
|
||||
|
||||
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
@@ -272,12 +276,17 @@ defmodule URI do
|
||||
defp hex_to_dec(_n), do: throw(:malformed_uri)
|
||||
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
Parses a well-formed URI reference into its components.
|
||||
|
||||
URIs have portions that are handled specially for the particular
|
||||
scheme of the URI. For example, http and https have different
|
||||
default ports. Such values can be accessed and registered via
|
||||
`URI.default_port/1` and `URI.default_port/2`.
|
||||
Note this function expects a well-formed URI and does not perform
|
||||
any validation. See the examples section below of how `URI.parse/1`
|
||||
can be used to parse a wide range of relative URIs.
|
||||
|
||||
This function uses the parsing regular expression as defined
|
||||
in the Appendix B of RFC3986.
|
||||
|
||||
When a URI is given without a port, the values registered via
|
||||
`URI.default_port/1` and `URI.default_port/2` are used.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -286,12 +295,24 @@ defmodule URI do
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
iex> URI.parse("//elixir-lang.org/")
|
||||
%URI{authority: "elixir-lang.org", fragment: nil, host: "elixir-lang.org",
|
||||
path: "/", port: nil, query: nil, scheme: nil, userinfo: nil}
|
||||
|
||||
iex> URI.parse("/foo/bar")
|
||||
%URI{authority: nil, fragment: nil, host: nil, path: "/foo/bar",
|
||||
port: nil, query: nil, scheme: nil, userinfo: nil}
|
||||
|
||||
iex> URI.parse("foo/bar")
|
||||
%URI{authority: nil, fragment: nil, host: nil, path: "foo/bar",
|
||||
port: nil, query: nil, scheme: nil, userinfo: nil}
|
||||
|
||||
"""
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
regex = ~r/^(([a-z][a-z0-9\+\-\.]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
@@ -307,7 +328,7 @@ defmodule URI do
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
if is_nil(port) and not is_nil(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
@@ -347,12 +368,20 @@ defimpl String.Chars, for: URI do
|
||||
if uri.port == port, do: uri = %{uri | port: nil}
|
||||
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
|
||||
|
||||
result = ""
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
|
||||
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
|
||||
|
||||
@@ -51,6 +51,19 @@ defmodule Version do
|
||||
|
||||
"~> 2.0.0"
|
||||
|
||||
`~>` will never include pre-release versions of its upper bound.
|
||||
It can also be used to set an upper bound on only the major
|
||||
version part. See the table below for `~>` requirements and
|
||||
their corresponding translation.
|
||||
|
||||
`~>` | Translation
|
||||
:------------- | :---------------------
|
||||
`~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
|
||||
`~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
|
||||
`~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
|
||||
`~> 2.0` | `>= 2.0.0 and < 3.0.0`
|
||||
`~> 2.1` | `>= 2.1.0 and < 3.0.0`
|
||||
|
||||
"""
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
@@ -58,14 +71,26 @@ defmodule Version do
|
||||
|
||||
@type version :: String.t | t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
@type matchable :: {major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
pre :: [String.t]}
|
||||
@type major :: String.t | non_neg_integer
|
||||
@type minor :: non_neg_integer | nil
|
||||
@type patch :: non_neg_integer | nil
|
||||
@type pre :: [String.t | non_neg_integer]
|
||||
@type build :: String.t | nil
|
||||
@type matchable :: {major :: major,
|
||||
minor :: minor,
|
||||
patch :: patch,
|
||||
pre :: pre}
|
||||
@type t :: %__MODULE__{
|
||||
major: major,
|
||||
minor: minor,
|
||||
patch: patch,
|
||||
pre: pre,
|
||||
build: build}
|
||||
|
||||
defmodule Requirement do
|
||||
@moduledoc false
|
||||
defstruct [:source, :matchspec]
|
||||
@type t :: %__MODULE__{}
|
||||
end
|
||||
|
||||
defmodule InvalidRequirementError do
|
||||
@@ -157,7 +182,8 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
iex> {:ok, version} = Version.parse("2.0.1-alpha1")
|
||||
iex> version
|
||||
#Version<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
@@ -181,7 +207,8 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
|
||||
iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
|
||||
iex> req
|
||||
#Version.Requirement<== 2.0.1>
|
||||
|
||||
iex> Version.parse_requirement("== == 2.0.1")
|
||||
@@ -310,7 +337,7 @@ defmodule Version do
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
if is_nil(minor) or (is_nil(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = String.to_integer(major)
|
||||
@@ -492,10 +519,22 @@ end
|
||||
|
||||
defimpl String.Chars, for: Version do
|
||||
def to_string(version) do
|
||||
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
|
||||
pre = pre(version.pre)
|
||||
build = if build = version.build, do: "+#{build}"
|
||||
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
|
||||
end
|
||||
|
||||
defp pre([]) do
|
||||
""
|
||||
end
|
||||
|
||||
defp pre(pre) do
|
||||
"-" <>
|
||||
Enum.map_join(pre, ".", fn
|
||||
int when is_integer(int) -> Integer.to_string(int)
|
||||
string when is_binary(string) -> string
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version do
|
||||
|
||||
+1
-4
@@ -4,9 +4,6 @@ defmodule Elixir.Mixfile do
|
||||
def project do
|
||||
[app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n"]
|
||||
build_per_environment: false]
|
||||
end
|
||||
end
|
||||
|
||||
+50
-16
@@ -2,7 +2,7 @@
|
||||
%% private to the Elixir compiler and reserved to be used by Elixir only.
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
-export([start_cli/0,
|
||||
string_to_quoted/4, 'string_to_quoted!'/4,
|
||||
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]).
|
||||
@@ -28,8 +28,13 @@ start(_Type, _Args) ->
|
||||
error -> [binary]
|
||||
end,
|
||||
|
||||
io:setopts(standard_io, Opts),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
ok = io:setopts(standard_io, Opts),
|
||||
|
||||
ok = case io:setopts(standard_error, [{encoding,utf8}]) of
|
||||
ok -> ok;
|
||||
{error, _} -> io:setopts(standard_error, [{unicode,true}]) %% R17.3 and earlier
|
||||
end,
|
||||
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
@@ -40,24 +45,48 @@ start(_Type, _Args) ->
|
||||
ok
|
||||
end,
|
||||
|
||||
elixir_sup:start_link().
|
||||
URIs = [{<<"ftp">>, 21},
|
||||
{<<"sftp">>, 22},
|
||||
{<<"tftp">>, 69},
|
||||
{<<"http">>, 80},
|
||||
{<<"https">>, 443},
|
||||
{<<"ldap">>, 389}],
|
||||
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
|
||||
CompilerOpts = [{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
Config = [{at_exit, []},
|
||||
{compiler_options, orddict:from_list(CompilerOpts)}
|
||||
| URIConfig],
|
||||
Tab = elixir_config:new(Config),
|
||||
case elixir_sup:start_link() of
|
||||
{ok, Sup} ->
|
||||
{ok, Sup, Tab};
|
||||
{error, _Reason} = Error ->
|
||||
elixir_config:delete(Tab),
|
||||
Error
|
||||
end.
|
||||
|
||||
stop(Tab) ->
|
||||
elixir_config:delete(Tab).
|
||||
|
||||
stop(_S) ->
|
||||
ok.
|
||||
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
%% escript entry point
|
||||
|
||||
main(Args) ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(Args).
|
||||
|
||||
%% Boot and process given options. Invoked by Elixir's script.
|
||||
|
||||
start_cli() ->
|
||||
application:start(?MODULE),
|
||||
{ok, _} = application:ensure_all_started(?MODULE),
|
||||
|
||||
%% We start the Logger so tools that depend on Elixir
|
||||
%% always have the Logger directly accessible. However
|
||||
%% Logger is not a dependency of the Elixir application,
|
||||
%% which means releases that want to use Logger must
|
||||
%% always list it as part of its applications.
|
||||
_ = case code:ensure_loaded('Elixir.Logger') of
|
||||
{module, _} -> application:start(logger);
|
||||
{error, _} -> ok
|
||||
end,
|
||||
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% EVAL HOOKS
|
||||
@@ -153,14 +182,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{atom, _, Atom} ->
|
||||
{Atom, Binding, NewEnv, NewScope};
|
||||
_ ->
|
||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
|
||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
|
||||
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
|
||||
end.
|
||||
|
||||
erl_eval(Erl, ParsedBinding) ->
|
||||
erl_eval(Erl, ParsedBinding, E) ->
|
||||
case erl_eval:check_command([Erl], ParsedBinding) of
|
||||
ok -> ok;
|
||||
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
|
||||
end,
|
||||
|
||||
% Below must be all one line for locations to be the same when the stacktrace
|
||||
% needs to be extended to the full stacktrace.
|
||||
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||
try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||
|
||||
get_stacktrace() ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=false,
|
||||
keep=false,
|
||||
file=nil,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
@@ -41,28 +41,29 @@
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
|
||||
-define(is_bin(S), (S >= $0 andalso S =< $1)).
|
||||
-define(is_octal(S), (S >= $0 andalso S =< $7)).
|
||||
-define(is_leading_octal(S), (S >= $0 andalso S =< $3)).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_digit(S), (S >= $0 andalso S =< $9)).
|
||||
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
|
||||
-define(is_downcase(S), (S >= $a andalso S =< $z)).
|
||||
|
||||
%% Atoms
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
-define(is_atom_start(S), (?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_atom(S), (?is_identifier(S) orelse (S == $@))).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
-define(is_identifier(S), (?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_))).
|
||||
-define(is_sigil(S), ((S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|))).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_quote(S), (S == $" orelse S == $')).
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
-define(is_horizontal_space(S), ((S == $\s) orelse (S == $\t))).
|
||||
-define(is_vertical_space(S), ((S == $\r) orelse (S == $\n))).
|
||||
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
|
||||
-define(is_invalid_space(S), (S == 16#A0)).
|
||||
@@ -35,70 +35,99 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
|
||||
%% Expand bit info
|
||||
|
||||
expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, default, [], E);
|
||||
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, [Info], E).
|
||||
expand_bit_info(Meta, unpack_bit_info(Info, []), default, [], E).
|
||||
|
||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
||||
type ->
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
|
||||
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
|
||||
case Size of
|
||||
default ->
|
||||
{[EArg], EE} = elixir_exp:expand_args(Args, E),
|
||||
|
||||
case EArg of
|
||||
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
|
||||
ok;
|
||||
_ when is_integer(EArg) ->
|
||||
ok;
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts",
|
||||
['Elixir.Macro':to_string(EArg)])
|
||||
end,
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
|
||||
|
||||
expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"duplicated size definition in bitstring")
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
||||
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
|
||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||
case expand_bit_type(Expr, Args) of
|
||||
type ->
|
||||
{EArgs, EE} = elixir_exp:expand_args(Args, E),
|
||||
validate_bit_type_args(Meta, Expr, EArgs, EE),
|
||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
case Size of
|
||||
expand_bit_info(Meta, [], Size, Types, _) ->
|
||||
[H|T] = case Size of
|
||||
default -> lists:reverse(Types);
|
||||
_ -> [Size|lists:reverse(Types)]
|
||||
end.
|
||||
_ -> lists:reverse(Types, [Size])
|
||||
end,
|
||||
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
|
||||
|
||||
expand_bit_type_or_size(binary, []) -> type;
|
||||
expand_bit_type_or_size(integer, []) -> type;
|
||||
expand_bit_type_or_size(float, []) -> type;
|
||||
expand_bit_type_or_size(bitstring, []) -> type;
|
||||
expand_bit_type_or_size(bytes, []) -> type;
|
||||
expand_bit_type_or_size(bits, []) -> type;
|
||||
expand_bit_type_or_size(utf8, []) -> type;
|
||||
expand_bit_type_or_size(utf16, []) -> type;
|
||||
expand_bit_type_or_size(utf32, []) -> type;
|
||||
expand_bit_type_or_size(signed, []) -> type;
|
||||
expand_bit_type_or_size(unsigned, []) -> type;
|
||||
expand_bit_type_or_size(big, []) -> type;
|
||||
expand_bit_type_or_size(little, []) -> type;
|
||||
expand_bit_type_or_size(native, []) -> type;
|
||||
expand_bit_type_or_size(unit, [_]) -> type;
|
||||
expand_bit_type_or_size(size, [_]) -> size;
|
||||
expand_bit_type_or_size(_, _) -> none.
|
||||
expand_bit_type(binary, []) -> type;
|
||||
expand_bit_type(integer, []) -> type;
|
||||
expand_bit_type(float, []) -> type;
|
||||
expand_bit_type(bitstring, []) -> type;
|
||||
expand_bit_type(bytes, []) -> type;
|
||||
expand_bit_type(bits, []) -> type;
|
||||
expand_bit_type(utf8, []) -> type;
|
||||
expand_bit_type(utf16, []) -> type;
|
||||
expand_bit_type(utf32, []) -> type;
|
||||
expand_bit_type(signed, []) -> type;
|
||||
expand_bit_type(unsigned, []) -> type;
|
||||
expand_bit_type(big, []) -> type;
|
||||
expand_bit_type(little, []) -> type;
|
||||
expand_bit_type(native, []) -> type;
|
||||
expand_bit_type(unit, [_]) -> type;
|
||||
expand_bit_type(_, _) -> none.
|
||||
|
||||
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
|
||||
validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unit in bitstring expects an integer as argument, got: ~ts",
|
||||
['Elixir.Macro':to_string(Unit)]);
|
||||
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, ?m(E, file),
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
expand_bit_info(Meta, List ++ T, Size, Types, E)
|
||||
Info ->
|
||||
expand_bit_info(Meta, unpack_bit_info(Info, []) ++ T, Size, Types, E)
|
||||
end.
|
||||
|
||||
unpack_bit_info({'-', _, [H, T]}, Acc) ->
|
||||
unpack_bit_info(H, unpack_bit_info(T, Acc));
|
||||
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
|
||||
[{unit, [], [Unit]}|Acc];
|
||||
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
|
||||
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
|
||||
unpack_bit_info(Size, Acc) when is_integer(Size) ->
|
||||
[{size, [], [Size]}|Acc];
|
||||
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
[{Expr, Meta, ListArgs}|Acc];
|
||||
unpack_bit_info(Other, Acc) ->
|
||||
[Other|Acc].
|
||||
|
||||
%% Translation
|
||||
|
||||
has_size({bin, _, Elements}) ->
|
||||
@@ -124,7 +153,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{Acc, S};
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
{Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
@@ -201,23 +230,20 @@ unit_size([], Guess) -> Guess.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
|
||||
case elixir_translator:translate(Arg, S) of
|
||||
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
|
||||
{Size, extract_bit_type(Meta, T, S)};
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
extract_bit_info(Meta, T, S) ->
|
||||
{default, extract_bit_type(Meta, T, S)}.
|
||||
extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
|
||||
{extract_bit_size(Size, S), extract_bit_type(L, [])};
|
||||
extract_bit_info({size, _, [Size]}, S) ->
|
||||
{extract_bit_size(Size, S), []};
|
||||
extract_bit_info(L, _S) ->
|
||||
{default, extract_bit_type(L, [])}.
|
||||
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
|
||||
[{unit, Arg}|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
||||
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
extract_bit_size(Size, S) ->
|
||||
{TSize, _} = elixir_translator:translate(Size, S),
|
||||
TSize.
|
||||
|
||||
extract_bit_type({'-', _, [L, R]}, Acc) ->
|
||||
extract_bit_type(L, extract_bit_type(R, Acc));
|
||||
extract_bit_type({unit, _, [Arg]}, Acc) ->
|
||||
[{unit, Arg}|Acc];
|
||||
extract_bit_type({Other, _, []}, Acc) ->
|
||||
[Other|Acc].
|
||||
|
||||
@@ -44,7 +44,7 @@ define({Line,E}, Kind, Call, Expr) ->
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
case code:is_loaded(?kernel) of
|
||||
{_, _} -> apply(?kernel, Fun, Args);
|
||||
false -> Callback()
|
||||
false -> Callback()
|
||||
end.
|
||||
|
||||
env() ->
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
compilation_status=[],
|
||||
argv=[],
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool={[],0},
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
erl_compiler_options=nil
|
||||
paths={[],[]},
|
||||
mod_pool={[],0},
|
||||
mod_ets=dict:new(),
|
||||
compilation_status=[]
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
@@ -27,11 +25,22 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
||||
|
||||
init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
%% We attempt to load those modules here so throughout
|
||||
%% the codebase we can avoid code:is_loaded/1 checks.
|
||||
_ = code:ensure_loaded('Elixir.Macro.Env'),
|
||||
|
||||
%% The table where we store module definitions
|
||||
_ = ets:new(elixir_modules, [set, protected, named_table, {read_concurrency, true}]),
|
||||
|
||||
{ok, #elixir_code_server{}}.
|
||||
|
||||
handle_call({defmodule, Pid, Tuple}, _From, Config) ->
|
||||
{Ref, New} = defmodule(Pid, Tuple, Config),
|
||||
{reply, Ref, New};
|
||||
|
||||
handle_call({undefmodule, Ref}, _From, Config) ->
|
||||
{reply, ok, undefmodule(Ref, Config)};
|
||||
|
||||
handle_call({acquire, Path}, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
@@ -48,58 +57,32 @@ handle_call({acquire, Path}, From, Config) ->
|
||||
handle_call(loaded, _From, Config) ->
|
||||
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
|
||||
|
||||
handle_call(at_exit, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.at_exit, Config};
|
||||
|
||||
handle_call(flush_at_exit, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
|
||||
|
||||
handle_call(argv, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.argv, Config};
|
||||
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.compiler_options, 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),
|
||||
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
{reply, CompilationStatus,
|
||||
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
case Config#elixir_code_server.mod_pool of
|
||||
{[H|T], Counter} ->
|
||||
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
|
||||
{reply, module_tuple(H), Config#elixir_code_server{mod_pool={T,Counter}}};
|
||||
{[], Counter} ->
|
||||
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
|
||||
{reply, module_tuple(Counter), Config#elixir_code_server{mod_pool={[],Counter+1}}}
|
||||
end;
|
||||
|
||||
handle_call(erl_compiler_options, _From, Config) ->
|
||||
case Config#elixir_code_server.erl_compiler_options of
|
||||
nil ->
|
||||
Opts = erl_compiler_options(),
|
||||
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
|
||||
Opts ->
|
||||
{reply, Opts, Config}
|
||||
end;
|
||||
handle_call(paths, _From, Config) ->
|
||||
{reply, Config#elixir_code_server.paths, Config};
|
||||
|
||||
handle_call(Request, _From, Config) ->
|
||||
{stop, {badcall, Request}, Config}.
|
||||
|
||||
handle_cast({at_exit, AtExit}, Config) ->
|
||||
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
|
||||
|
||||
handle_cast({argv, Argv}, Config) ->
|
||||
{noreply, Config#elixir_code_server{argv=Argv}};
|
||||
|
||||
handle_cast({compiler_options, Options}, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{noreply, Config#elixir_code_server{compiler_options=Final}};
|
||||
|
||||
handle_cast({register_warning, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
CompilerOptions = elixir_config:get(compiler_options),
|
||||
case orddict:find(warnings_as_errors, CompilerOptions) of
|
||||
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
_ -> {noreply, Config}
|
||||
end;
|
||||
@@ -115,7 +98,7 @@ handle_cast({loaded, Path}, Config) ->
|
||||
{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)],
|
||||
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{noreply, Config#elixir_code_server{loaded=Done}};
|
||||
error ->
|
||||
@@ -128,13 +111,19 @@ handle_cast({unload_files, Files}, Config) ->
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{noreply, Config#elixir_code_server{loaded=Unloaded}};
|
||||
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{mod_pool={[H|T],Counter}}};
|
||||
|
||||
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
|
||||
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
|
||||
|
||||
handle_cast(Request, Config) ->
|
||||
{stop, {badcast, Request}, Config}.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
handle_info({'DOWN', Ref, process, _Pid, _Reason}, Config) ->
|
||||
{noreply, undefmodule(Ref, Config)};
|
||||
|
||||
handle_info(_Msg, Config) ->
|
||||
{noreply, Config}.
|
||||
|
||||
terminate(_Reason, _Config) ->
|
||||
@@ -146,22 +135,17 @@ code_change(_Old, Config, _Extra) ->
|
||||
module_tuple(I) ->
|
||||
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
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)}}.
|
||||
|
||||
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
|
||||
case dict:find(Ref, ModEts) of
|
||||
{ok, Mod} ->
|
||||
ets:delete(elixir_modules, Mod),
|
||||
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
|
||||
error ->
|
||||
Config
|
||||
end.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
|
||||
get_opt(Key) ->
|
||||
Dict = elixir_code_server:call(compiler_options),
|
||||
Dict = elixir_config:get(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{Key, Value} -> Value
|
||||
@@ -15,15 +15,19 @@ get_opt(Key) ->
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
string(Contents, File, nil).
|
||||
string(Contents, File, Dest) ->
|
||||
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
quoted(Forms, File, Dest).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
quoted(Forms, File, nil).
|
||||
quoted(Forms, File, Dest) ->
|
||||
Previous = get(elixir_compiled),
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
elixir_lexical:run(File, fun
|
||||
elixir_lexical:run(File, Dest, fun
|
||||
(Pid) ->
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
|
||||
@@ -34,14 +38,20 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
end.
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
file(Relative, nil).
|
||||
file(Relative, Dest) ->
|
||||
File = filename:absname(Relative),
|
||||
{ok, Bin} = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
string(elixir_utils:characters_to_list(Bin), File, case Dest of
|
||||
nil -> Dest;
|
||||
_ -> filename:absname(Dest)
|
||||
end).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Lists = file(File),
|
||||
[binary_to_path(X, Path) || X <- Lists],
|
||||
Lists.
|
||||
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
|
||||
Comp = file(File, Dest),
|
||||
Abs = filename:absname(Dest),
|
||||
_ = [binary_to_path(X, Abs) || X <- Comp],
|
||||
Comp.
|
||||
|
||||
%% Evaluation
|
||||
|
||||
@@ -68,9 +78,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
AllOpts = elixir_code_server:call(erl_compiler_options),
|
||||
AllOpts = options(),
|
||||
FinalOpts = AllOpts -- [native, warn_missing_spec],
|
||||
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
|
||||
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
@@ -82,6 +92,36 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
end).
|
||||
|
||||
options() ->
|
||||
case elixir_config:get(erl_compiler_options) of
|
||||
nil ->
|
||||
elixir_config:update(erl_compiler_options, fun options/1);
|
||||
Opts ->
|
||||
Opts
|
||||
end.
|
||||
|
||||
options(nil) ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end;
|
||||
options(Opts) ->
|
||||
Opts.
|
||||
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
Res = Module:Fun(Args),
|
||||
code:delete(Module),
|
||||
@@ -128,43 +168,55 @@ allows_fast_compilation(_) -> false.
|
||||
%% Compile the module by forms based on the scope information
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, File, Opts, Callback) ->
|
||||
module(Forms, Opts, E, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
|
||||
false -> elixir_code_server:call(erl_compiler_options)
|
||||
true -> [debug_info] ++ options();
|
||||
false -> options()
|
||||
end,
|
||||
module(Forms, File, Final, false, Callback).
|
||||
inner_module(Forms, Final, false, E, Callback).
|
||||
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
|
||||
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Source = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Source}|Options]) of
|
||||
{ok, Module, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
|
||||
Callback(Module, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
beam_location(#{module := nil}) -> in_memory;
|
||||
beam_location(#{lexical_tracker := Pid, module := Module}) ->
|
||||
case elixir_lexical:dest(Pid) of
|
||||
nil -> in_memory;
|
||||
Dest ->
|
||||
filename:join(elixir_utils:characters_to_list(Dest),
|
||||
atom_to_list(Module) ++ ".beam")
|
||||
end.
|
||||
|
||||
no_auto_import() ->
|
||||
{attribute, 0, compile, no_auto_import}.
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
||||
{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,
|
||||
_ = elixir_config:update(compiler_options, Update),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
core_file(File) ->
|
||||
try
|
||||
Lists = file(File),
|
||||
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||
_ = [binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||
io:format("Compiled ~ts~n", [File])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
@@ -214,11 +266,11 @@ format_errors([]) ->
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
||||
lists:foreach(fun(Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
||||
end, Errors).
|
||||
|
||||
format_warnings(Bootstrap, Warnings) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
||||
lists:foreach(fun(Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
||||
end, Warnings).
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
-module(elixir_config).
|
||||
-compile({no_auto_import, [get/1]}).
|
||||
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, code_change/3, terminate/2]).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% public api
|
||||
|
||||
new(Opts) ->
|
||||
Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
|
||||
true = ets:insert_new(?MODULE, Opts),
|
||||
Tab.
|
||||
|
||||
delete(?MODULE) ->
|
||||
ets:delete(?MODULE).
|
||||
|
||||
put(Key, Value) ->
|
||||
gen_server:call(?MODULE, {put, Key, Value}).
|
||||
|
||||
get(Key) ->
|
||||
case ets:lookup(?MODULE, Key) of
|
||||
[{_, Value}] -> Value;
|
||||
[] -> nil
|
||||
end.
|
||||
|
||||
update(Key, Fun) ->
|
||||
gen_server:call(?MODULE, {update, Key, Fun}).
|
||||
|
||||
get_and_put(Key, Value) ->
|
||||
gen_server:call(?MODULE, {get_and_put, Key, Value}).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []).
|
||||
|
||||
%% gen_server api
|
||||
|
||||
init(Tab) ->
|
||||
%% Ets table must be writable
|
||||
public = ets:info(Tab, protection),
|
||||
{ok, Tab}.
|
||||
|
||||
handle_call({put, Key, Value}, _From, Tab) ->
|
||||
ets:insert(Tab, {Key, Value}),
|
||||
{reply, ok, Tab};
|
||||
handle_call({update, Key, Fun}, _From, Tab) ->
|
||||
Value = Fun(get(Key)),
|
||||
ets:insert(Tab, {Key, Value}),
|
||||
{reply, Value, Tab};
|
||||
handle_call({get_and_put, Key, Value}, _From, Tab) ->
|
||||
OldValue = get(Key),
|
||||
ets:insert(Tab, {Key, Value}),
|
||||
{reply, OldValue, Tab}.
|
||||
|
||||
handle_cast(Cast, Tab) ->
|
||||
{stop, {bad_cast, Cast}, Tab}.
|
||||
|
||||
handle_info(_Msg, Tab) ->
|
||||
{noreply, Tab}.
|
||||
|
||||
code_change(_OldVsn, Tab, _Extra) ->
|
||||
{ok, Tab}.
|
||||
|
||||
terminate(_Reason, _Tab) ->
|
||||
ok.
|
||||
@@ -3,8 +3,8 @@
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
-define(timeout, 30000). %% 30 seconds
|
||||
-define(limit, 4294967295). %% 2^32 - 1
|
||||
|
||||
next() ->
|
||||
gen_server:call(?MODULE, next, ?timeout).
|
||||
|
||||
+185
-132
@@ -1,49 +1,35 @@
|
||||
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
delete_definition/2, store_definition/6, unwrap_definitions/1,
|
||||
store_each/8, format_error/1]).
|
||||
-export([setup/1, reset_last/1, lookup_definition/2,
|
||||
delete_definition/2, store_definition/6, unwrap_definitions/2,
|
||||
store_each/7, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__def_table').
|
||||
-define(clauses_attr, '__clauses_table').
|
||||
-define(last_def, {elixir, last_def}).
|
||||
-define(attr, {elixir, def_table}).
|
||||
-define(clauses_attr, {elixir, clauses_table}).
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
table(Module) ->
|
||||
ets:lookup_element(Module, ?attr, 2).
|
||||
|
||||
clauses_table(Module) ->
|
||||
ets:lookup_element(Module, ?clauses_attr, 2).
|
||||
|
||||
setup(Module) ->
|
||||
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
|
||||
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
|
||||
reset_last(Module),
|
||||
ok.
|
||||
|
||||
cleanup(Module) ->
|
||||
ets:delete(table(Module)),
|
||||
ets:delete(clauses_table(Module)).
|
||||
|
||||
%% Reset the last item. Useful when evaling code.
|
||||
reset_last(Module) ->
|
||||
ets:insert(table(Module), {last, []}).
|
||||
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
|
||||
|
||||
%% Looks up a definition from the database.
|
||||
lookup_definition(Module, Tuple) ->
|
||||
case ets:lookup(table(Module), Tuple) of
|
||||
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
|
||||
[Result] ->
|
||||
CTable = clauses_table(Module),
|
||||
CTable = elixir_module:clas_table(Module),
|
||||
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete_definition(Module, Tuple) ->
|
||||
ets:delete(table(Module), Tuple),
|
||||
ets:delete(clauses_table(Module), Tuple).
|
||||
ets:delete(elixir_module:defs_table(Module), Tuple),
|
||||
ets:delete(elixir_module:clas_table(Module), Tuple).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
@@ -55,7 +41,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
{Name, Args} = case NameAndArgs of
|
||||
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
|
||||
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
|
||||
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
|
||||
_ -> elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
|
||||
end,
|
||||
|
||||
%% Now that we have verified the call format,
|
||||
@@ -64,11 +50,17 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
||||
|
||||
%% 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.
|
||||
{File, Key} = case lists:keyfind(file, 1, Meta) of
|
||||
{file, Bin} when is_binary(Bin) -> {Bin, keep};
|
||||
_ -> {nil, line}
|
||||
%% If so, we assume this come from another source and
|
||||
%% we need to linify taking into account keep line numbers.
|
||||
{Location, Key} = case lists:keyfind(file, 1, Meta) of
|
||||
{file, KeepFile} when is_binary(KeepFile) ->
|
||||
case lists:keyfind(keep, 1, Meta) of
|
||||
{keep, KeepLine} when is_integer(KeepLine) -> KeepLine;
|
||||
_ -> KeepLine = 0
|
||||
end,
|
||||
{{KeepFile, KeepLine}, keep};
|
||||
_ ->
|
||||
{nil, line}
|
||||
end,
|
||||
|
||||
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
||||
@@ -76,31 +68,28 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
assert_valid_name(Line, Kind, Name, Args, E),
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, File, E).
|
||||
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) ->
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
|
||||
Arity = length(Args),
|
||||
Tuple = {Name, Arity},
|
||||
E = ER#{function := Tuple},
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
Location = retrieve_location(KeepLocation, Module),
|
||||
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = ?m(E, file),
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
File = ?m(E, file),
|
||||
compile_super(Module, Super, E),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
|
||||
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
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],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
@@ -115,31 +104,36 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
_ ->
|
||||
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]
|
||||
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||
ok
|
||||
end.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid -> Pid ! {struct_available, Module}
|
||||
Pid ->
|
||||
Pid ! {struct_available, Module},
|
||||
ok
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
%% Retrieve location from meta file (if Key == keep)
|
||||
%% or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
retrieve_location(Location, Module) ->
|
||||
case get_location_attribute(Module) of
|
||||
nil when not is_binary(File) ->
|
||||
nil;
|
||||
nil ->
|
||||
nil when is_tuple(Location) ->
|
||||
{File, Line} = Location,
|
||||
{normalize_location(File), Line};
|
||||
X when is_binary(X) ->
|
||||
nil ->
|
||||
nil;
|
||||
File when is_binary(File) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{normalize_location(X), 0};
|
||||
{X, L} when is_binary(X) andalso is_integer(L) ->
|
||||
{normalize_location(File), 0};
|
||||
{File, Line} when is_binary(File) andalso is_integer(Line) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{normalize_location(X), L}
|
||||
{normalize_location(File), Line}
|
||||
end.
|
||||
|
||||
get_location_attribute(Module) ->
|
||||
@@ -148,13 +142,13 @@ get_location_attribute(Module) ->
|
||||
false -> 'Elixir.Module':get_attribute(Module, file)
|
||||
end.
|
||||
|
||||
normalize_location(X) ->
|
||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
|
||||
normalize_location(File) ->
|
||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
elixir_def_overridable:super(Module, Function);
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
@@ -164,7 +158,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
Arity = length(Args),
|
||||
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
|
||||
@@ -179,7 +173,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
{[], false};
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind});
|
||||
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, true, S),
|
||||
@@ -216,120 +210,167 @@ is_macro(_) -> false.
|
||||
% Unwrap the functions stored in the functions table.
|
||||
% It returns a list of all functions to be exported, plus the macros,
|
||||
% and the body of all functions.
|
||||
unwrap_definitions(Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
|
||||
unwrap_definitions(File, Module) ->
|
||||
Table = elixir_module:defs_table(Module),
|
||||
CTable = elixir_module:clas_table(Module),
|
||||
|
||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
|
||||
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
|
||||
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
|
||||
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
|
||||
|
||||
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
|
||||
case Clauses of
|
||||
[] -> {false, Exports, Private, Def, Defmacro};
|
||||
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
|
||||
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
|
||||
Export = export(Kind, Tuple),
|
||||
|
||||
{NewFunctions, NewTail} = case NewFun of
|
||||
case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
|
||||
[] ->
|
||||
warn_bodyless_function(Line, File, Module, Kind, Tuple),
|
||||
unwrap_definition(T, File, Module, CTable, All, Private);
|
||||
Clauses ->
|
||||
Unwrapped = {Tuple, Kind, Line, Location,
|
||||
function_for_stored_definition(Line, Export, Clauses)},
|
||||
|
||||
NewPrivate =
|
||||
if
|
||||
Kind == defp; Kind == defmacrop ->
|
||||
[{Tuple, Kind, Line, Check, Defaults}|Private];
|
||||
true ->
|
||||
Private
|
||||
end,
|
||||
|
||||
unwrap_definition(T, File, Module, CTable, [Unwrapped|All], NewPrivate)
|
||||
end;
|
||||
|
||||
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
|
||||
{All, Private}.
|
||||
|
||||
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
|
||||
[export(def, Tuple)|Exports],
|
||||
add_definition(Line, Location, Body, Functions));
|
||||
|
||||
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
case lists:member(Tuple, Unreachable) of
|
||||
false ->
|
||||
NewAll = All,
|
||||
{Functions, Tail};
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
||||
end,
|
||||
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
|
||||
Exports, add_definition(Line, Location, Body, Functions));
|
||||
true ->
|
||||
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
|
||||
Exports, Functions)
|
||||
end;
|
||||
|
||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
|
||||
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
|
||||
[export(defmacro, Tuple)|Exports],
|
||||
add_definition(Line, Location, Body, Functions));
|
||||
|
||||
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
|
||||
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = {elixir_utils:macro_name(Name), Arity + 1},
|
||||
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
|
||||
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
||||
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
|
||||
{Fun, Exports, [Info|Private], Def, Defmacro};
|
||||
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
||||
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
|
||||
{false, Exports, [Info|Private], Def, Defmacro}.
|
||||
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
|
||||
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
|
||||
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
|
||||
Exports, Functions);
|
||||
|
||||
split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, Exports, {Head, Tail}) ->
|
||||
{Def, Defp, Defmacro, Defmacrop, Exports, Head ++ Tail}.
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
|
||||
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
|
||||
export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
|
||||
{elixir_utils:macro_name(Name), Arity + 1};
|
||||
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
|
||||
{Name, Arity}.
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
||||
{Functions, [
|
||||
{function, Line, Name, Arity, Clauses},
|
||||
{attribute, Line, file, Location} | Tail
|
||||
]}.
|
||||
function_for_stored_definition(Line, {Name,Arity}, Clauses) ->
|
||||
{function, Line, Name, Arity, Clauses}.
|
||||
|
||||
add_definition(_Line, nil, Body, {Head, Tail}) ->
|
||||
{[Body|Head], Tail};
|
||||
add_definition(Line, Location, Body, {Head, Tail}) ->
|
||||
{Head,
|
||||
[{attribute, Line, file, Location}, Body|Tail]}.
|
||||
|
||||
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
{function, Line, Name, length(Args) - 1, [Clause]};
|
||||
|
||||
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
|
||||
{function, Line, Name, length(Args), [Clause]}.
|
||||
|
||||
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
|
||||
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
|
||||
ok;
|
||||
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
|
||||
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
|
||||
ok.
|
||||
|
||||
%% Store each definition in the table.
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Tuple = {Name, Arity},
|
||||
case ets:lookup(Table, Tuple) of
|
||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
|
||||
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Data = elixir_module:data_table(Module),
|
||||
Defs = elixir_module:defs_table(Module),
|
||||
Clas = elixir_module:clas_table(Module),
|
||||
|
||||
Tuple = {Name, Arity},
|
||||
HasBody = Clauses =/= [],
|
||||
|
||||
case ets:lookup(Defs, Tuple) of
|
||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
|
||||
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
(Check and StoredCheck) andalso
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults
|
||||
FinalDefaults = {Defaults, HasBody, Defaults}
|
||||
end,
|
||||
Check andalso ets:insert(Table, {last, {Name, Arity}}),
|
||||
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
|
||||
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
|
||||
|
||||
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
|
||||
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
|
||||
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
|
||||
|
||||
%% Validations
|
||||
|
||||
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||
{changed_kind, {Name, Arity, StoredKind, Kind}}).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Data, ?last_def, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
_ ->
|
||||
Relative = elixir_utils:relative_to_cwd(StoredFile),
|
||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
|
||||
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
|
||||
Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
|
||||
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
|
||||
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}});
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
|
||||
% Any clause after clause with defaults (body less does not count)
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, StoredDefaults, true) when StoredDefaults > 0 ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||
{clauses_with_defaults, {Kind, Name, Arity}});
|
||||
% Clause without defaults
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _) -> [];
|
||||
% Clause with defaults after clause without defaults
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0, _) ->
|
||||
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
|
||||
% Clause with defaults after clause with defaults
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||
{clauses_with_defaults, {Kind, Name, Arity}}).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
|
||||
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
|
||||
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
|
||||
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
@@ -339,7 +380,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
|
||||
check_args_for_bodyless_clause(Line, Args, E) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
|
||||
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
|
||||
invalid_args_for_bodyless_clause)
|
||||
end || Arg <- Args, invalid_arg(Arg) ].
|
||||
|
||||
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
|
||||
@@ -350,13 +392,20 @@ invalid_arg(_) ->
|
||||
true.
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
|
||||
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
|
||||
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({bodyless_fun, Kind, {Name, Arity}}) ->
|
||||
io_lib:format("bodyless clause provided for nonexistent ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({no_module,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
@@ -391,5 +440,9 @@ format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({is_record, Kind}) ->
|
||||
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
|
||||
"issues with the Erlang compiler (it is a known bug)", [Kind]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand(Args, E) ->
|
||||
NoContext = E#{context := nil},
|
||||
lists:mapfoldl(fun
|
||||
({'\\\\', Meta, [Left, Right]}, Acc) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, Acc),
|
||||
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
|
||||
{ERight, _} = elixir_exp:expand(Right, NoContext),
|
||||
{{'\\\\', Meta, [ELeft, ERight]}, EL};
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, ensure_defined/4,
|
||||
name/2, store/3, format_error/1]).
|
||||
-export([setup/1, overridable/1, overridable/2, name/2, super/2, store_pending/1,
|
||||
ensure_defined/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-define(attr, {elixir, overridable}).
|
||||
|
||||
setup(Module) ->
|
||||
overridable(Module, []).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
|
||||
|
||||
overridable(Module, Value) ->
|
||||
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
|
||||
ets:insert(elixir_module:data_table(Module), {?attr, Value}).
|
||||
|
||||
%% Check if an overridable function is defined.
|
||||
|
||||
@@ -36,30 +40,31 @@ store(Module, Function, GenerateName) ->
|
||||
{_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}, Clauses} = Clause,
|
||||
{{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
|
||||
|
||||
{FinalKind, FinalName} = case GenerateName of
|
||||
true -> {defp, name(Module, Function, Overridable)};
|
||||
false -> {Kind, Name}
|
||||
end,
|
||||
|
||||
case code:is_loaded('Elixir.Module.LocalsTracker') of
|
||||
{_, _} ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
false ->
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
|
||||
_ ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Def = {function, Line, FinalName, Arity, Clauses},
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
elixir_def:store_each(false, FinalKind, File, Location, Module, Defaults, Def)
|
||||
end.
|
||||
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
super(Module, Function) ->
|
||||
store(Module, Function, true).
|
||||
|
||||
store_pending(Module) ->
|
||||
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
||||
not 'Elixir.Module':'defines?'(Module, X)].
|
||||
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
||||
not 'Elixir.Module':'defines?'(Module, X)],
|
||||
ok.
|
||||
|
||||
%% Error handling
|
||||
|
||||
@@ -72,4 +77,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
|
||||
format_fa({Name, Arity}) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
B = integer_to_binary(Arity),
|
||||
<< A/binary, $/, B/binary >>.
|
||||
<<A/binary, $/, B/binary>>.
|
||||
|
||||
@@ -4,24 +4,12 @@
|
||||
-module(elixir_dispatch).
|
||||
-export([dispatch_import/5, dispatch_require/6,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/5, expand_require/5,
|
||||
expand_import/6, expand_require/5,
|
||||
default_functions/0, default_macros/0, default_requires/0,
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
-define(atom, 'Elixir.Atom').
|
||||
-define(float, 'Elixir.Float').
|
||||
-define(io, 'Elixir.IO').
|
||||
-define(integer, 'Elixir.Integer').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(list, 'Elixir.List').
|
||||
-define(map, 'Elixir.Map').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(string, 'Elixir.String').
|
||||
-define(system, 'Elixir.System').
|
||||
-define(tuple, 'Elixir.Tuple').
|
||||
|
||||
default_functions() ->
|
||||
[{?kernel, elixir_imported_functions()}].
|
||||
@@ -30,17 +18,20 @@ default_macros() ->
|
||||
default_requires() ->
|
||||
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
|
||||
|
||||
%% This is used by elixir_quote. Note we don't record the
|
||||
%% import locally because at that point there is no
|
||||
%% ambiguity.
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = {Name, Arity},
|
||||
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
%% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
Receiver;
|
||||
{macro, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
%% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
@@ -78,7 +69,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
case inline(Receiver, Name, Arity) of
|
||||
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {remote, AR, AN, Arity};
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
end.
|
||||
@@ -87,7 +78,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
|
||||
|
||||
dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
Arity = length(Args),
|
||||
case expand_import(Meta, {Name, Arity}, Args, E, []) of
|
||||
case expand_import(Meta, {Name, Arity}, Args, E, [], false) of
|
||||
{ok, Receiver, Quoted} ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ok, Receiver, NewName, NewArgs} ->
|
||||
@@ -99,9 +90,9 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, AR, AN, AA} ->
|
||||
Callback(AR, AN, AA);
|
||||
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
||||
{AR, AN} ->
|
||||
Callback(AR, AN, Args);
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
|
||||
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
@@ -114,12 +105,12 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) ->
|
||||
Module = ?m(E, module),
|
||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||
Function = ?m(E, function),
|
||||
Local = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra, External) ->
|
||||
Module = ?m(E, module),
|
||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||
Function = ?m(E, function),
|
||||
AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
|
||||
Local = AllowLocals andalso elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, we dispatch the import.
|
||||
@@ -147,11 +138,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
{ok, Receiver, Name, Args};
|
||||
{macro, Receiver} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
@@ -163,8 +150,8 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
case elixir_rewrite:inline(Module, Name, Arity) of
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
false -> error
|
||||
end;
|
||||
false ->
|
||||
@@ -228,7 +215,7 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
end.
|
||||
|
||||
caller(Line, #{module := nil} = E) ->
|
||||
{elixir_compiler, '__FILE__', 2, location(Line, E)};
|
||||
{elixir_compiler_0, '__FILE__', 1, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := nil} = E) ->
|
||||
{Module, '__MODULE__', 0, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
|
||||
@@ -338,152 +325,6 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
rewrite(?atom, to_string, [Arg], _) ->
|
||||
{ok, erlang, atom_to_binary, [Arg, utf8]};
|
||||
rewrite(?kernel, elem, [Tuple, Index], _) ->
|
||||
{ok, erlang, element, [increment(Index), Tuple]};
|
||||
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
|
||||
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
|
||||
rewrite(?map, 'has_key?', [Map, Key], _) ->
|
||||
{ok, maps, is_key, [Key, Map]};
|
||||
rewrite(?map, fetch, [Map, Key], _) ->
|
||||
{ok, maps, find, [Key, Map]};
|
||||
rewrite(?map, put, [Map, Key, Value], _) ->
|
||||
{ok, maps, put, [Key, Value, Map]};
|
||||
rewrite(?map, delete, [Map, Key], _) ->
|
||||
{ok, maps, remove, [Key, Map]};
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
rewrite(?string, to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?string, to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
false -> false
|
||||
end.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
|
||||
|
||||
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?float, to_string, 1) -> {erlang, float_to_binary};
|
||||
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
|
||||
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?list, to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
|
||||
inline(?kernel, '+', 2) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 2) -> {erlang, '-'};
|
||||
inline(?kernel, '+', 1) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 1) -> {erlang, '-'};
|
||||
inline(?kernel, '*', 2) -> {erlang, '*'};
|
||||
inline(?kernel, '/', 2) -> {erlang, '/'};
|
||||
inline(?kernel, '++', 2) -> {erlang, '++'};
|
||||
inline(?kernel, '--', 2) -> {erlang, '--'};
|
||||
inline(?kernel, 'not', 1) -> {erlang, 'not'};
|
||||
inline(?kernel, '<', 2) -> {erlang, '<'};
|
||||
inline(?kernel, '>', 2) -> {erlang, '>'};
|
||||
inline(?kernel, '<=', 2) -> {erlang, '=<'};
|
||||
inline(?kernel, '>=', 2) -> {erlang, '>='};
|
||||
inline(?kernel, '==', 2) -> {erlang, '=='};
|
||||
inline(?kernel, '!=', 2) -> {erlang, '/='};
|
||||
inline(?kernel, '===', 2) -> {erlang, '=:='};
|
||||
inline(?kernel, '!==', 2) -> {erlang, '=/='};
|
||||
inline(?kernel, abs, 1) -> {erlang, abs};
|
||||
inline(?kernel, apply, 2) -> {erlang, apply};
|
||||
inline(?kernel, apply, 3) -> {erlang, apply};
|
||||
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
|
||||
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
|
||||
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
|
||||
inline(?kernel, 'div', 2) -> {erlang, 'div'};
|
||||
inline(?kernel, exit, 1) -> {erlang, exit};
|
||||
inline(?kernel, hd, 1) -> {erlang, hd};
|
||||
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
|
||||
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
|
||||
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
|
||||
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
|
||||
inline(?kernel, is_float, 1) -> {erlang, is_float};
|
||||
inline(?kernel, is_function, 1) -> {erlang, is_function};
|
||||
inline(?kernel, is_function, 2) -> {erlang, is_function};
|
||||
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
|
||||
inline(?kernel, is_list, 1) -> {erlang, is_list};
|
||||
inline(?kernel, is_map, 1) -> {erlang, is_map};
|
||||
inline(?kernel, is_number, 1) -> {erlang, is_number};
|
||||
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
|
||||
inline(?kernel, is_port, 1) -> {erlang, is_port};
|
||||
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
|
||||
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
|
||||
inline(?kernel, length, 1) -> {erlang, length};
|
||||
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
|
||||
inline(?kernel, map_size, 1) -> {erlang, map_size};
|
||||
inline(?kernel, max, 2) -> {erlang, max};
|
||||
inline(?kernel, min, 2) -> {erlang, min};
|
||||
inline(?kernel, node, 0) -> {erlang, node};
|
||||
inline(?kernel, node, 1) -> {erlang, node};
|
||||
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
|
||||
inline(?kernel, round, 1) -> {erlang, round};
|
||||
inline(?kernel, self, 0) -> {erlang, self};
|
||||
inline(?kernel, send, 2) -> {erlang, send};
|
||||
inline(?kernel, spawn, 1) -> {erlang, spawn};
|
||||
inline(?kernel, spawn, 3) -> {erlang, spawn};
|
||||
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, throw, 1) -> {erlang, throw};
|
||||
inline(?kernel, tl, 1) -> {erlang, tl};
|
||||
inline(?kernel, trunc, 1) -> {erlang, trunc};
|
||||
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
|
||||
inline(?node, spawn, 2) -> {erlang, spawn};
|
||||
inline(?node, spawn, 3) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn, 4) -> {erlang, spawn};
|
||||
inline(?node, spawn, 5) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
|
||||
|
||||
inline(?process, exit, 2) -> {erlang, exit};
|
||||
inline(?process, spawn, 2) -> {erlang, spawn_opt};
|
||||
inline(?process, spawn, 4) -> {erlang, spawn_opt};
|
||||
inline(?process, demonitor, 1) -> {erlang, demonitor};
|
||||
inline(?process, demonitor, 2) -> {erlang, demonitor};
|
||||
inline(?process, link, 1) -> {erlang, link};
|
||||
inline(?process, unlink, 1) -> {erlang, unlink};
|
||||
|
||||
inline(?string, to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
|
||||
case deprecation(Receiver, Name, Arity) of
|
||||
false -> ok;
|
||||
@@ -507,15 +348,5 @@ deprecation_message(Warning, Message) ->
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
deprecation('Elixir.Mix.Generator', 'from_file', _) ->
|
||||
"instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
|
||||
"Note that [from_file: file] expects paths relative to the current working "
|
||||
"directory and not to the current file";
|
||||
deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
|
||||
"check EEx.SmartEngine for how to build custom engines";
|
||||
deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
|
||||
"check EEx.SmartEngine for how to build custom engines";
|
||||
deprecation('Elixir.Kernel', 'xor', _) ->
|
||||
true; %% Remember to remove xor operator from tokenizer
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -2,36 +2,37 @@
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/2, warn/3,
|
||||
form_error/4, form_warn/4, parse_error/4, warn/2, warn/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-type line_or_meta() :: integer() | list().
|
||||
-spec warn(non_neg_integer(), binary(), unicode:chardata()) -> ok.
|
||||
|
||||
warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({register_warning, CompilerPid});
|
||||
true -> false
|
||||
end,
|
||||
io:put_chars(standard_error, Warning).
|
||||
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
|
||||
warn(file_format(Line, File), Warning).
|
||||
|
||||
-spec warn(unicode:chardata(), unicode:chardata()) -> ok.
|
||||
|
||||
warn(Caller, Warning) ->
|
||||
warn([Caller, "warning: ", Warning]).
|
||||
do_warn([Caller, "warning: ", Warning, $\n]).
|
||||
|
||||
warn(Line, File, Warning) when is_integer(Line) ->
|
||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
||||
%% General forms handling.
|
||||
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
compile_error(Meta, File, format_error(Module, Desc)).
|
||||
|
||||
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
|
||||
-spec form_warn(list(), binary(), module(), any()) -> ok.
|
||||
|
||||
form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
|
||||
{MetaLine, MetaFile} = meta_location(Meta, File),
|
||||
warn(MetaLine, MetaFile, format_error(Module, Desc)).
|
||||
|
||||
%% Compilation error.
|
||||
|
||||
-spec compile_error(list(), binary(), unicode:charlist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), string(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
@@ -39,23 +40,39 @@ compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
compile_error(Meta, File, io_lib:format(Format, Args)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
%% Tokenization parsing/errors.
|
||||
|
||||
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return().
|
||||
-spec parse_error(non_neg_integer(), binary(), binary(), binary()) -> no_return().
|
||||
|
||||
parse_error(Meta, File, Error, <<>>) ->
|
||||
parse_error(Line, File, Error, <<>>) ->
|
||||
Message = case Error of
|
||||
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
|
||||
_ -> Error
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.TokenMissingError', Message);
|
||||
do_raise(Line, File, 'Elixir.TokenMissingError', Message);
|
||||
|
||||
%% Show a nicer message for missing end tokens
|
||||
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
||||
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
||||
%% Produce a human-readable message for errors before a sigil
|
||||
parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) ->
|
||||
{ok, Tokens, _} = erl_scan:string(binary_to_list(Full)),
|
||||
{ok, {sigil, _, Sigil, [Content], _}} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
|
||||
Message = <<"syntax error before: sigil ~", Sigil," with content '", Content/binary, "'">>,
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', Message);
|
||||
|
||||
%% Aliases are wrapped in ['']
|
||||
parse_error(Line, File, Error, <<"['", Token/binary>>) when is_binary(Error) ->
|
||||
Rest =
|
||||
case binary:split(Token, <<"'">>) of
|
||||
[Part, _] -> Part;
|
||||
_ -> <<>>
|
||||
end,
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary>>);
|
||||
|
||||
%% Binaries (and interpolation) are wrapped in [<<...>>]
|
||||
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
||||
Rest =
|
||||
case binary:split(Full, <<"<<">>) of
|
||||
[Lead, Token] ->
|
||||
@@ -66,27 +83,27 @@ parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -
|
||||
[_] ->
|
||||
<<$">>
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
|
||||
|
||||
%% Everything else is fine as is
|
||||
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||
Message = <<Error / binary, Token / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
parse_error(Line, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||
Message = <<Error/binary, Token/binary >>,
|
||||
do_raise(Line, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
%% Handle warnings and errors from Erlang land (called during module compilation)
|
||||
|
||||
%% Ignore on bootstrap
|
||||
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> [];
|
||||
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> [];
|
||||
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> ok;
|
||||
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> ok;
|
||||
|
||||
%% Ignore always
|
||||
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> [];
|
||||
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
|
||||
|
||||
%% This is an Erlang bug, it considers {tuple, _}.call to always fail
|
||||
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> [];
|
||||
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> ok;
|
||||
|
||||
%% We handle unused local warnings ourselves
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> [];
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
|
||||
|
||||
%% Make no_effect clauses pretty
|
||||
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
|
||||
@@ -95,7 +112,7 @@ handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}})
|
||||
false ->
|
||||
case erl_internal:bif(F, A) of
|
||||
false -> {"the call to :erlang.~ts/~B has no effect", [F,A]};
|
||||
true -> {"the call to ~ts/~B has no effect", [F,A]}
|
||||
true -> {"the call to ~ts/~B has no effect", [F,A]}
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
@@ -116,7 +133,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
true -> ok;
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||
warn(Line, File, Message)
|
||||
@@ -124,17 +141,29 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
|
||||
%% Ignore unused vars at "weird" lines (<= 0)
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
[];
|
||||
ok;
|
||||
|
||||
%% Ignore shadowed vars as we guarantee no conflicts ourselves
|
||||
%% Ignore shadowed and exported vars as we guarantee no conflicts ourselves
|
||||
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
[];
|
||||
ok;
|
||||
|
||||
handle_file_warning(_, _File, {_Line,erl_lint,{exported_var,_Var,_Where}}) ->
|
||||
ok;
|
||||
|
||||
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
|
||||
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
|
||||
warn(Line, File, "this check/guard will always yield the same result");
|
||||
|
||||
%% Properly format other unused vars
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Handle literal eval failures
|
||||
handle_file_warning(_, File, {Line,sys_core_fold,{eval_failure, Error}}) ->
|
||||
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
|
||||
warn(Line, File, ["this expression will fail with ", elixir_aliases:inspect(Struct)]);
|
||||
|
||||
%% Default behaviour
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
@@ -152,39 +181,27 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
_ -> In
|
||||
end,
|
||||
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
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]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
Message = format_error(Module, Desc),
|
||||
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
raise(?line(Meta), File, Kind, Message);
|
||||
{MetaLine, MetaFile} = meta_location(Meta, File),
|
||||
do_raise(MetaLine, MetaFile, Kind, Message).
|
||||
|
||||
raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
file_format(0, File) ->
|
||||
io_lib:format("~ts: ", [elixir_utils:relative_to_cwd(File)]);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
||||
%% Populate the stacktrace so we can raise it
|
||||
try
|
||||
throw(ok)
|
||||
catch
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
|
||||
|
||||
file_format(Line, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
|
||||
file_format(Line, File) ->
|
||||
io_lib:format("~ts:~w: ", [elixir_utils:relative_to_cwd(File), Line]).
|
||||
|
||||
format_var(Var) ->
|
||||
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
|
||||
@@ -215,3 +232,37 @@ translate_comp_op('=<') -> '<=';
|
||||
translate_comp_op('=:=') -> '===';
|
||||
translate_comp_op('=/=') -> '!==';
|
||||
translate_comp_op(Other) -> Other.
|
||||
|
||||
meta_location(Meta, File) ->
|
||||
case lists:keyfind(file, 1, Meta) of
|
||||
{file, MetaFile} when is_binary(MetaFile) ->
|
||||
case lists:keyfind(keep, 1, Meta) of
|
||||
{keep, MetaLine} when is_integer(MetaLine) -> ok;
|
||||
_ -> MetaLine = 0
|
||||
end,
|
||||
{MetaLine, MetaFile};
|
||||
_ ->
|
||||
{?line(Meta), File}
|
||||
end.
|
||||
|
||||
do_warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({register_warning, CompilerPid});
|
||||
true -> false
|
||||
end,
|
||||
io:put_chars(standard_error, Warning),
|
||||
ok.
|
||||
|
||||
do_raise(none, File, Kind, Message) ->
|
||||
do_raise(0, File, Kind, Message);
|
||||
do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_binary(Message) ->
|
||||
try
|
||||
throw(ok)
|
||||
catch
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user