Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83ceee54b3 | ||
|
|
b7a33832a5 | ||
|
|
ef76cadcb2 | ||
|
|
ebb989d3ed | ||
|
|
00eede696e | ||
|
|
3a94bab1bb | ||
|
|
2564635548 | ||
|
|
657054116b | ||
|
|
b7da298d02 | ||
|
|
a75e70b856 | ||
|
|
aba0d5ba3f | ||
|
|
d334c1d6e4 | ||
|
|
d856e667b9 | ||
|
|
5bd12f9cb9 | ||
|
|
3c151d5357 | ||
|
|
d92175ae41 | ||
|
|
2e9f4196cd | ||
|
|
3f8363c019 | ||
|
|
98d791acb4 | ||
|
|
0134a8f394 | ||
|
|
3d6071d640 | ||
|
|
821b7c6f76 | ||
|
|
9897b894be | ||
|
|
90af53c070 | ||
|
|
2566dd7e24 | ||
|
|
a7e63ae1f6 | ||
|
|
a91e1f2288 | ||
|
|
174f031efd | ||
|
|
f1e2fcf9f9 | ||
|
|
dd47d099c9 | ||
|
|
992ac67512 | ||
|
|
9ff328096b | ||
|
|
6fbc92b088 | ||
|
|
2d1facb17b | ||
|
|
ad4c89714c | ||
|
|
7120a5e050 | ||
|
|
d0fd5ed7f0 | ||
|
|
0b624fff39 | ||
|
|
5c4b8fb2cd | ||
|
|
64296f114d | ||
|
|
8e7b86964e | ||
|
|
ef475b241b | ||
|
|
1ca7c7a16e | ||
|
|
3f71c03547 | ||
|
|
15d2d9f249 | ||
|
|
a31f180dd9 | ||
|
|
e31a06d979 | ||
|
|
49fe6134ee | ||
|
|
9a684f0c0f | ||
|
|
7193753b82 | ||
|
|
e9af1ea988 | ||
|
|
97d4c56f3a | ||
|
|
759539ffe5 | ||
|
|
3bc24dfcf0 | ||
|
|
4ecc939a86 | ||
|
|
030816959c | ||
|
|
feab232bca | ||
|
|
eee4351311 | ||
|
|
6c3664c989 | ||
|
|
737b39850f | ||
|
|
67ac77fdb3 | ||
|
|
0171e201fb | ||
|
|
86b3f72c5d | ||
|
|
ed2c41837a | ||
|
|
5bdcc6643d | ||
|
|
2464cea20f | ||
|
|
4343b8a1e2 | ||
|
|
98a5748376 | ||
|
|
404f77421a | ||
|
|
1e0daad1b1 | ||
|
|
0b3c91bf6d | ||
|
|
446f5dc2c3 | ||
|
|
d421af678a | ||
|
|
e63e4d44a3 | ||
|
|
09414b0ade | ||
|
|
9e6f49a55e | ||
|
|
60455a9cdc | ||
|
|
d054f4f852 | ||
|
|
39b07ae8f7 | ||
|
|
b89309b05a | ||
|
|
2d8268cb8d | ||
|
|
80aac85caf | ||
|
|
874517efd1 | ||
|
|
1963026833 | ||
|
|
edcde3e801 | ||
|
|
088cfe0a4c | ||
|
|
fc42dc9bb7 | ||
|
|
5f9e0c1212 | ||
|
|
7131696621 | ||
|
|
a4234a4494 | ||
|
|
925f08c612 | ||
|
|
16a457f2f2 | ||
|
|
1c3f8a059b | ||
|
|
dca38e4377 | ||
|
|
db41ceaf38 | ||
|
|
b32b6a5108 | ||
|
|
4391247d30 | ||
|
|
e46e1b2907 | ||
|
|
2644060de4 | ||
|
|
295fe4605b | ||
|
|
8d2251f8f1 | ||
|
|
f0771bb392 | ||
|
|
d93258ec69 | ||
|
|
f751deea85 | ||
|
|
f8c894004b | ||
|
|
8b77b603b1 | ||
|
|
38948f7ed7 | ||
|
|
bb5b0b87c2 | ||
|
|
5b42cefb64 | ||
|
|
3ee311cd0b | ||
|
|
18a33427f5 | ||
|
|
d68b9340f0 | ||
|
|
50a2f8a8ea | ||
|
|
bc080f5d0b | ||
|
|
1a0e8378ea | ||
|
|
e2e975a28e | ||
|
|
3b141bb474 | ||
|
|
3b07eb3ceb | ||
|
|
a5f2a04ae3 | ||
|
|
046af30c1b | ||
|
|
71071b6a09 | ||
|
|
8785313413 | ||
|
|
fe2f04837e | ||
|
|
e3da91ad4f | ||
|
|
51d9856638 | ||
|
|
f4fc764789 | ||
|
|
79366ee7fc | ||
|
|
5765db5919 | ||
|
|
ca8a877d69 | ||
|
|
caa2eb1709 | ||
|
|
8be9ceac82 | ||
|
|
4fc3fc11de | ||
|
|
7d2669634b | ||
|
|
70275a5c8c | ||
|
|
0b92acf309 | ||
|
|
c228323228 | ||
|
|
b91ae66e04 | ||
|
|
7981f9df2d | ||
|
|
641b3f0766 | ||
|
|
9a9fc7318c | ||
|
|
95e3fec729 | ||
|
|
e884f84f03 | ||
|
|
3cc8e10525 | ||
|
|
60d11ed7cc | ||
|
|
64754ab7bf | ||
|
|
8099754898 | ||
|
|
4c2301b3a3 | ||
|
|
cd74bd5d54 | ||
|
|
a8debf06ad | ||
|
|
2ca8066c3a | ||
|
|
f0b36dcc4e | ||
|
|
be47b2aaf8 | ||
|
|
bf7662f9ec | ||
|
|
ea9b0e07da | ||
|
|
b343b6c459 | ||
|
|
429548ea75 | ||
|
|
997a0857c3 | ||
|
|
d7ab47cddb | ||
|
|
4e0b1b5150 | ||
|
|
811b60c115 | ||
|
|
203e37d698 | ||
|
|
0ab77cf550 | ||
|
|
a4e69dea6b | ||
|
|
fc762befcc | ||
|
|
2952512330 | ||
|
|
e93bd1e21b | ||
|
|
d50cd8a681 | ||
|
|
356d445f2e | ||
|
|
bf5982c239 | ||
|
|
056069071c | ||
|
|
a4deb46d9e | ||
|
|
7e8b9b6e52 | ||
|
|
1bd5003f9b | ||
|
|
cdd11e7bf1 | ||
|
|
10270da4cf | ||
|
|
9fbe5cc57c | ||
|
|
cbec463554 | ||
|
|
378f6ee0e2 | ||
|
|
2bb10d43da | ||
|
|
3eb89fb31c | ||
|
|
54353f39dc | ||
|
|
45b7debd35 | ||
|
|
79d2ddc46a | ||
|
|
0df9285db1 | ||
|
|
9cc440bfd7 | ||
|
|
404faaf484 | ||
|
|
e2f81270a6 | ||
|
|
b262884a19 | ||
|
|
6126261dea | ||
|
|
76b3a2af69 | ||
|
|
39c1fa91b4 | ||
|
|
bc10b6d848 | ||
|
|
0b51a53fe9 | ||
|
|
af2ce9cfc4 | ||
|
|
53c9dc33e0 | ||
|
|
0870003f1f | ||
|
|
bce28ab58a | ||
|
|
90de6c9a54 | ||
|
|
2eee8f77c7 | ||
|
|
6923e7bfc6 | ||
|
|
3f8d5b59ec | ||
|
|
1317164d11 | ||
|
|
63b2180553 | ||
|
|
e7c3bed4dd | ||
|
|
8683026d05 | ||
|
|
949c45d610 | ||
|
|
3dbdc77550 | ||
|
|
099879ef6d | ||
|
|
e217f7ec5b | ||
|
|
34b9cb67bd | ||
|
|
a3800b0ae5 | ||
|
|
b1b7c82f48 | ||
|
|
f2919e873d | ||
|
|
b90d2608b5 | ||
|
|
04077ec7c2 | ||
|
|
bae4c1c4c8 | ||
|
|
fad38e1bab | ||
|
|
e17df44ef3 | ||
|
|
5aa2340c7d | ||
|
|
65b0bde5e8 | ||
|
|
260b7387df | ||
|
|
d13fe163e3 | ||
|
|
3a834fcd2c | ||
|
|
f0af35d585 | ||
|
|
f510d4c3df | ||
|
|
4eee44fbae | ||
|
|
0c883e1aa2 | ||
|
|
2297f34179 | ||
|
|
a2c1f362c2 | ||
|
|
8575319c0e | ||
|
|
4c67f3daa0 | ||
|
|
3cf9774b92 | ||
|
|
e35ddb6781 | ||
|
|
a0b8067571 | ||
|
|
5f6fa9be11 | ||
|
|
81c6ac2548 | ||
|
|
47ab0c7a3a | ||
|
|
436cdde499 | ||
|
|
7c7c3eb3c2 | ||
|
|
5cb06e9640 | ||
|
|
a7142e2f6d | ||
|
|
36331f0f0b | ||
|
|
4997bc1e10 | ||
|
|
0f14e5001d | ||
|
|
d0f410ccd0 | ||
|
|
cb023c4934 | ||
|
|
6a77057910 | ||
|
|
e2be2579c5 | ||
|
|
f9ec6cfb5f | ||
|
|
cc1d7c5823 | ||
|
|
7649a5dba4 | ||
|
|
d221e9a161 | ||
|
|
4638831103 | ||
|
|
60b2928678 | ||
|
|
3f0c583045 | ||
|
|
0ce2fbb3e4 | ||
|
|
04c5c80353 | ||
|
|
1cb8f3a654 | ||
|
|
7b025dd982 | ||
|
|
eff51e4045 | ||
|
|
ea5df228ed | ||
|
|
769b69ad69 | ||
|
|
0e725fb906 | ||
|
|
4b317a75d8 | ||
|
|
2977a46865 | ||
|
|
d92b2e6831 | ||
|
|
0a7f6e7cd2 | ||
|
|
8bd43ed677 | ||
|
|
bcf8563d38 | ||
|
|
352379112a | ||
|
|
f8c66c8516 | ||
|
|
f090045e99 | ||
|
|
4256daf6a7 | ||
|
|
8c77ada8d2 | ||
|
|
63ed270c68 | ||
|
|
acdc55bd90 | ||
|
|
d2bfd10299 | ||
|
|
b2e32e4ccd | ||
|
|
0c80905f63 | ||
|
|
429fc2094b | ||
|
|
e8c3ed2881 | ||
|
|
3d583e7cdc | ||
|
|
7b1b0d93d8 | ||
|
|
9839bfa4ad | ||
|
|
0b640d985c | ||
|
|
b161d80b4c | ||
|
|
748b70d796 | ||
|
|
0267f312b1 | ||
|
|
8a45ca8905 | ||
|
|
05c3e98f64 | ||
|
|
212acf437a | ||
|
|
fc0af0542b | ||
|
|
14272c98b4 | ||
|
|
42ffd31e0e | ||
|
|
7aeb1b6930 | ||
|
|
f3f64cdba2 | ||
|
|
5b8a30be6b | ||
|
|
e90b877575 | ||
|
|
3d2ad9ac77 | ||
|
|
881bdd5534 | ||
|
|
c3bdfc9ba7 | ||
|
|
262e8b5f22 | ||
|
|
2f5afcaab9 | ||
|
|
4e42b75e17 | ||
|
|
c6f117d60f | ||
|
|
80c0ff84fb | ||
|
|
59b4b5b9bc | ||
|
|
2945939f3e | ||
|
|
3d9aa3e978 | ||
|
|
860fcc6eaa | ||
|
|
73f40cb10e | ||
|
|
8b56252e1b | ||
|
|
b8a0717873 | ||
|
|
8dbfcba237 | ||
|
|
5bb4f43e9d | ||
|
|
808f8981b3 | ||
|
|
73049ac355 | ||
|
|
d77ccf9415 | ||
|
|
9e644dfba1 | ||
|
|
e00eedec45 | ||
|
|
4016864db2 | ||
|
|
38c7edd203 | ||
|
|
fe3ab0cb77 | ||
|
|
9f81ac89e5 | ||
|
|
cd4c1976c9 | ||
|
|
87be134a5b | ||
|
|
e654f73762 | ||
|
|
147a452e7f | ||
|
|
b39a5d8632 | ||
|
|
4caa2e01f1 | ||
|
|
71129bca33 | ||
|
|
a107b2ba64 | ||
|
|
654988fe60 | ||
|
|
10a795ff44 | ||
|
|
ba209268ad | ||
|
|
80ae944d83 | ||
|
|
4d3a83262c | ||
|
|
6d02cd9076 | ||
|
|
42692c27ab | ||
|
|
2b34ae14da | ||
|
|
6e60fa160e | ||
|
|
dd4a0e96dd | ||
|
|
f4175a1dfa | ||
|
|
bcceccd85e | ||
|
|
b1cf5d9252 | ||
|
|
447d2215d1 | ||
|
|
ffbcf67431 | ||
|
|
d88cd5c92c | ||
|
|
fd5cbd2be6 | ||
|
|
e525fb3176 | ||
|
|
49aed8cb9a | ||
|
|
366d9843aa | ||
|
|
0f2838c98c | ||
|
|
8ae1a3b60a | ||
|
|
4a405cc8f0 | ||
|
|
1aca6db15a | ||
|
|
f7391b48fa | ||
|
|
d78014e7e2 | ||
|
|
b2c2a30c83 | ||
|
|
9ef2996b78 | ||
|
|
0d13468b1f | ||
|
|
6ef031b87d | ||
|
|
5f1e5ebc5f | ||
|
|
9312a7d127 | ||
|
|
c555bf934b | ||
|
|
d4efc7bc5d | ||
|
|
3c7fb0da6d | ||
|
|
89bebeaeea | ||
|
|
c826aadc0f | ||
|
|
e304ea9543 | ||
|
|
64cdc76832 | ||
|
|
dbbfa3536f | ||
|
|
25ffb4b4b1 | ||
|
|
e80dad1925 | ||
|
|
e2599beff2 | ||
|
|
8478db168a | ||
|
|
34dade0916 | ||
|
|
eb96d73198 | ||
|
|
cbb1f45ede | ||
|
|
4723bca17e | ||
|
|
263f4bf543 | ||
|
|
fad339957d | ||
|
|
488344839a | ||
|
|
f02f90fce0 | ||
|
|
c73919993d | ||
|
|
3768087b47 | ||
|
|
4a0db2e974 | ||
|
|
55aad68512 | ||
|
|
3b4954b652 | ||
|
|
fb30101632 | ||
|
|
c348eb7cc5 | ||
|
|
98c47e94a1 | ||
|
|
4c28f325e3 | ||
|
|
da50584060 | ||
|
|
891564f114 | ||
|
|
22336a94d0 | ||
|
|
cae9b05d6a | ||
|
|
a6cdc835a1 | ||
|
|
7ae1795c62 | ||
|
|
a5ea8a63bf | ||
|
|
742258340c | ||
|
|
1be3335f0b | ||
|
|
7b3bbe3a36 | ||
|
|
ade2c25e08 | ||
|
|
e2ba0bd649 | ||
|
|
3de4cbbdb9 | ||
|
|
0510512375 | ||
|
|
a3827527a8 | ||
|
|
820d0cb9a0 | ||
|
|
0e1aac32f5 | ||
|
|
307198c750 | ||
|
|
308d5a35eb | ||
|
|
6244bdd140 | ||
|
|
384921a905 | ||
|
|
fcc37030e2 | ||
|
|
473f3c560e | ||
|
|
fb8ad142e9 | ||
|
|
7a50baa37b | ||
|
|
2f63c745a3 | ||
|
|
fcef2d57fc | ||
|
|
fcabb677d5 | ||
|
|
de3d5cdc40 | ||
|
|
29f030bb28 | ||
|
|
8b02a2a7a0 | ||
|
|
f7e6b29e86 | ||
|
|
c6a12f5039 | ||
|
|
20418a14f7 | ||
|
|
3e1099830a | ||
|
|
82aed93466 | ||
|
|
df9098ea36 | ||
|
|
6831c09913 | ||
|
|
7f00aded18 | ||
|
|
37689946d2 | ||
|
|
d76dba1518 | ||
|
|
baf647986f | ||
|
|
84a2b31221 | ||
|
|
9385e59c29 | ||
|
|
6d9506c012 | ||
|
|
11640d521f | ||
|
|
34e0637520 | ||
|
|
f84b4121ce | ||
|
|
ff2af55e1d | ||
|
|
a45a2c9d93 | ||
|
|
e3dc5564e8 | ||
|
|
b4a42a4f87 | ||
|
|
f2066e0ea9 | ||
|
|
eccf6c9e6e | ||
|
|
aaebf84440 | ||
|
|
9956785769 | ||
|
|
8a69780246 | ||
|
|
7f36bf105a | ||
|
|
f832d3874b | ||
|
|
42339679e0 | ||
|
|
0edc5ac2b9 | ||
|
|
74e194669b | ||
|
|
80ce7fc787 | ||
|
|
f4270a0432 | ||
|
|
a696b9cc11 | ||
|
|
ffc629a408 | ||
|
|
081dbceeee | ||
|
|
9ec2176675 | ||
|
|
1ec8321658 | ||
|
|
eb9753ddd1 | ||
|
|
5193410ba9 | ||
|
|
59013d4066 | ||
|
|
a641a63f98 | ||
|
|
a86288f5e3 | ||
|
|
4900cd84fc | ||
|
|
09b34c3aa6 | ||
|
|
5c3ff52741 | ||
|
|
7ad6d8e6e2 | ||
|
|
35af4348ae | ||
|
|
a60e6fab48 | ||
|
|
d6c7e813f3 | ||
|
|
5ce8f34fe6 | ||
|
|
ff9aba4350 | ||
|
|
6ac280e17c | ||
|
|
db47bd56b4 | ||
|
|
e2ef1ae8de | ||
|
|
d5fc147ff0 | ||
|
|
cea31f69dc | ||
|
|
3259599111 | ||
|
|
8affa706b7 | ||
|
|
b248822a0c | ||
|
|
e4d679c039 | ||
|
|
b5cebbfc14 | ||
|
|
649536fc61 | ||
|
|
676cab5c8f | ||
|
|
bdf7bc5e6c | ||
|
|
d8ba3e63b3 | ||
|
|
8e43b4268d | ||
|
|
0850430768 | ||
|
|
b14617ca90 | ||
|
|
bc35aebd0c | ||
|
|
0276419f4e | ||
|
|
b710ebd5e6 | ||
|
|
ec479a6805 | ||
|
|
6277009d90 | ||
|
|
52188fab89 | ||
|
|
19ac199b06 | ||
|
|
5262cb9c57 | ||
|
|
82d8d2ad63 | ||
|
|
5a620ca319 | ||
|
|
cbcdf411fa | ||
|
|
40478d1014 | ||
|
|
7100965169 | ||
|
|
d8ded6ecd4 | ||
|
|
7e0428f52f | ||
|
|
77ddc4540d | ||
|
|
742219339c | ||
|
|
e02bf30c9d | ||
|
|
cde006affa | ||
|
|
9daa0bc3a2 | ||
|
|
b0317a8e18 | ||
|
|
4eb98a83e5 | ||
|
|
7e17551456 | ||
|
|
437037cda6 | ||
|
|
d3cb41614b | ||
|
|
cdef2e762f | ||
|
|
a7ee38ae9e | ||
|
|
ad67e4935a | ||
|
|
5371101ad0 | ||
|
|
d9cb9c99f0 | ||
|
|
978850ec9e | ||
|
|
1b5d81c4d4 | ||
|
|
12d6666b54 | ||
|
|
e83dc3495e | ||
|
|
4ade2cbd48 | ||
|
|
bf96705d5f | ||
|
|
de72fa2012 | ||
|
|
55de57ced6 | ||
|
|
70421e79b1 | ||
|
|
a538e86c0c | ||
|
|
95bffca5f0 | ||
|
|
e18ae5e335 | ||
|
|
00d2ad0649 | ||
|
|
9c7e6495e9 | ||
|
|
ed7965e257 | ||
|
|
0187f31520 | ||
|
|
a577051b09 | ||
|
|
abbbcdf659 | ||
|
|
9fa7f0810f | ||
|
|
770a7880c2 | ||
|
|
edfacf0c2f | ||
|
|
06bc04a7bf | ||
|
|
730c0992b5 | ||
|
|
8d6d4c37a4 | ||
|
|
bae0a46bab | ||
|
|
80caf331ec | ||
|
|
47316bcae7 | ||
|
|
b83eb85a25 | ||
|
|
596f62c57e | ||
|
|
9d9bec0af2 | ||
|
|
db26fb2d18 | ||
|
|
23c286c4ac | ||
|
|
0c7995d119 | ||
|
|
7aa28e042e | ||
|
|
c0ad8c5539 | ||
|
|
03d2260776 | ||
|
|
8d156ba5fb | ||
|
|
8acdb26842 | ||
|
|
bb487447ac | ||
|
|
156905d684 | ||
|
|
b212b036f9 | ||
|
|
4906f22479 | ||
|
|
35282e1d35 | ||
|
|
2bf693292b | ||
|
|
7c8b183513 | ||
|
|
d5cfe56c67 | ||
|
|
d74a19c887 | ||
|
|
09f5a84a13 | ||
|
|
93eea5dd6d | ||
|
|
d8393f97ca | ||
|
|
905a526b18 | ||
|
|
a8cee02558 | ||
|
|
08ed1f087a | ||
|
|
e591a0f8e8 | ||
|
|
7a7d1c54e7 | ||
|
|
74c3ed0cfb | ||
|
|
5d1a6c2fc0 | ||
|
|
fe65f5f1f4 | ||
|
|
b948f3b3dd | ||
|
|
aad42261cc | ||
|
|
bd21251659 | ||
|
|
5bbaeee149 | ||
|
|
206fa3f9ed | ||
|
|
6470a36e68 | ||
|
|
69754fa6b5 | ||
|
|
591c576307 | ||
|
|
8eabc189b7 | ||
|
|
bb98f6a40a | ||
|
|
9861d8c55c | ||
|
|
eb3d720556 | ||
|
|
17eb7320ac | ||
|
|
3d147b1f4e | ||
|
|
fb05c7e5a7 | ||
|
|
f55f4d4a4e | ||
|
|
0f0efbc13c | ||
|
|
99394d2a75 | ||
|
|
bac39e4a8d | ||
|
|
0523789c33 | ||
|
|
6d62983581 | ||
|
|
2e8cb67880 | ||
|
|
37a1a232b3 | ||
|
|
b92099f5bd | ||
|
|
15236c34e3 | ||
|
|
34b6e17a1b | ||
|
|
c374b357bb | ||
|
|
31818d677e | ||
|
|
c6f9287ce7 | ||
|
|
09b1216c54 | ||
|
|
f7f486c7d1 | ||
|
|
7df5d157af | ||
|
|
d7ff228b14 | ||
|
|
934b47f2d3 | ||
|
|
ec02c65af5 | ||
|
|
ff0304cac7 | ||
|
|
660740bee0 | ||
|
|
4068160a76 | ||
|
|
2a672faca1 | ||
|
|
d454aaf155 | ||
|
|
a154a9e99a | ||
|
|
2ed1c373e2 | ||
|
|
46fae17b7e | ||
|
|
fe9ae95bdc | ||
|
|
6a68ca5eef | ||
|
|
b5428adda6 | ||
|
|
05a20e3bc7 | ||
|
|
11093b7e27 | ||
|
|
6d57915496 | ||
|
|
55623c1dcb | ||
|
|
2e348288a1 | ||
|
|
fb5c67bf99 | ||
|
|
d63d2c93e5 | ||
|
|
abb2ada8b1 | ||
|
|
5dfb2c33e2 | ||
|
|
1c2f82362e | ||
|
|
73645f05f0 | ||
|
|
cda0c6c3d6 | ||
|
|
933d4b88ee | ||
|
|
26efdaf931 | ||
|
|
e6b075f52f | ||
|
|
a453ea1afa | ||
|
|
61fb890d01 | ||
|
|
d1e26eafaa | ||
|
|
7bb592cccf | ||
|
|
a24b9c4afc | ||
|
|
4c7ee1e65c | ||
|
|
f2addbb7b8 | ||
|
|
b5e57970fe | ||
|
|
eee3d3f757 | ||
|
|
835403af54 | ||
|
|
9200327f77 | ||
|
|
12f6a9aa86 | ||
|
|
fe340feb06 | ||
|
|
1e90076738 | ||
|
|
456b84616c | ||
|
|
ac666a9731 | ||
|
|
cca4ac9052 | ||
|
|
a700e23d90 | ||
|
|
eee8880930 | ||
|
|
004bfc43a4 | ||
|
|
318716afcd | ||
|
|
ca6b8d356e | ||
|
|
178b9370d0 | ||
|
|
e49baddffa | ||
|
|
a1c7aa1c7a | ||
|
|
973df580a1 | ||
|
|
91d5ee1d8f | ||
|
|
5116e28aac | ||
|
|
751ebc8c63 | ||
|
|
7f18e1426e | ||
|
|
a013b39b8f | ||
|
|
4e4c9f13a0 | ||
|
|
3ebd7bbdb7 | ||
|
|
7672e7740d | ||
|
|
d77d013b32 | ||
|
|
1db491eb49 | ||
|
|
4d70494474 | ||
|
|
954310ae2c | ||
|
|
f86c99c91a | ||
|
|
408ef28da2 | ||
|
|
151e49ef49 | ||
|
|
655a3c7d70 | ||
|
|
b52c3c7b52 | ||
|
|
38ea253d02 | ||
|
|
182e11f059 | ||
|
|
fe67ecd2b6 | ||
|
|
b628956310 | ||
|
|
0fdc15ccdf | ||
|
|
514be83b63 | ||
|
|
867a3ab4d2 | ||
|
|
888155ee08 | ||
|
|
540d3151b4 | ||
|
|
089f82205f | ||
|
|
9cd6b528c9 | ||
|
|
eff2fecf5a | ||
|
|
f944694b2e | ||
|
|
36f50af6e0 | ||
|
|
155398c445 | ||
|
|
73506d2051 | ||
|
|
477fdc7053 | ||
|
|
424bbbaf74 | ||
|
|
fdb8e8d137 | ||
|
|
c4b656eba6 | ||
|
|
b6e0e2fdba | ||
|
|
ba967a0834 | ||
|
|
587bb9a075 | ||
|
|
7c789afcb5 | ||
|
|
e922847115 | ||
|
|
407b0519cf | ||
|
|
8f686815b3 | ||
|
|
214bfbbfdf | ||
|
|
e14d689e0e | ||
|
|
c294a7f507 | ||
|
|
de7cf1c093 | ||
|
|
989fc155de | ||
|
|
69386027ac | ||
|
|
157173d000 | ||
|
|
f1e8c05fef | ||
|
|
bcf0c26224 | ||
|
|
48c67c131b | ||
|
|
7f147a2b89 | ||
|
|
dc7490eb46 | ||
|
|
14f8b20084 | ||
|
|
c816380a4f | ||
|
|
d4e2502c70 | ||
|
|
02f85d32a9 | ||
|
|
fb18f51b95 | ||
|
|
0cd1be1439 | ||
|
|
37589f99b2 | ||
|
|
4bc2bf3a6d | ||
|
|
1b79a1782c | ||
|
|
4aab8656d8 | ||
|
|
391276762a | ||
|
|
ce86541135 | ||
|
|
4fcc14041c | ||
|
|
0bcf0571d9 | ||
|
|
fffafb84bc | ||
|
|
7b1f194b8d | ||
|
|
a517a1a72a | ||
|
|
6d03230aaf | ||
|
|
83d0581370 | ||
|
|
89e7fe4039 | ||
|
|
e31294863e | ||
|
|
3368578316 | ||
|
|
5aab6da4f9 | ||
|
|
fecace0a77 | ||
|
|
c5ca8f31ea | ||
|
|
7ed1f2cbef | ||
|
|
40ee57534a | ||
|
|
843343c7ad | ||
|
|
0ac743e6aa | ||
|
|
268443edb5 | ||
|
|
b878ec25bb | ||
|
|
90d404334e | ||
|
|
7ab19565da | ||
|
|
7fc787bdcc | ||
|
|
1f76fb5246 | ||
|
|
4e20aee0d1 | ||
|
|
4df054c1ad | ||
|
|
e32593aa30 | ||
|
|
21d9690faf | ||
|
|
5da9c83588 | ||
|
|
8be887bff7 | ||
|
|
1ff2326755 | ||
|
|
fa0c9dfc7c | ||
|
|
16d3a84637 | ||
|
|
cec35782ef | ||
|
|
2e3c4ee1c9 | ||
|
|
af454d3f90 | ||
|
|
4eeef37495 | ||
|
|
afbfbab6a6 | ||
|
|
c8f1123ee9 | ||
|
|
44ddd520fb | ||
|
|
3ecc226d7a | ||
|
|
9a51189dbd | ||
|
|
94d3f187ad | ||
|
|
2ebd1cf5ea | ||
|
|
0c7d12f0da | ||
|
|
8f4bc95b2d | ||
|
|
e7354614c1 | ||
|
|
2a57338f06 | ||
|
|
f8bf2824c8 | ||
|
|
1c914621a4 | ||
|
|
c78e47a42a | ||
|
|
233319df89 | ||
|
|
669a8867bd | ||
|
|
11c2854a85 | ||
|
|
40b8e8b4ac | ||
|
|
229b1919d3 | ||
|
|
83ff73806c | ||
|
|
01e2e974b8 | ||
|
|
498e8038fc | ||
|
|
79222a45d1 | ||
|
|
6e3a64a1d0 | ||
|
|
37992017ea | ||
|
|
267a68c399 | ||
|
|
a46b78a353 | ||
|
|
e93dfe6de2 | ||
|
|
6031c27aa1 | ||
|
|
83ac67565b | ||
|
|
660f7d0dd7 | ||
|
|
6160fd2669 | ||
|
|
2c457d47f2 | ||
|
|
121d16dc87 | ||
|
|
6034738d48 | ||
|
|
740b724f07 | ||
|
|
9304aae28a | ||
|
|
a26a55876d | ||
|
|
b45620d85d | ||
|
|
9c86fb8df1 | ||
|
|
0d5d955ada | ||
|
|
cfd546b42b | ||
|
|
716890be3c | ||
|
|
55ca2c199e | ||
|
|
095ac686cc | ||
|
|
67e9cb5b05 | ||
|
|
8d3c78bd40 | ||
|
|
0675ecbbe4 | ||
|
|
392697d64b | ||
|
|
3f8a8c547e | ||
|
|
9f8601d5ac | ||
|
|
c29dfb01ed | ||
|
|
e3ea93f8de | ||
|
|
888d7c285b | ||
|
|
dbf1e25e15 | ||
|
|
d88138a8af | ||
|
|
7ec5ec80b8 | ||
|
|
071988e8b1 | ||
|
|
e820fb27b1 | ||
|
|
f4b329a796 |
+240
-45
@@ -1,6 +1,201 @@
|
||||
# v0.11.0 (2013-11-02)
|
||||
|
||||
* Enhancements
|
||||
* [Code] Eval now returns variables from other contexts
|
||||
* [Dict] Document and enforce all dicts use the match operator (`===`) when checking for keys
|
||||
* [Enum] Add `Enum.slice/2` with a range
|
||||
* [Enum] Document and enforce `Enum.member?/2` to use the match operator (`===`)
|
||||
* [IEx] Split `IEx.Evaluator` from `IEx.Server` to allow custom evaluators
|
||||
* [IEx] Add support for `IEx.pry` which halts a given process for inspection
|
||||
* [IO] Add specs and allow some IO APIs to receive any data that implements `String.Chars`
|
||||
* [Kernel] Improve stacktraces on command line interfaces
|
||||
* [Kernel] Sigils can now handle balanced tokens as in `%s(f(o)o)`
|
||||
* [Kernel] Emit warnings when an alias is not used
|
||||
* [Macro] Add `Macro.pipe/3` and `Macro.unpipe/1` for building pipelines
|
||||
* [Mix] Allow umbrella children to share dependencies between them
|
||||
* [Mix] Allow mix to be escriptize'd
|
||||
* [Mix] Speed mix projects compilation by relying on more manifests information
|
||||
* [Protocol] Protocols now provide `impl_for/1` and `impl_for!/1` functions which receive a structure and returns its respective implementation, otherwise returns nil or an error
|
||||
* [Set] Document and enforce all sets use the match operator (`===`) when checking for keys
|
||||
* [String] Update to Unicode 6.3.0
|
||||
* [String] Add `Enum.slice/2` with a range
|
||||
|
||||
* Bug fixes
|
||||
* [Exception] Ensure `defexception` fields can be set dynamically
|
||||
* [Kernel] Guarantee aliases hygiene is respected when the current module name is not known upfront
|
||||
* [Kernel] `Kernel.access/2` no longer flattens lists
|
||||
* [Mix] Ensure cyclic dependencies are properly handled
|
||||
* [String] Implement the extended grapheme cluster algorithm for `String` operations
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `pid_to_list/1`, `list_to_pid/1`, `binary_to_atom/2`, `binary_to_existing_atom/2` and `atom_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
|
||||
* [Kernel] `insert_elem/3` and `delete_elem/2` are deprecated in favor of `Tuple.insert_at/3` and `Tuple.delete_at/2`
|
||||
* [Kernel] Use of `in` inside matches (as in `x in [1,2,3] -> x`) is deprecated in favor of the guard syntax (`x when x in [1,2,3]`)
|
||||
* [Macro] `Macro.expand_all/2` is deprecated
|
||||
* [Protocol] `@only` and `@except` in protocols are now deprecated
|
||||
* [Protocol] Protocols no longer fallback to `Any` out of the box (this functionality needs to be explicitly enabled by setting `@fallback_to_any` to true)
|
||||
* [String] `String.to_integer/1` and `String.to_float/1` are deprecated in favor of `Integer.parse/1` and `Float.parse/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [CLI] Reading `.elixirrc` has been dropped in favor of setting env vars
|
||||
* [Kernel] `Kernel.access/2` now expects the second argument to be a compile time list
|
||||
* [Kernel] `fn -> end` quoted expression is no longer wrapped in a `do` keyword
|
||||
* [Kernel] Quoted variables from the same module must be explicitly shared. Previously, if a function returned `quote do: a = 1`, another function from the same module could access it as `quote do: a`. This has been fixed and the variables must be explicitly shared with `var!(a, __MODULE__)`
|
||||
* [Mix] Umbrella apps now treat children apps as dependencies. This means all dependencies will be checked out in the umbrela `deps` directory. On upgrade, child apps need to point to the umbrella project by setting `deps_path: "../../deps_path", lockfile: "../../mix.lock"` in their project config
|
||||
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
|
||||
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
|
||||
|
||||
# v0.10.3 (2013-10-02)
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Add `Enum.take_every/2`
|
||||
* [IEx] IEx now respects signals sent from the Ctrl+G menu
|
||||
* [Kernel] Allow documentation for types with `@typedoc`
|
||||
* [Mix] Allow apps to be selected in umbrella projects
|
||||
* [Record] Generated record functions `new` and `update` also take options with strings as keys
|
||||
* [Stream] Add `Stream.unfold/1`
|
||||
|
||||
* Bug fixes
|
||||
* [Dict] Fix a bug when a HashDict was marked as equal when one was actually a subset of the other
|
||||
* [EEx] Solve issue where `do` blocks inside templates were not properly aligned
|
||||
* [ExUnit] Improve checks and have better error reports on poorly aligned doctests
|
||||
* [Kernel] Fix handling of multiple heredocs on the same line
|
||||
* [Kernel] Provide better error messages for match, guard and quoting errors
|
||||
* [Kernel] Make `Kernel.raise/2` a macro to avoid messing up stacktraces
|
||||
* [Kernel] Ensure `&()` works on quoted blocks with only one expression
|
||||
* [Mix] Address an issue where a dependency was not compiled in the proper order when specified in different projects
|
||||
* [Mix] Ensure `compile: false` is a valid mechanism for disabling the compilation of dependencies
|
||||
* [Regex] Fix bug on `Regex.scan/3` when capturing groups and the regex has no groups
|
||||
* [String] Fix a bug with `String.split/2` when given an empty pattern
|
||||
* [Typespec] Guarantee typespecs error reports point to the proper line
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] The previous partial application syntax (without the `&` operator) has now been deprecated
|
||||
* [Regex] `Regex.captures/3` is deprecated in favor of `Regex.named_captures/3`
|
||||
* [String] `String.valid_codepoint?/1` is deprecated in favor of pattern matching with `<<_ :: utf8 >>`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [IEx] The `r/0` helper has been removed as it caused surprising behaviour when many modules with dependencies were accumulated
|
||||
* [Mix] `Mix.Version` was renamed to `Version`
|
||||
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
|
||||
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
|
||||
|
||||
# v0.10.2 (2013-09-03)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
|
||||
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
|
||||
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
|
||||
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
|
||||
* [IEx] Allow for strings in `h` helper
|
||||
* [IEx] Helpers `r` and `c` can handle erlang sources
|
||||
* [Integer] Add `odd?/1` and `even?/1`
|
||||
* [IO] Added support to specifying a number of bytes to stream to `IO.stream`, `IO.binstream`, `File.stream!` and `File.binstream!`
|
||||
* [Kernel] Include file and line on error report for overriding an existing function/macro
|
||||
* [Kernel] Convert external functions into quoted expressions. This allows record fields to contain functions as long as they point to an `&Mod.fun/arity`
|
||||
* [Kernel] Allow `foo?` and `bar!` as valid variable names
|
||||
* [List] Add `List.replace_at/3`
|
||||
* [Macro] Improve printing of the access protocol on `Macro.to_string/1`
|
||||
* [Macro] Add `Macro.to_string/2` to support annotations on the converted string
|
||||
* [Mix] Automatically recompile a project if the Elixir version changes
|
||||
* [Path] Add `Path.relative_to_cwd/2`
|
||||
* [Regex] Allow erlang `re` options when compiling Elixir regexes
|
||||
* [Stream] Add `Stream.concat/1`, `Stream.concat/2` and `Stream.flat_map/2`
|
||||
* [String] Add regex pattern support to `String.replace/3`
|
||||
* [String] Add `String.ljust/2`, `String.rjust/2`, `String.ljust/3` and `String.rjust/3`
|
||||
* [URI] `URI.parse/1` supports IPv6 addresses
|
||||
|
||||
* Bug fixes
|
||||
* [Behaviour] Do not compile behaviour docs if docs are disabled on compilation
|
||||
* [ExUnit] Doctests no longer eat too much space and provides detailed reports for poorly indented lines
|
||||
* [File] Fix a bug where `File.touch(file, datetime)` was not setting the proper datetime when the file did not exist
|
||||
* [Kernel] Limit `inspect` results to 50 items by default to avoid printing too much data
|
||||
* [Kernel] Return a readable error on oversized atoms
|
||||
* [Kernel] Allow functions ending with `?` or `!` to be captured
|
||||
* [Kernel] Fix default shutdown of child supervisors to `:infinity`
|
||||
* [Kernel] Fix regression when calling a function/macro ending with bang, followed by `do/end` blocks
|
||||
* [List] Fix bug on `List.insert_at/3` that added the item at the wrong position for negative indexes
|
||||
* [Macro] `Macro.escape/2` can now escape improper lists
|
||||
* [Mix] Fix `Mix.Version` matching on pre-release info
|
||||
* [Mix] Ensure `watch_exts` trigger full recompilation on change with `mix compile`
|
||||
* [Mix] Fix regression on `mix clean --all`
|
||||
* [String] `String.strip/2` now supports removing unicode characters
|
||||
* [String] `String.slice/3` still returns the proper result when there is no length to be extracted
|
||||
* [System] `System.get_env/0` now returns a list of tuples as previously advertised
|
||||
|
||||
* Deprecations
|
||||
* [Dict] `Dict.update/3` is deprecated in favor of `Dict.update!/3`
|
||||
* [Enum] `Enum.min/2` and `Enum.max/2` are deprecated in favor of `Enum.min_by/2` and `Enum.max_by/2`
|
||||
* [Enum] `Enum.join/2` and `Enum.map_join/3` with a char list are deprecated
|
||||
* [IO] `IO.stream(device)` and `IO.binstream(device)` are deprecated in favor of `IO.stream(device, :line)` and `IO.binstream(device, :line)`
|
||||
* [Kernel] `list_to_binary/1`, `binary_to_list/1` and `binary_to_list/3` are deprecated in favor of `String.from_char_list!/1` and `String.to_char_list!/1` for characters and `:binary.list_to_bin/1`, `:binary.bin_to_list/1` and `:binary.bin_to_list/3` for bytes
|
||||
* [Kernel] `to_binary/1` is deprecated in favor of `to_string/1`
|
||||
* [Kernel] Deprecate `def/4` and friends in favor of `def/2` with unquote and friends
|
||||
* [Kernel] Deprecate `%b` and `%B` in favor of `%s` and `%S`
|
||||
* [List] `List.concat/2` is deprecated in favor of `Enum.concat/2`
|
||||
* [Macro] `Macro.unescape_binary/1` and `Macro.unescape_binary/2` are deprecated in favor of `Macro.unescape_string/1` and `Macro.unescape_string/2`
|
||||
* [Mix] `:umbrella` option for umbrella paths has been deprecated in favor of `:in_umbrella`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [IO] IO functions now only accept iolists as arguments
|
||||
* [Kernel] `Binary.Chars` was renamed to `String.Chars`
|
||||
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
|
||||
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
|
||||
|
||||
# v0.10.1 (2013-08-03)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add support for `defmacrocallback/1`
|
||||
* [Enum] Add `Enum.shuffle/1`
|
||||
* [ExUnit] The `:trace` option now also reports run time for each test
|
||||
* [ExUnit] Add support for `:color` to enable/disable ANSI coloring
|
||||
* [IEx] Add the `clear` helper to clear the screen.
|
||||
* [Kernel] Add the capture operator `&`
|
||||
* [Kernel] Add support for `GenFSM.Behaviour`
|
||||
* [Kernel] Functions now points to the module and function they were defined when inspected
|
||||
* [Kernel] A documentation attached to a function that is never defined now prints warnings
|
||||
* [List] Add `List.keysort/2`
|
||||
* [Mix] `:test_helper` project configuration did not affect `mix test` and was therefore removed. A `test/test_helper.exs` file is still necessary albeit it doesn't need to be automatically required in each test file
|
||||
* [Mix] Add manifests for yecc, leex and Erlang compilers, making it easier to detect dependencies in between compilers and providing a more useful clean behaviour
|
||||
* [Mix] `mix help` now outputs information about the default mix task
|
||||
* [Mix] Add `--no-deps-check` option to `mix run`, `mix compile` and friends to not check dependency status
|
||||
* [Mix] Add support for `MIX_GIT_FORCE_HTTPS` system environment that forces HTTPS for known providers, useful when the regular git port is blocked. This configuration does not affect the `mix.lock` results
|
||||
* [Mix] Allow coverage tool to be pluggable via the `:test_coverage` configuration
|
||||
* [Mix] Add `mix cmd` as a convenience to run a command recursively in child apps in an umbrella application
|
||||
* [Mix] Support `umbrella: true` in dependencies as a convenience for setting up umbrella path deps
|
||||
* [Mix] `mix run` now behaves closer to the `elixir` command and properly mangles the ARGV
|
||||
* [String] Add `Regex.scan/3` now supports capturing groups
|
||||
* [String] Add `String.reverse/1`
|
||||
|
||||
* Bug fixes
|
||||
* [Behaviour] Ensure callbacks are stored in the definition order
|
||||
* [CLI] Speed up boot time on Elixir .bat files
|
||||
* [IEx] Reduce cases where IEx parser can get stuck
|
||||
* [Kernel] Improve error messages when the use of an operator has no effect
|
||||
* [Kernel] Fix a bug where warnings were not being generated when imported macros conflicted with local functions or macros
|
||||
* [Kernel] Document that `on_definition` can only be a function as it is evaluated inside the function context
|
||||
* [Kernel] Ensure `%w` sigils with no interpolation are fully expanded at compile time
|
||||
* [Mix] `mix deps.update`, `mix deps.clean` and `mix deps.unlock` no longer change all dependencies unless `--all` is given
|
||||
* [Mix] Always run ` mix loadpaths` on `mix app.start`, even if `--no-compile` is given
|
||||
* [OptionParser] Do not add boolean flags to the end result if they were not given
|
||||
* [OptionParser] Do not parse non-boolean flags as booleans when true or false are given
|
||||
* [OptionParser] Ensure `:keep` and `:integer`|`:float` can be given together as options
|
||||
* [OptionParser] Ensure `--no-flag` sets `:flag` to false when `:flag` is a registered boolean switch
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `function(Mod.fun/arity)` and `function(fun/arity)` are deprecated in favor of `&Mod.fun/arity` and `&fun/arity`
|
||||
* [Kernel] `function/3` is deprecated in favor of `Module.function/3`
|
||||
* [Kernel] `Kernel.ParallelCompiler` now receives a set of callbacks instead of a single one
|
||||
* [Mix] `:test_coverage` option now expect keywords arguments and the `--cover` flag is now treated as a boolean
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
|
||||
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
|
||||
|
||||
# v0.10.0 (2013-07-15)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
|
||||
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
|
||||
* [Kernel] Add pretty printing support for inspect
|
||||
@@ -13,7 +208,7 @@
|
||||
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
|
||||
* [Typespec] Improve error messages to contain file, line and the typespec itself
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [CLI] Elixir can now run on Unix directories with `:` in its path
|
||||
* [Kernel] `match?/2` does not leak variables to outer scope
|
||||
* [Kernel] Keep `head|tail` format when splicing at the tail
|
||||
@@ -22,7 +217,7 @@
|
||||
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
|
||||
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
|
||||
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
|
||||
@@ -31,7 +226,7 @@
|
||||
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
|
||||
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
|
||||
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
|
||||
|
||||
@@ -44,7 +239,7 @@
|
||||
|
||||
# v0.9.3 (2013-06-23)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
|
||||
* [Kernel] Print warnings to stderr
|
||||
@@ -58,7 +253,7 @@
|
||||
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
|
||||
* [Record] Allow types to be given to `defrecordp`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
|
||||
* [Kernel] Improve error messages on default clauses clash
|
||||
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
|
||||
@@ -67,12 +262,12 @@
|
||||
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
|
||||
* [Mix] Mix now properly evaluates rebar scripts
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
|
||||
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
|
||||
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
@@ -82,7 +277,7 @@
|
||||
|
||||
# v0.9.2 (2013-06-13)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
* [Mix] Automatically import git dependencies from Rebar
|
||||
* [Mix] Support for dependencies directly from the umbrella application
|
||||
@@ -90,7 +285,7 @@
|
||||
* [String] Add `String.contains?`
|
||||
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [HashDict] Ensure HashDict uses exact match throughout its implementation
|
||||
* [IEx] Do not interpret ANSI codes in IEx results
|
||||
* [IEx] Ensure `--cookie` is set before accessing remote shell
|
||||
@@ -98,21 +293,21 @@
|
||||
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
|
||||
* [Mix] Start the application by default on escripts
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
|
||||
* [Kernel] `super` no longer supports implicit arguments
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
* [Kernel] Add `String.start_with?` and `String.end_with?`
|
||||
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
|
||||
* [IEx] Enable shell customization via the `IEx.Options` module
|
||||
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
|
||||
@@ -123,12 +318,12 @@
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
* [IEx] Add support for .iex files that are loaded during shell's boot process
|
||||
* [IEx] Add `import_file/1` helper
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
@@ -136,7 +331,7 @@
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
|
||||
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
|
||||
@@ -159,7 +354,7 @@
|
||||
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
|
||||
* [String] Add `String.valid?` and `String.valid_character?`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [ExUnit] Handle exit messages from in ExUnit
|
||||
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
|
||||
* [Kernel] Ensure we don't splice keyword args unecessarily
|
||||
@@ -171,7 +366,7 @@
|
||||
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
|
||||
* [URI] Downcase host and scheme and URIs
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
|
||||
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
|
||||
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
|
||||
@@ -180,7 +375,7 @@
|
||||
* [Kernel] `var_context` in quote was deprecated in favor of `context`
|
||||
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Dict] `List.Dict` was moved to `ListDict`
|
||||
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
@@ -188,7 +383,7 @@
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
* [ExUnit] Include suite run time on CLI results
|
||||
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
|
||||
@@ -201,7 +396,7 @@
|
||||
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
|
||||
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Binary] inspect no longer escapes standalone hash `#`
|
||||
* [IEx] The `h` helper can now retrieve docs for special forms
|
||||
* [Kernel] Record optimizations were not being triggered in functions inside the record module
|
||||
@@ -209,11 +404,11 @@
|
||||
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
|
||||
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
|
||||
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
|
||||
|
||||
quote do
|
||||
@@ -228,7 +423,7 @@
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
|
||||
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
|
||||
@@ -241,13 +436,13 @@
|
||||
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
|
||||
* [Typespec] Better error reporting for invalid typespecs
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Mix] Allow Mix projects to be generated with just one letter
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
|
||||
* [Kernel] Tidy up and clean `quote` API
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
@@ -255,7 +450,7 @@
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
|
||||
* [Binary] Support `\a` escape character in binaries
|
||||
* [Binary] Support syntax shortcut for specifying size in bit syntax
|
||||
@@ -278,16 +473,16 @@
|
||||
* [String] Add `String.capitalize` and `String.slice`
|
||||
* [System] Add `System.tmp_dir`, `System.cwd` and `System.user_home`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Kernel] `import` with `only` accepts functions starting with underscore
|
||||
* [String] `String.first` and `String.last` return nil for empty binaries
|
||||
* [String] `String.rstrip` and `String.lstrip` now verify if argument is a binary
|
||||
* [Typespec] Support `...` inside typespec's lists
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
|
||||
* [File] Deprecate path related functions in favor of the module `Path`
|
||||
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
|
||||
@@ -297,7 +492,7 @@
|
||||
|
||||
# v0.7.2 (2012-12-04)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [CLI] `--debug-info` is now true by default
|
||||
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
|
||||
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
|
||||
@@ -310,12 +505,12 @@
|
||||
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
|
||||
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
|
||||
* [IEx] IEx will now wait for all command line options to be processed before starting
|
||||
* [Kernel] Ensure proper stacktraces when showing deprecations
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
|
||||
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
@@ -323,7 +518,7 @@
|
||||
|
||||
# v0.7.1 (2012-11-18)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [IEx] Only show documented functions and also show docs for default generated functions
|
||||
* [IO] Add `IO.binread`, `IO.binwrite` and `IO.binreadline` to handle raw binary file operations
|
||||
* [ExUnit] Add support for user configuration at `HOME/.ex_unit.exs`
|
||||
@@ -341,20 +536,20 @@
|
||||
* [Typespec] Define a default type `t` for protocols and records
|
||||
* [Typespec] Add support for the access protocol in typespecs
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Kernel] Fix an issue where variables inside clauses remained unassigned
|
||||
* [Kernel] Ensure `defoverridable` functions can be referred in many clauses
|
||||
* [Kernel] Allow keywords as function names when following a dot (useful when integrating with erlang libraries)
|
||||
* [File] File is opened by default on binary mode instead of utf-8
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
|
||||
* [Enum] `Enum.times` is deprecated in favor of using ranges
|
||||
* [System] `halt` moved to `System` module
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
|
||||
* [Binary] Add a Dict binary that converts its keys to binaries on insertion
|
||||
* [Binary] Optimize `Binary.Inspect` and improve inspect for floats
|
||||
@@ -380,7 +575,7 @@
|
||||
* [Regex] Add `:return` option to `Regex.run` and `Regex.scan`
|
||||
* [String] Add a String module responsible for handling UTf-8 binaries
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [File] `File.cp` and `File.cp_r` now preserves the file's mode
|
||||
* [IEx] Fix a bug where printing to `:stdio` on `IEx` was causing it to hang
|
||||
* [Macro] Fix a bug where quoted expressions were not behaving the same as their non-quoted counterparts
|
||||
@@ -388,12 +583,12 @@
|
||||
* [Mix] Mix now exits with status 1 in case of failures
|
||||
* [Protocol] Avoid false positives on protocol dispatch (a bug caused the dispatch to be triggered to an invalid protocol)
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] `setup` and `teardown` callbacks now receives the test name as second argument
|
||||
* [Kernel] Raw function definition with `def/4`, `defp/4`, `defmacro/4`, `defmacrop/4` now evaluates all arguments. The previous behaviour was accidental and did not properly evaluate all arguments
|
||||
* [Kernel] Change tuple-related (`elem` and `setelem`), Enum functions (`find_index`, `nth!` and `times`) and List functions (List.key*) to zero-index
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Code] `Code.require_file` and `Code.load_file` now expect the full name as argument
|
||||
* [Enum] `List.reverse/1` and `List.zip/2` were moved to `Enum`
|
||||
* [GenServer] Rename `GenServer.Behavior` to `GenServer.Behaviour`
|
||||
@@ -403,14 +598,14 @@
|
||||
|
||||
# v0.6.0 (2012-08-01)
|
||||
|
||||
* incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
|
||||
* [Kernel] loop and recur are removed in favor of recursion with named functions
|
||||
* [Module] Removed data functions in favor of unifying the attributes API
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
|
||||
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
|
||||
* [File] `File.read_info` is deprecated in favor of `File.stat`
|
||||
@@ -422,7 +617,7 @@
|
||||
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
|
||||
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [Enum] Enhance Enum protocol to support `Enum.count`
|
||||
* [Enum] Optimize functions when a list is given as collection
|
||||
* [Enum] Add `find_index`, `nth!` and others
|
||||
|
||||
+78
-11
@@ -5,13 +5,18 @@ process easy and effective for everyone involved!
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The issue tracker is the preferred channel for [bug reports](#bugs-reports),
|
||||
[features requests](#feature-requests) and [submitting pull
|
||||
requests](#pull-requests), but please **do not** use the issue tracker for
|
||||
personal support requests. Instead, use
|
||||
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
|
||||
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
|
||||
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
|
||||
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:
|
||||
|
||||
* [the elixir-talk mailing list](http://groups.google.com/group/elixir-lang-talk)
|
||||
* [Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir)
|
||||
* [#elixir-lang](irc://chat.freenode.net/elixir-lang)
|
||||
|
||||
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
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
@@ -60,10 +65,10 @@ Example:
|
||||
|
||||
## Feature requests
|
||||
|
||||
Feature requests are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature. Please
|
||||
provide as much detail and context as possible.
|
||||
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
|
||||
out whether your idea fits with the scope and aims of the project. It's up to *you*
|
||||
to make a strong case to convince the project's developers of the merits of this
|
||||
feature. Please provide as much detail and context as possible.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -86,9 +91,71 @@ You can run all tests in the root directory with `make test` and you can
|
||||
also run tests for a specific framework `make test_#{NAME}`, for example,
|
||||
`make test_ex_unit`.
|
||||
|
||||
From time to time, your tests may fail in an existing Elixir checkout and
|
||||
may require a clean start by running `make clean compile`. You can always
|
||||
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
|
||||
the first paragraph is considered to be a short summary.
|
||||
|
||||
For functions, macros and callbacks say what it will do. For example write
|
||||
something like:
|
||||
|
||||
```elixir
|
||||
@doc """
|
||||
Return only those elements for which `fun` is true.
|
||||
|
||||
...
|
||||
"""
|
||||
def filter(collection, fun) ...
|
||||
```
|
||||
|
||||
For modules, records, protocols and types say what it is. For example write
|
||||
something like:
|
||||
|
||||
```elixir
|
||||
defrecord File.Stat, [...] do
|
||||
@moduledoc """
|
||||
Information about a file.
|
||||
|
||||
...
|
||||
"""
|
||||
end
|
||||
```
|
||||
|
||||
Keep in mind that the first paragraph might show up in a summary somewhere, long
|
||||
texts in the first paragraph create very ugly summaries. As a rule of thumb
|
||||
anything longer than 80 characters is too long.
|
||||
|
||||
Try to keep unneccesary details out of the first paragraph, it's only there to
|
||||
give a user a quick idea of what the documented "thing" does/is. The rest of the
|
||||
documentation string can contain the details, for example when a value and when
|
||||
`nil` is returned.
|
||||
|
||||
If possible include examples, preferably in a form that works with doctests. For
|
||||
example:
|
||||
|
||||
```elixir
|
||||
@doc """
|
||||
Return only those elements for which `fun` is true.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
[2]
|
||||
|
||||
"""
|
||||
def filter(collection, fun) ...
|
||||
```
|
||||
|
||||
This makes it easy to test the examples so that they don't go stale and examples
|
||||
are often a great help in explaining what a function does.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic
|
||||
|
||||
@@ -5,6 +5,4 @@ All the files in this distribution are covered under either Elixir's
|
||||
license (see the file LICENSE) except the files mentioned below that
|
||||
contains sections that are under Erlang's License (EPL):
|
||||
|
||||
lib/elixir/src/elixir_glob.erl
|
||||
lib/elixir/src/elixir_parser.erl (generated by build scripts)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
REBAR := "$(shell echo `pwd`/rebar)"
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
@@ -11,7 +11,6 @@ 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 release_erl
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
@@ -20,11 +19,12 @@ INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ rm -rf lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@@ -66,10 +66,10 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
|
||||
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/GraphemeBreakProperty.txt
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin --no-debug-info;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -90,16 +90,14 @@ install: compile
|
||||
done
|
||||
|
||||
clean:
|
||||
$(Q) cd lib/elixir && $(REBAR) clean
|
||||
cd lib/elixir && $(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/*/test/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/*/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/test/fixtures/deps_on_git_repo
|
||||
rm -rf lib/mix/test/fixtures/git_rebar
|
||||
rm -rf lib/elixir/src/elixir.app.src
|
||||
rm -rf lib/elixir/src/*_lexer.erl
|
||||
rm -rf lib/elixir/src/*_parser.erl
|
||||
rm -rf lib/elixir/test/ebin
|
||||
|
||||
clean_exbeam:
|
||||
$(Q) rm -f lib/*/ebin/Elixir.*.beam
|
||||
@@ -124,7 +122,7 @@ release_zip: compile
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../elixir-lang.github.com && git checkout master
|
||||
cd ../elixir-lang.github.com
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv docs ../elixir-lang.github.com/docs/master
|
||||
|
||||
@@ -141,7 +139,7 @@ test_erlang: compile
|
||||
$(Q) mkdir -p lib/elixir/test/ebin
|
||||
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
|
||||
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
@ echo ""
|
||||
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
@@ -155,12 +153,12 @@ test_kernel: compile
|
||||
|
||||
.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 syntax_tools inets crypto ssl
|
||||
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
|
||||
|
||||
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/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
$(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
|
||||
@ echo "==> Dialyzing Elixir..."
|
||||
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
||||
|
||||
@@ -10,11 +10,13 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
|
||||
$ git clone https://github.com/elixir-lang/elixir.git
|
||||
$ cd elixir
|
||||
$ make test
|
||||
$ make clean test
|
||||
|
||||
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
|
||||
|
||||
+6
-4
@@ -8,9 +8,9 @@ This document simply outlines the release process:
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
|
||||
4) Commit changes above and update stable branch
|
||||
4) Commit changes above with title "Release vVERSION"
|
||||
|
||||
5) Create tag from master branch
|
||||
5) Push master and create tag from master branch
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
@@ -18,9 +18,11 @@ This document simply outlines the release process:
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) After release, bump versions and add `-dev` back
|
||||
9) Merge master into stable branch and push it
|
||||
|
||||
10) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
10) After release, bump versions and add `-dev` back
|
||||
|
||||
11) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+1
-3
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version and exit
|
||||
@@ -73,8 +73,6 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
|
||||
+32
-63
@@ -1,5 +1,4 @@
|
||||
@echo off
|
||||
SETLOCAL enabledelayedexpansion
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc%== 0 (
|
||||
@@ -13,7 +12,7 @@ echo.
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -pr file Requires the given files/patterns in parallel (*)
|
||||
echo -pa path Prepends the given path to Erlang code path (*)
|
||||
echo -pz path Appends the given path to Erlang code path (*)
|
||||
@@ -51,69 +50,39 @@ set par="%1"
|
||||
shift
|
||||
if "%par%"=="" (
|
||||
rem if no parameters defined
|
||||
goto :run
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
if "%par%"=="""" (
|
||||
rem if no parameters defined - special case for parameter that is already quoted
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
|
||||
set parsErlang=%parsErlang% -detached
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
|
||||
set parsErlang=%parsErlang% -hidden
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
|
||||
set parsErlang=%parsErlang% -setcookie %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
|
||||
set parsErlang=%parsErlang% -sname %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
|
||||
set parsErlang=%parsErlang% -name %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
|
||||
set beforeExtra=%beforeExtra% %~1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
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)
|
||||
rem ******* elixir parameters **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--app`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
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)
|
||||
goto:startloop
|
||||
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
:expand_erl_libs
|
||||
rem ******* expand all ebin paths as Windows does not support the ..\*\ebin wildcard ********************
|
||||
SETLOCAL enabledelayedexpansion
|
||||
set ext_libs=
|
||||
for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
set ext_libs=!ext_libs! -pa "%%~fd\ebin"
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
if [ $# -eq 0 ]; then
|
||||
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-o The directory to output compiled files
|
||||
@@ -7,6 +7,7 @@ if [ $# -eq 0 ]; then
|
||||
--no-debug-info Do not attach debug info to compiled modules
|
||||
--ignore-module-conflict
|
||||
--warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
--verbose Print informational messages.
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
|
||||
@@ -14,6 +14,7 @@ echo --no-docs Do not attach documentation to compiled modules
|
||||
echo --no-debug-info Do not attach debug info to compiled modules
|
||||
echo --ignore-module-conflict
|
||||
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
echo --verbose Print informational messages.
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version
|
||||
|
||||
+19
-21
@@ -1,7 +1,7 @@
|
||||
defexception EEx.SyntaxError, message: nil
|
||||
|
||||
defmodule EEx do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way:
|
||||
|
||||
@@ -93,11 +93,15 @@ defmodule EEx do
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args // [], options // []) do
|
||||
info = [file: __CALLER__.file, line: __CALLER__.line + 1]
|
||||
quote do
|
||||
info = Keyword.merge unquote(info), unquote(options)
|
||||
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
|
||||
unquote(args), EEx.compile_string(unquote(source), info), info)
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
|
||||
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
|
||||
compiled = EEx.compile_string(source, info)
|
||||
|
||||
case kind do
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -124,14 +128,17 @@ defmodule EEx do
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_file(kind, name, filename, args // [], options // []) do
|
||||
quote do
|
||||
file = unquote(filename)
|
||||
info = Keyword.merge unquote(options), [file: file, line: 1]
|
||||
defmacro function_from_file(kind, name, file, args // [], options // []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge options, [file: file, line: 1]
|
||||
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@file file
|
||||
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
|
||||
unquote(args), EEx.compile_file(file, info), info)
|
||||
case kind do
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
:defp -> defp(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -187,15 +194,6 @@ defmodule EEx do
|
||||
|
||||
### Helpers
|
||||
|
||||
@doc false
|
||||
def function_from_quoted(module, kind, name, args, source, info) do
|
||||
args = Enum.map args, fn arg -> { arg, [], nil } end
|
||||
quote = quote do
|
||||
unquote(kind)(unquote(name)(unquote_splicing(args)), do: unquote(source))
|
||||
end
|
||||
Module.eval_quoted module, quote, [], info
|
||||
end
|
||||
|
||||
defp do_eval(compiled, bindings, options) do
|
||||
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
|
||||
result
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule EEx.Compiler do
|
||||
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
|
||||
buffer = state.engine.handle_expr(buffer, mark, contents)
|
||||
generate_buffer(t, buffer, scope, state.dict([]))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
|
||||
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
|
||||
else
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict([{key, buffer}|&1]) }
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,7 +108,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp insert_quotes(list, dict) when is_list(list) do
|
||||
Enum.map list, insert_quotes(&1, dict)
|
||||
Enum.map list, &insert_quotes(&1, dict)
|
||||
end
|
||||
|
||||
defp insert_quotes(other, _dict) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
An engine needs to implement two functions:
|
||||
|
||||
@@ -20,6 +20,11 @@ defmodule EEx.Engine do
|
||||
implemented by default by this engine.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc """
|
||||
The default implementation simply concatenates text to the buffer.
|
||||
"""
|
||||
@@ -35,22 +40,18 @@ defmodule EEx.Engine do
|
||||
|
||||
All other markers are not implemented by this engine.
|
||||
"""
|
||||
def handle_expr(buffer, '=', expr) do
|
||||
def handle_expr(buffer, "=", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
tmp <> to_binary(unquote(expr))
|
||||
tmp <> to_string(unquote(expr))
|
||||
end
|
||||
end
|
||||
|
||||
def handle_expr(buffer, '', expr) do
|
||||
def handle_expr(buffer, "", expr) do
|
||||
quote do
|
||||
tmp = unquote(buffer)
|
||||
unquote(expr)
|
||||
tmp
|
||||
end
|
||||
end
|
||||
|
||||
def behaviour_info(:callbacks) do
|
||||
[handle_text: 2, handle_expr: 3]
|
||||
end
|
||||
end
|
||||
@@ -67,6 +67,21 @@ defmodule EEx.AssignsEngine do
|
||||
a template, after compiled, may receive different assigns
|
||||
and the developer don't want to recompile it for each
|
||||
variable set.
|
||||
|
||||
Assigns can also be used when compiled to a function:
|
||||
|
||||
# sample.eex
|
||||
<%= @a + @b %>
|
||||
|
||||
# sample.ex
|
||||
defmodule Sample do
|
||||
require EEx
|
||||
EEx.function_from_file :def, :sample, "sample.eex", [:assigns]
|
||||
end
|
||||
|
||||
# iex
|
||||
Sample.sample(a: 1, b: 2) #=> "3"
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
@@ -91,7 +106,7 @@ defmodule EEx.SmartEngine do
|
||||
|
||||
@moduledoc """
|
||||
An engine meant for end-user usage that includes
|
||||
`AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(:unicode.characters_to_list(bin), line)
|
||||
tokenize(String.to_char_list!(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
@@ -54,11 +54,11 @@ defmodule EEx.Tokenizer do
|
||||
# Retrieve marker for <%
|
||||
|
||||
defp retrieve_marker('=' ++ t) do
|
||||
{ '=', t }
|
||||
{ "=", t }
|
||||
end
|
||||
|
||||
defp retrieve_marker(t) do
|
||||
{ '', t }
|
||||
{ "", t }
|
||||
end
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
@@ -103,7 +103,7 @@ defmodule EEx.Tokenizer do
|
||||
# token and, if so, it is not followed by an "end"
|
||||
# token. If this is the case, we are on a start expr.
|
||||
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
|
||||
{ :ok, tokens } ->
|
||||
{ :ok, _line, tokens } ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
@@ -128,15 +128,15 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp fn_index(tokens) do
|
||||
Enum.find_index(tokens, function do
|
||||
Enum.find_index tokens, fn
|
||||
{ :fn_paren, _ } -> true
|
||||
{ :fn, _ } -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp end_index(tokens) do
|
||||
Enum.find_index(tokens, match?({ :end, _ }, &1)) || :infinity
|
||||
Enum.find_index(tokens, &match?({ :end, _ }, &1)) || :infinity
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
|
||||
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,15 +13,15 @@ defmodule EEx.TokenizerTest do
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, [], ' bar ' } ]
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, '=', ' bar ' } ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, '=', ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -34,9 +34,9 @@ baz %>
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
{:text, 1, "foo "},
|
||||
{:expr, 1, '=', ' bar\n\nbaz '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, 3, "\n"},
|
||||
{:expr, 4, [], ' foo '},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, 4, "\n"}
|
||||
]
|
||||
end
|
||||
@@ -68,32 +68,32 @@ baz %>
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, '', ' if true do ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :end_expr, 1, '', ' end ' }
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, '', ' cond do ' },
|
||||
{ :middle_expr, 1, '', ' false -> ' },
|
||||
{ :start_expr, 1, "", ' cond do ' },
|
||||
{ :middle_expr, 1, "", ' false -> ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :middle_expr, 1, '', ' true -> ' },
|
||||
{ :middle_expr, 1, "", ' true -> ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :end_expr, 1, '', ' end ' }
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, '', ' if true do ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :middle_expr, 1, '', ' else ' },
|
||||
{ :middle_expr, 1, "", ' else ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :end_expr, 1, '', ' end ' }
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
+17
-17
@@ -25,12 +25,6 @@ defmodule EExText.Compiled do
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
end
|
||||
|
||||
@file __ENV__
|
||||
def other do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
end
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
try do
|
||||
:erlang.error "failed"
|
||||
@@ -270,6 +264,21 @@ foo
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "evaluates nested do expressions" do
|
||||
string = """
|
||||
<% y = ["a", "b", "c"] %>
|
||||
<%= cond do %>
|
||||
<% "a" in y -> %>
|
||||
Good
|
||||
<% true -> %>
|
||||
<% if true do %>true<% else %>false<% end %>
|
||||
Bad
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "unicode" do
|
||||
template = """
|
||||
• <%= "•" %> •
|
||||
@@ -312,7 +321,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: binary_to_list(__FILE__), line: 7]
|
||||
[file: to_char_list(__FILE__), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +330,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: binary_to_list(__FILE__), line: 18]
|
||||
[file: to_char_list(__FILE__), line: 18]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,15 +342,6 @@ foo
|
||||
[file: 'unknown', line: 24]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.other ==
|
||||
{ 31,
|
||||
{ EExText.Compiled,
|
||||
:other,
|
||||
0,
|
||||
[file: binary_to_list(__FILE__), line: 30]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
|
||||
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
|
||||
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
|
||||
-define(line(Opts), elixir_tree_helpers:get_line(Opts)).
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match for fns
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
list_vars=nil, %% a list of vars passed down to Macro.Env
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
@@ -25,6 +25,8 @@
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
context_modules=[], %% modules defined in the current context
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
macro_counter=0, %% macros expansions counter
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
@@ -35,7 +37,7 @@
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
line=nil,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
@@ -45,6 +47,13 @@
|
||||
escape=false
|
||||
}).
|
||||
|
||||
-record(elixir_tokenizer, {
|
||||
file,
|
||||
terminators=[],
|
||||
check_terminators=true,
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ defprotocol Access do
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
|
||||
@only [List, Record, Atom]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
"""
|
||||
@@ -22,6 +20,7 @@ end
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a tuple list.
|
||||
The key is found via the `===` operator.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -34,14 +33,10 @@ defimpl Access, for: List do
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access(dict, key)
|
||||
def access([{ key, value }|_], key), do: value
|
||||
def access([{ _, _ }|t], key), do: access(t, key)
|
||||
def access([], _key), do: nil
|
||||
|
||||
def access(list, key) do
|
||||
case :lists.keyfind(key, 1, list) do
|
||||
{ ^key, value } -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define application module callbacks.
|
||||
Default callbacks for applications.
|
||||
|
||||
In Erlang/OTP, an application is a component that can be started
|
||||
and stopped as a unit, and which can be re-used in other systems
|
||||
as well.
|
||||
and stopped as a unit, and which can be reused in other systems.
|
||||
|
||||
The first step to achieve this is to define an application specification.
|
||||
The first step in creating an application is to define an application specification.
|
||||
For example, if your application is named `:my_app`, an app specification
|
||||
should exist at `ebin/my_app.app`. This file is usually defined by
|
||||
build tools like Mix.
|
||||
|
||||
Then, with the app specification in hands, we must also define an
|
||||
application module callback that controls how to start and stop
|
||||
such applications. This module is about defining such callbacks.
|
||||
With the app specification in hand, we must define
|
||||
application module callbacks that control how to start and stop
|
||||
instances of the application. This module is about defining such callbacks.
|
||||
|
||||
There are two callbacks required to be implemented:
|
||||
There are two callbacks which must be implemented:
|
||||
|
||||
1. `start(type, args)` - It must return `{ :ok, pid }` or
|
||||
1. `start(type, args)` - must return `{ :ok, pid }` or
|
||||
`{ :ok, pid, state }`, where `pid` is the process identifier
|
||||
of the supervisor tree root;
|
||||
of the supervisor tree root and `state` is application defined
|
||||
state information;
|
||||
|
||||
2. `stop(state)` receives the state returned by `start` and should
|
||||
do any necessary cleaning up. Notice that shutting down the supervisor
|
||||
2. `stop(state)` - receives the `state` returned by `start` and should
|
||||
do any necessary clean up. Notice that shutting down the supervisor
|
||||
is automatically handled by the VM;
|
||||
|
||||
When using this module, it simply tags the module behaviour as
|
||||
`:application` and defines a default `stop/1` callback. The `start/2`
|
||||
still needs to be defined by the user.
|
||||
When using this module, it tags the module behaviour as
|
||||
`:application` and provides a default `stop/1` callback. The `start/2` callback
|
||||
still needs to be implemented by the user.
|
||||
|
||||
You can learn more about the `:application` module, the application
|
||||
specification and the application module callbacks below:
|
||||
specification and the application module callbacks from these sources:
|
||||
|
||||
http://www.erlang.org/doc/man/application.html
|
||||
http://www.erlang.org/doc/design_principles/applications.html
|
||||
http://learnyousomeerlang.com/building-otp-applications
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://learnyousomeerlang.com/building-otp-applications
|
||||
|
||||
## Example
|
||||
|
||||
@@ -102,4 +102,4 @@ defmodule Application.Behaviour do
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+45
-19
@@ -1,12 +1,12 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
A convenience module for defining behaviours.
|
||||
Utilities for defining behaviour intefaces.
|
||||
|
||||
Behaviours can be referenced by other modules
|
||||
in order to ensure they implement the proper
|
||||
callbacks.
|
||||
to ensure they implement required callbacks.
|
||||
|
||||
For example, you can specify the `URI.Parser`
|
||||
behaviour as follow:
|
||||
behaviour as follows:
|
||||
|
||||
defmodule URI.Parser do
|
||||
use Behaviour
|
||||
@@ -18,7 +18,7 @@ defmodule Behaviour do
|
||||
defcallback default_port() :: integer
|
||||
end
|
||||
|
||||
And then a specific module may use it as:
|
||||
And then a module may use it as:
|
||||
|
||||
defmodule URI.HTTP do
|
||||
@behaviour URI.Parser
|
||||
@@ -26,24 +26,25 @@ defmodule Behaviour do
|
||||
def parse(info), do: info
|
||||
end
|
||||
|
||||
In case the behaviour changes or URI.HTTP does
|
||||
If the behaviour changes or `URI.HTTP` does
|
||||
not implement one of the callbacks, a warning
|
||||
will be raised.
|
||||
|
||||
## Implementation
|
||||
|
||||
Behaviours since Erlang R15 must be defined via
|
||||
Since Erlang R15, behaviours must be defined via
|
||||
`@callback` attributes. `defcallback` is a simple
|
||||
mechanism that defines the `@callback` attribute
|
||||
according to the type specification and also allows
|
||||
docs and defines a custom function signature.
|
||||
according to the given type specification. `defcallback` allows
|
||||
documentsion to be created for the callback and defines
|
||||
a custom function signature.
|
||||
|
||||
The callbacks and their documentation can be retrieved
|
||||
via the `__behaviour__` callback function.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a callback according to the given type specification.
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return] }) do
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
@@ -53,15 +54,36 @@ defmodule Behaviour do
|
||||
do_defcallback(fun, quote(do: term), __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, caller) do
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return] }) do
|
||||
do_defmacrocallback(fun, return, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, spec, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } -> :ok
|
||||
{ name, args } ->
|
||||
do_callback(:def, name, args, name, length(args), spec, caller)
|
||||
:error ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
defp do_defmacrocallback(fun, spec, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
|
||||
:error ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
@@ -74,7 +96,7 @@ defmodule Behaviour do
|
||||
|
||||
quote do
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(name), unquote(arity)
|
||||
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,23 +107,27 @@ defmodule Behaviour do
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, name, arity) do
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
Module.put_attribute module, :__behaviour_docs__, { { name, arity }, line, doc }
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
Module.register_attribute(__MODULE__, :__behaviour_docs__, accumulate: true)
|
||||
Module.register_attribute(__MODULE__, :behaviour_docs, accumulate: true)
|
||||
@before_compile unquote(__MODULE__)
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
defmacro __before_compile__(env) do
|
||||
docs = if Code.compiler_options[:docs] do
|
||||
Enum.reverse Module.get_attribute(env.module, :behaviour_docs)
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def __behaviour__(:callbacks) do
|
||||
@@ -109,7 +135,7 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
def __behaviour__(:docs) do
|
||||
@__behaviour_docs__
|
||||
unquote(Macro.escape(docs))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import Kernel, except: [to_binary: 1]
|
||||
|
||||
defprotocol Binary.Chars do
|
||||
@moduledoc %B"""
|
||||
The Binary.Chars protocol is responsible for
|
||||
converting a structure to a Binary (only if applicable).
|
||||
The only function required to be implemented is
|
||||
`to_binary` which does the conversion.
|
||||
|
||||
The `to_binary` function automatically imported
|
||||
by Kernel invokes this protocol. String
|
||||
interpolation also invokes to_binary in its
|
||||
arguments. For example, `"foo#{bar}"` is the same
|
||||
as `"foo" <> to_binary(bar)`.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Number, Atom, Record]
|
||||
|
||||
def to_binary(thing)
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: Atom do
|
||||
@doc """
|
||||
Convert the atom literally to a binary, except
|
||||
`nil` which is converted to an empty string.
|
||||
"""
|
||||
def to_binary(nil) do
|
||||
""
|
||||
end
|
||||
|
||||
def to_binary(atom) do
|
||||
atom_to_binary(atom, :utf8)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: BitString do
|
||||
@doc """
|
||||
Simply returns the binary itself.
|
||||
"""
|
||||
def to_binary(thing) when is_binary(thing) do
|
||||
thing
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: List do
|
||||
@doc """
|
||||
Consider the list is an iolist and converts it
|
||||
to a binary. This allows a list of binaries, or
|
||||
a charlist, or a mix of both, to be converted
|
||||
successfully.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> to_binary('foo')
|
||||
"foo"
|
||||
iex> to_binary(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_binary(thing) do
|
||||
try do
|
||||
iolist_to_binary(thing)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: __MODULE__,
|
||||
structure: thing,
|
||||
extra: "Only iolists are supported"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: Number do
|
||||
@doc """
|
||||
Simply converts the number (integer or a float) to a binary.
|
||||
"""
|
||||
def to_binary(thing) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def to_binary(thing) do
|
||||
list_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
+129
-100
@@ -6,24 +6,24 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
The Code module is responsible for managing code compilation,
|
||||
code evaluation and code loading.
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns all the loaded files.
|
||||
List all loaded files.
|
||||
"""
|
||||
def loaded_files do
|
||||
:elixir_code_server.call :loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the given files from the loaded files list.
|
||||
The modules defined in the file are not removed,
|
||||
Remove files from the loaded files list.
|
||||
|
||||
The modules defined in the file are not removed;
|
||||
calling this function only removes them from the list,
|
||||
allowing them to be required again.
|
||||
"""
|
||||
@@ -32,33 +32,39 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends a path to Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before being appended.
|
||||
Append a path to the Erlang VM code path.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being appended.
|
||||
"""
|
||||
def append_path(path) do
|
||||
:code.add_pathz(Path.expand to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prepends a path to Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before being prepended.
|
||||
Prepend a path to the Erlang VM code path.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being prepended.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
:code.add_patha(Path.expand to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a path from Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before being deleted.
|
||||
Delete a path from the Erlang VM code path.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the contents given by string. The second argument is the
|
||||
binding (which should be a keyword), followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
@@ -71,28 +77,42 @@ defmodule Code do
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
of function names and arity must be sorted
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
of function names and arity must be sorted
|
||||
|
||||
Notice that setting any of the values above overrides Elixir default
|
||||
Notice that setting any of the values above overrides Elixir's default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
automatically require the `Kernel` module; in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
|
||||
etc.
|
||||
|
||||
Returns a tuple of the form `{ value, binding }`,
|
||||
where `value` is the the value returned from evaluating `string`.
|
||||
If an error occurs while evaluating `string` an exception will be raised.
|
||||
|
||||
`binding` is a keyword list with the value of all variable bindings
|
||||
after evaluating `string`. The binding key is usually an atom, but it
|
||||
may be a tuple for variables defined in a different context.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as an argument and
|
||||
all imports, requires and aliases will be automatically carried
|
||||
over:
|
||||
iex> Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
|
||||
{3, [a: 1, b: 2, c: 3]}
|
||||
|
||||
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
|
||||
{3, [a: 3, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all imports, requires and aliases defined in the current environment
|
||||
will be automatically carried over:
|
||||
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
@@ -107,29 +127,27 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the quoted contents.
|
||||
Evaluate the quoted contents.
|
||||
|
||||
This function accepts a list of environment options.
|
||||
Check `Code.eval_string` for more information.
|
||||
See `eval_string/3` for a description of arguments and return values.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as an argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all options will be automatically extracted from the current environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
@@ -157,10 +175,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp validate_requires(kind, requires) do
|
||||
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
|
||||
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -170,7 +188,7 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,40 +200,36 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to its quoted form. Returns `{ :ok, quoted_form }`
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
and the file reported in the `__ENV__` variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
* `:line` - The line reported in the `__ENV__` variable.
|
||||
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
* `:existing_atoms_only` - When `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_string/1
|
||||
## Macro.to_string/2
|
||||
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string`, which converts a quoted form to a string/binary
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_quoted(string, opts // []) do
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
|
||||
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
@@ -223,24 +237,20 @@ defmodule Code do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast!(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted!(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to its quoted form. It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a TokenMissingError
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a `TokenMissingError`
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
SyntaxError otherwise.
|
||||
`SyntaxError` otherwise.
|
||||
|
||||
Check `Code.string_to_quoted/2` for options information.
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_quoted!(string, opts // []) do
|
||||
def string_to_quoted!(string, opts // []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
|
||||
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
|
||||
unpack_quote(line, res)
|
||||
end
|
||||
|
||||
@@ -249,16 +259,18 @@ defmodule Code do
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Loads the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. If the file was already required/loaded, loads it again.
|
||||
It returns a list of tuples { ModuleName, <<byte_code>> }, one tuple for each
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
|
||||
module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file` if you don't want a file to be loaded concurrently.
|
||||
`require_file/2` if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
@@ -269,17 +281,19 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file`. If
|
||||
the file was already required/loaded, doesn't do anything and returns nil.
|
||||
Require the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file/2`. If
|
||||
the file was already required/loaded, doesn't do anything and returns `nil`.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
ones will block until the file is available. I.e. if `require_file` is called
|
||||
N times with a given file, it will be loaded only once. The first process to
|
||||
call `require_file` will get the list of loaded modules, others will get nil.
|
||||
call `require_file` will get the list of loaded modules, others will get `nil`.
|
||||
|
||||
Check `load_file` if you want a file to be loaded concurrently.
|
||||
Check `load_file/2` if you want a file to be loaded concurrently.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
@@ -297,7 +311,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Load the compilation options from the code server.
|
||||
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
@@ -305,22 +320,23 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options. These options are global
|
||||
since they are stored by Elixir's Code Server.
|
||||
Set compilation options.
|
||||
|
||||
These options are global since they are stored by Elixir's Code Server.
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when true, retain documentation in the compiled module,
|
||||
true by default;
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` by default;
|
||||
|
||||
* `:debug_info` - when true, retain debug information in the compiled module.
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
code, `false` by default;
|
||||
|
||||
* `:ignore_module_conflict` - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were already defined
|
||||
without raising errors, `false` by default;
|
||||
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
@@ -328,18 +344,22 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given string and returns a list of tuples where
|
||||
Compile the given string.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler`.
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string :unicode.characters_to_list(string), file
|
||||
:elixir_compiler.string String.to_char_list!(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the quoted expression and returns a list of tuples where
|
||||
Compile the quoted expression.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
"""
|
||||
@@ -348,8 +368,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is loaded. If the module is already
|
||||
loaded, it works as no-op. If the module was not loaded yet,
|
||||
Ensure the given module is loaded.
|
||||
|
||||
If the module is already
|
||||
loaded, this works as no-op. If the module was not yet loaded,
|
||||
it tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
@@ -364,9 +386,9 @@ defmodule Code do
|
||||
are loaded as needed. In embedded mode the opposite happens, as all
|
||||
modules need to be loaded upfront or explicitly.
|
||||
|
||||
Therefore, this function is useful to check if a module is loaded
|
||||
before using it and react accordingly. For example, the `URI` module
|
||||
uses this function to check if a specific parser exists for a given
|
||||
Therefore, this function is used to check if a module is loaded
|
||||
before using it and allows one to react accordingly. For example, the `URI`
|
||||
module uses this function to check if a specific parser exists for a given
|
||||
URI scheme.
|
||||
|
||||
## Code.ensure_compiled
|
||||
@@ -375,32 +397,36 @@ defmodule Code do
|
||||
superset of `ensure_loaded/1`.
|
||||
|
||||
Since Elixir's compilation happens in parallel, in some situations
|
||||
you may need to use a module but it was not compiled yet, therefore
|
||||
you may need to use a module but that was not yet compiled, therefore
|
||||
it can't even be loaded.
|
||||
|
||||
`ensure_compiled/1` puts a halt in the current process until the
|
||||
`ensure_compiled/1` halts the current process until the
|
||||
module we are depending on is available.
|
||||
|
||||
In most of the cases, `ensure_loaded` is enough. `ensure_compiled`
|
||||
must be used just in same rare conditions, usually involving macros
|
||||
that needs to invoke a module for callback information.
|
||||
In most cases, `ensure_loaded` is enough. `ensure_compiled`
|
||||
must be used in some rare cases, usually involving macros
|
||||
that need to invoke a module for callback information.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_loaded/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
Ensure the given module is loaded.
|
||||
|
||||
Similar to `ensure_loaded/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded. Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is compiled and loaded. If the module
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
If the module
|
||||
is already loaded, it works as no-op. If the module was not
|
||||
loaded yet, it checks if it needs to be compiled first and just
|
||||
loaded yet, it checks if it needs to be compiled first and
|
||||
then tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
@@ -428,8 +454,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_compiled/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
Similar to `ensure_compiled/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
match?({ :module, ^module }, ensure_compiled(module))
|
||||
|
||||
+86
-64
@@ -1,5 +1,5 @@
|
||||
defmodule Dict do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict, allowing
|
||||
@@ -11,30 +11,31 @@ defmodule Dict do
|
||||
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
For simplicity's sake, in the examples below everytime
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above. Likewise, when the result of a function
|
||||
invocation is shown in the form `[a: 1, b: 2]`, it implies
|
||||
that the returned value is actually of the same dict type
|
||||
as the input one.
|
||||
In the examples below, `dict_impl` means a specific
|
||||
`Dict` implementation, for example `HashDict` or `ListDict`.
|
||||
|
||||
## Protocols
|
||||
|
||||
Besides implementing the functions in this module, all
|
||||
dictionaries are also required to implement the `Access`
|
||||
dictionaries are required to implement the `Access`
|
||||
protocol:
|
||||
|
||||
iex> dict = HashDict.new
|
||||
iex> dict = dict_impl.new
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enumerable` protocol, allowing one to write:
|
||||
And the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end)
|
||||
|
||||
## Match
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -44,12 +45,16 @@ defmodule Dict do
|
||||
@type keys :: [ key ]
|
||||
@type t :: tuple | list
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Keyword.t) :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, keys) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback fetch(t, key) :: { :ok, value } | :error
|
||||
defcallback fetch!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback merge(t, t) :: t
|
||||
@@ -62,9 +67,10 @@ defmodule Dict do
|
||||
defcallback split(t, keys) :: {t, t}
|
||||
defcallback take(t, keys) :: t
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback update(t, key, (value -> value)) :: t | no_return
|
||||
defcallback update(t, key, value, (value -> value)) :: t
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
defcallback values(t) :: list(value)
|
||||
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
@@ -80,13 +86,12 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's keys.
|
||||
The keys are not guaranteed to be sorted, unless
|
||||
the underlying dict implementation defines so.
|
||||
Returns a list of all keys in `dict`.
|
||||
The keys are not guaranteed to be in any order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
@@ -97,11 +102,12 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's values.
|
||||
Returns a list of all values in `dict`
|
||||
The values are not guaranteed to be in any order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
@@ -116,7 +122,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
2
|
||||
|
||||
@@ -127,11 +133,11 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the given key exists in the given dict.
|
||||
Returns whether the given `key` exists in the given `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
@@ -145,11 +151,11 @@ defmodule Dict do
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, returns `default` (or nil if not provided).
|
||||
contain `key`, returns `default` (or `nil` if not provided).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
@@ -162,18 +168,13 @@ defmodule Dict do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) do
|
||||
target(dict).get!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the `{ :ok, value }` associated with `key` in `dict`.
|
||||
Returns `{ :ok, value }` associated with `key` in `dict`.
|
||||
If `dict` does not contain `key`, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
iex> Dict.fetch(d, :b)
|
||||
@@ -191,7 +192,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> Dict.fetch!(d, :b)
|
||||
@@ -209,7 +210,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
3
|
||||
@@ -225,7 +226,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
1
|
||||
@@ -237,17 +238,17 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the entry stored under the given key from `dict`.
|
||||
Removes the entry stored under the given `key` from `dict`.
|
||||
If `dict` does not contain `key`, returns the dictionary unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new([b: 2])
|
||||
iex> d = dict_impl.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
@@ -258,13 +259,17 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given enum into the dict. In case one of the enum entries
|
||||
alread exist in the dict, it is given higher preference.
|
||||
Merges the given `enum` into `dict`. If one of the `enum` keys
|
||||
already exists in `dict`, the `dict` value is replaced by the `enum`
|
||||
value.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = HashDict.new([a: 1, b: 2])
|
||||
...> d2 = HashDict.new([a: 3, d: 4])
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
@@ -276,14 +281,17 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given enum into the dict. In case one of the enum entries
|
||||
alread exist in the dict, the given function is invoked to solve
|
||||
conflicts.
|
||||
Merges the given `enum` into `dict`. If one of the `enum` entries
|
||||
already exists in `dict`, the given function is invoked to resolve
|
||||
the conflict.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = HashDict.new([a: 1, b: 2])
|
||||
...> d2 = HashDict.new([a: 3, d: 4])
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
@@ -302,17 +310,17 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :a
|
||||
...> {v, Enum.sort(d)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b
|
||||
...> {v, Enum.sort(d)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b, 3
|
||||
...> {v, Enum.sort(d)}
|
||||
{3,[a: 1]}
|
||||
@@ -329,13 +337,18 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :a, fn(val) -> -val end)
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
...> Dict.get(d, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@spec update(t, key, (value -> value)) :: t
|
||||
@spec update!(t, key, (value -> value)) :: t
|
||||
def update!(dict, key, fun) do
|
||||
target(dict).update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) do
|
||||
target(dict).update(dict, key, fun)
|
||||
end
|
||||
@@ -347,7 +360,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
3
|
||||
@@ -367,17 +380,17 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [a: 1], [b: 2] }
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
|
||||
iex> d = HashDict.new([])
|
||||
iex> d = dict_impl.new([])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
@@ -394,12 +407,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[b: 2]
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1, b: 2]
|
||||
@@ -412,11 +425,12 @@ defmodule Dict do
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are
|
||||
included. Any non-member keys are ignored.
|
||||
included.
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
@@ -434,6 +448,14 @@ defmodule Dict do
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> e = Dict.empty(d)
|
||||
...> Dict.to_list(e)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
@@ -441,17 +463,17 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal, if the dicts are of different types they're
|
||||
first converted to lists.
|
||||
Check if two dicts are equal using `===`. If the dicts are
|
||||
of different types, they are first converted to lists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = []
|
||||
...> Dict.equal?(a, b)
|
||||
false
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
Usage:
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
|
||||
# implement required functions (see below)
|
||||
|
||||
# override default implementations if needed
|
||||
end
|
||||
|
||||
The client module must contain following functions: `size/1`, `fetch/2`,
|
||||
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
|
||||
the Dict behaviour, so no extra functions are actually required.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for other functions such as `drop`, `take`, etc. All of the functions are
|
||||
defined as overridable, so you can provide your own implementation if
|
||||
needed.
|
||||
|
||||
If you implement `new/0` and `new/1` functions, you can also test your custom
|
||||
module via `Dict` doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords // []) do
|
||||
...
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MyTests do
|
||||
use ExUnit.Case
|
||||
doctest Dict
|
||||
defp dict_impl, do: MyDict
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
defoverridable get: 2, get: 3
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
defoverridable fetch!: 2
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
end
|
||||
defoverridable has_key?: 2
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
defoverridable put_new: 3
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
defoverridable drop: 2
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
defoverridable take: 2
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
|
||||
defoverridable to_list: 1
|
||||
|
||||
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
|
||||
defoverridable keys: 1
|
||||
|
||||
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
|
||||
defoverridable values: 1
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
case Dict.size(dict1) == Dict.size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
try do
|
||||
reduce(dict1, nil, fn({ k, v }, _acc) ->
|
||||
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
|
||||
end)
|
||||
true
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
end
|
||||
defoverridable equal?: 2
|
||||
|
||||
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
|
||||
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
|
||||
end)
|
||||
end
|
||||
defoverridable merge: 2, merge: 3
|
||||
end
|
||||
end
|
||||
end
|
||||
+571
-249
File diff suppressed because it is too large
Load Diff
+101
-46
@@ -5,19 +5,19 @@ defexception SystemLimitError, message: "a system limit has been reached"
|
||||
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,6 +39,12 @@ defexception CaseClauseError, [actual: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
@@ -48,7 +54,7 @@ end
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
@@ -59,7 +65,7 @@ end
|
||||
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
@@ -67,11 +73,11 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
|
||||
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
|
||||
def message(exception) do
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
if exception.extra do
|
||||
msg <> ". " <> exception.extra
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
|
||||
if exception.description do
|
||||
msg <> ", " <> exception.description
|
||||
else
|
||||
msg
|
||||
end
|
||||
@@ -96,12 +102,21 @@ defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Several convenience functions to work and pretty print
|
||||
Several convenience functions to work with and pretty print
|
||||
exceptions and stacktraces.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Normalizes an exception converting Erlang exceptions
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions. It takes the kind spilled by
|
||||
`catch` as an argument as a convenience for converting only
|
||||
`:errors`, ignorning the others.
|
||||
"""
|
||||
def normalize(:error, exception), do: normalize(exception)
|
||||
def normalize(_kind, other), do: other
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions. Useful when interfacing Erlang
|
||||
code with Elixir code.
|
||||
"""
|
||||
@@ -137,6 +152,10 @@ defmodule Exception do
|
||||
CaseClauseError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize({ :try_clause, actual }) do
|
||||
TryClauseError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
@@ -157,39 +176,40 @@ defmodule Exception do
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
The current working directory may be given as argument, which
|
||||
is used to prettify the stacktrace.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
"""
|
||||
def format_stacktrace_entry(entry, cwd // nil)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
|
||||
"#{format_location(file_line, cwd)}#{inspect module} (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}(module)"
|
||||
"#{format_location(file_line, cwd)}(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}(file)"
|
||||
"#{format_location(file_line, cwd)}(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
|
||||
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
|
||||
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the stacktrace.
|
||||
|
||||
A stacktrace must be given as argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As a consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil) do
|
||||
@@ -199,15 +219,17 @@ defmodule Exception do
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
cwd = System.cwd
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
Notice that doing to tail call optimization, the stacktrace
|
||||
Notice that due to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def format_caller(trace // nil) do
|
||||
@@ -224,11 +246,17 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@doc """
|
||||
Receives an anonymous function and arity and formats it as
|
||||
shown in stacktraces. The arity may also be a list of arguments.
|
||||
|
||||
# Format fun and arity
|
||||
@doc false
|
||||
def format_fun_arity(fun, arity) do
|
||||
## Examples
|
||||
|
||||
Exception.format_fa(fn -> end, 1)
|
||||
#=> "#Function<...>/1"
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) do
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
|
||||
@@ -237,11 +265,22 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
# Receives a module, fun and arity and returns a string
|
||||
# representing such invocation. Arity may also be a list
|
||||
# of arguments. It follows the same syntax as in stacktraces.
|
||||
@doc false
|
||||
def format_module_fun_arity(module, fun, arity) do
|
||||
@doc """
|
||||
Receives a module, fun and arity and formats it
|
||||
as shown in stacktraces. The arity may also be a list
|
||||
of arguments.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, 1
|
||||
"Foo.bar/1"
|
||||
iex> Exception.format_mfa Foo, :bar, []
|
||||
"Foo.bar()"
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
"""
|
||||
def format_mfa(module, fun, arity) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
<< ?:, erl :: binary >> -> erl
|
||||
@@ -250,25 +289,37 @@ defmodule Exception do
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
"#{format_module module}#{fun}/#{arity}"
|
||||
end
|
||||
end
|
||||
|
||||
# Format file and line for exception printing.
|
||||
@doc false
|
||||
def format_file_line(opts, cwd) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
|
||||
end
|
||||
defp format_module(mod), do: "#{inspect mod}."
|
||||
|
||||
def format_file_line(file, line, cwd) do
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_file_line("foo", 1)
|
||||
"foo:1: "
|
||||
|
||||
iex> Exception.format_file_line("foo", nil)
|
||||
"foo: "
|
||||
|
||||
iex> Exception.format_file_line(nil, nil)
|
||||
""
|
||||
|
||||
"""
|
||||
def format_file_line(file, line, cwd // nil) do
|
||||
if file do
|
||||
file = to_binary(file)
|
||||
|
||||
if cwd do
|
||||
file = Path.relative_to(file, cwd)
|
||||
end
|
||||
file = to_string(file)
|
||||
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
|
||||
|
||||
if line && line != 0 do
|
||||
"#{file}:#{line}: "
|
||||
@@ -280,6 +331,10 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp format_location(opts, cwd) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
{ module, function, length(args) }
|
||||
end
|
||||
|
||||
+140
-137
@@ -33,45 +33,38 @@ end
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.IteratorError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
"error during file iteration: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `File.open/2`,
|
||||
`File.copy/3` and others. This module also provides higher
|
||||
to interact with the file or IO devices, like `open/2`,
|
||||
`copy/3` and others. This module also provides higher
|
||||
level functions that works with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `File.cp/3` and remove files and directories recursively
|
||||
via `File.rm_rf/2`
|
||||
via `cp/3` and remove files and directories recursively
|
||||
via `rm_rf/1`
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the IO module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread` and `IO.binwrite`
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions
|
||||
from IO are available, since they work directly with Unicode
|
||||
from `IO` are available, since they work directly with Unicode
|
||||
data.
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
@@ -104,7 +97,7 @@ defmodule File do
|
||||
alias :filelib, as: FL
|
||||
|
||||
@doc """
|
||||
Returns true if the path is a regular file.
|
||||
Returns `true` if the path is a regular file.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -116,14 +109,14 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the path is a directory.
|
||||
Returns `true` if the path is a directory.
|
||||
"""
|
||||
def dir?(path) do
|
||||
FL.is_dir(path)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given path exists.
|
||||
Returns `true` if the given path exists.
|
||||
It can be regular file, directory, socket,
|
||||
symbolic link, named pipe or device file.
|
||||
|
||||
@@ -161,13 +154,13 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `mkdir`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
Same as `mkdir/1`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def mkdir!(path) do
|
||||
case mkdir(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "make directory", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "make directory", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -186,13 +179,13 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `mkdir_p`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
Same as `mkdir_p/1`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def mkdir_p!(path) do
|
||||
case mkdir_p(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "make directory (with -p)", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "make directory (with -p)", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -210,7 +203,7 @@ defmodule File do
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* :enomem - There is not enough memory for the contents of the file.
|
||||
|
||||
You can use `:file.format_error(reason)` to get a descriptive string of the error.
|
||||
You can use `:file.format_error/1` to get a descriptive string of the error.
|
||||
"""
|
||||
def read(path) do
|
||||
F.read_file(path)
|
||||
@@ -218,14 +211,14 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Returns binary with the contents of the given filename or raises
|
||||
File.Error if an error occurs.
|
||||
`File.Error` if an error occurs.
|
||||
"""
|
||||
def read!(path) do
|
||||
case read(path) do
|
||||
{ :ok, binary } ->
|
||||
binary
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "read file", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "read file", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -233,34 +226,34 @@ defmodule File do
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{ :ok, info }` tuple, where info is a
|
||||
`File.Info` record. Retuns `{ :error, reason }` with
|
||||
the same reasons as `File.read` if a failure occurs.
|
||||
the same reasons as `read/1` if a failure occurs.
|
||||
|
||||
## Options
|
||||
|
||||
The accepted options are:
|
||||
|
||||
* `:time` if the time should be local, universal or posix.
|
||||
Default is local.
|
||||
* `:time` if the time should be `:local`, `:universal` or `:posix`.
|
||||
Default is `:local`.
|
||||
|
||||
"""
|
||||
def stat(path, opts // []) do
|
||||
case F.read_file_info(path, opts) do
|
||||
{:ok, fileinfo} ->
|
||||
{:ok, File.Stat.new fileinfo}
|
||||
{:ok, set_elem(fileinfo, 0, File.Stat) }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `stat` but returns the `File.Stat` directly and
|
||||
Same as `stat/2` but returns the `File.Stat` directly and
|
||||
throws `File.Error` if an error is returned.
|
||||
"""
|
||||
def stat!(path, opts // []) do
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "read file stats", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "read file stats", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -280,7 +273,7 @@ defmodule File do
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "write file stats", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "write file stats", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -290,20 +283,23 @@ defmodule File do
|
||||
"""
|
||||
def touch(path, time // :calendar.local_time) do
|
||||
case F.change_time(path, time) do
|
||||
{ :error, :enoent } -> write(path, "")
|
||||
other -> other
|
||||
{ :error, :enoent } ->
|
||||
write(path, "")
|
||||
F.change_time(path, time)
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `touch/1` but raises an exception if it fails.
|
||||
Same as `touch/2` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def touch!(path, time // :calendar.local_time) do
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "touch", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "touch", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -311,7 +307,7 @@ defmodule File do
|
||||
Copies the contents of `source` to `destination`.
|
||||
|
||||
Both parameters can be a filename or an io device opened
|
||||
with `File.open/2`. `bytes_count` specifies the number of
|
||||
with `open/2`. `bytes_count` specifies the number of
|
||||
bytes to copy, the default being `:infinity`.
|
||||
|
||||
If file `destination` already exists, it is overwritten
|
||||
@@ -320,14 +316,14 @@ defmodule File do
|
||||
Returns `{ :ok, bytes_copied }` if successful,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
Compared to the `File.cp/3`, this function is more low-level,
|
||||
Compared to the `cp/3`, this function is more low-level,
|
||||
allowing a copy from device to device limited by a number of
|
||||
bytes. On the other hand, `File.cp/3` performs more extensive
|
||||
bytes. On the other hand, `cp/3` performs more extensive
|
||||
checks on both source and destination and it also preserves
|
||||
the file mode after copy.
|
||||
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/2`.
|
||||
`read/1` and `write/3`.
|
||||
"""
|
||||
def copy(source, destination, bytes_count // :infinity) do
|
||||
F.copy(source, destination, bytes_count)
|
||||
@@ -342,7 +338,7 @@ defmodule File do
|
||||
{ :ok, bytes_count } -> bytes_count
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: to_string(source), destination: to_string(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -355,15 +351,15 @@ defmodule File do
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return true if the existing file
|
||||
should be overwritten, false otherwise. It defaults to return true.
|
||||
callback which should return `true` if the existing file
|
||||
should be overwritten, `false` otherwise. It defaults to return `true`.
|
||||
|
||||
It returns `:ok` in case of success, returns
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `File.copy/3` instead.
|
||||
preserving modes, check `copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
output =
|
||||
@@ -381,7 +377,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `cp/3`, but raises File.CopyError if it fails.
|
||||
The same as `cp/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp!(source, destination, callback // fn(_, _) -> true end) do
|
||||
@@ -389,12 +385,12 @@ defmodule File do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination)
|
||||
source: to_string(source),
|
||||
destination: to_string(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Copies the contents in source to destination.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
@@ -407,8 +403,8 @@ defmodule File do
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overwritten,
|
||||
false otherwise. It defaults to return true.
|
||||
`true` if the existing file should be overwritten,
|
||||
`false` otherwise. It defaults to return `true`.
|
||||
|
||||
If a directory already exists in the destination
|
||||
where a file is meant to be (or otherwise), this
|
||||
@@ -456,7 +452,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `cp_r/3`, but raises File.CopyError if it fails.
|
||||
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
@@ -464,8 +460,8 @@ defmodule File do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason, file } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination),
|
||||
source: to_string(source),
|
||||
destination: to_string(destination),
|
||||
on: file
|
||||
end
|
||||
end
|
||||
@@ -473,28 +469,28 @@ defmodule File do
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case :elixir.file_type(src) do
|
||||
case :elixir_utils.file_type(src) do
|
||||
{ :ok, :regular } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{ :ok, :symlink } ->
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
{ :ok, :directory } ->
|
||||
case F.list_dir(src) do
|
||||
{ :ok, files } ->
|
||||
case mkdir(dest) do
|
||||
success in [:ok, { :error, :eexist }] ->
|
||||
success when success in [:ok, { :error, :eexist }] ->
|
||||
Enum.reduce(files, [dest|acc], fn(x, acc) ->
|
||||
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
|
||||
end)
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
|
||||
{ :error, reason } -> { :error, reason, to_string(dest) }
|
||||
end
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
{ :ok, _ } -> { :error, :eio, src }
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -523,12 +519,12 @@ defmodule File do
|
||||
{ :ok, _ } ->
|
||||
copy_file_mode!(src, dest)
|
||||
[dest|acc]
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -542,12 +538,12 @@ defmodule File do
|
||||
rm(dest)
|
||||
case F.make_symlink(link, dest) do
|
||||
:ok -> [dest|acc]
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
{ :error, reason } -> { :error, reason, to_string(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -576,7 +572,7 @@ defmodule File do
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "write to file", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "write to file", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -607,13 +603,13 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `rm`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def rm!(path) do
|
||||
case rm(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove file", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove file", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -623,7 +619,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.rddir('tmp_dir')
|
||||
File.rmdir('tmp_dir')
|
||||
#=> :ok
|
||||
|
||||
File.rmdir('file.txt')
|
||||
@@ -641,7 +637,7 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove directory", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove directory", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -651,7 +647,7 @@ defmodule File do
|
||||
files are simply ignored (i.e. doesn't make this function fail).
|
||||
|
||||
Returns `{ :ok, files_and_directories }` with all files and
|
||||
directories removed in no specific order, `{ :error, reason }`
|
||||
directories removed in no specific order, `{ :error, reason, file }`
|
||||
otherwise.
|
||||
|
||||
## Examples
|
||||
@@ -669,7 +665,7 @@ defmodule File do
|
||||
|
||||
defp do_rm_rf(path, { :ok, acc } = entry) do
|
||||
case safe_list_dir(path) do
|
||||
{ :ok, files } ->
|
||||
{ :ok, files } when is_list(files) ->
|
||||
res =
|
||||
Enum.reduce files, entry, fn(file, tuple) ->
|
||||
do_rm_rf(FN.join(path, file), tuple)
|
||||
@@ -680,18 +676,15 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> res
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
{ :error, reason } -> { :error, reason, to_string(path) }
|
||||
end
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
reason ->
|
||||
reason
|
||||
end
|
||||
{ :error, reason } when reason in [:enotdir, :eio] ->
|
||||
case rm(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enoent } -> entry
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
{ :ok, :directory } -> do_rm_directory(path, entry)
|
||||
{ :ok, :regular } -> do_rm_regular(path, entry)
|
||||
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
|
||||
{ :error, reason } -> { :error, reason, to_string(path) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -699,10 +692,40 @@ defmodule File do
|
||||
reason
|
||||
end
|
||||
|
||||
defp do_rm_regular(path, { :ok, acc } = entry) do
|
||||
case rm(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> entry
|
||||
{ :error, reason } -> { :error, reason, to_string(path) }
|
||||
end
|
||||
end
|
||||
|
||||
# On windows, symlinks are treated as directory and must be removed
|
||||
# with rmdir/1. But on Unix, we remove them via rm/1. So we first try
|
||||
# to remove it as a directory and, if we get :enotdir, we fallback to
|
||||
# a file removal.
|
||||
defp do_rm_directory(path, { :ok, acc } = entry) do
|
||||
case rmdir(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enotdir } -> do_rm_regular(path, entry)
|
||||
{ :error, :enoent } -> entry
|
||||
{ :error, reason } -> { :error, reason, to_string(path) }
|
||||
end
|
||||
end
|
||||
|
||||
defp safe_list_dir(path) do
|
||||
case F.read_link(path) do
|
||||
{ :ok, _ } -> { :error, :enotdir }
|
||||
_ -> F.list_dir(path)
|
||||
case :elixir_utils.file_type(path) do
|
||||
{ :ok, :symlink } ->
|
||||
case :elixir_utils.file_type(path, :read_file_info) do
|
||||
{ :ok, :directory } -> { :ok, :directory }
|
||||
_ -> { :ok, :regular }
|
||||
end
|
||||
{ :ok, :directory } ->
|
||||
F.list_dir(path)
|
||||
{ :ok, _ } ->
|
||||
{ :ok, :regular }
|
||||
{ :error, reason } ->
|
||||
{ :error, reason }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -716,7 +739,7 @@ defmodule File do
|
||||
{ :error, reason, _ } ->
|
||||
raise File.Error, reason: reason,
|
||||
action: "remove files and directories recursively from",
|
||||
path: :unicode.characters_to_binary(path)
|
||||
path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -724,8 +747,8 @@ defmodule File do
|
||||
Opens the given `path` according to the given list of modes.
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the IO module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread` and `IO.binwrite`
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions from
|
||||
`IO` are available, since they work directly with Unicode data.
|
||||
@@ -756,10 +779,10 @@ defmodule File do
|
||||
or if data is read by a function that returns data in a format that cannot cope
|
||||
with the character range of the data, an error occurs and the file will be closed.
|
||||
|
||||
If a function is given to modes (instead of a list), it dispatches to `open/3`.
|
||||
If a function is given two modes (instead of a list), it dispatches to `open/3`.
|
||||
|
||||
Check `http://www.erlang.org/doc/man/file.html#open-2` for more information about
|
||||
other options as `read_ahead` and `delayed_write`.
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `read_ahead` and `delayed_write`.
|
||||
|
||||
This function returns:
|
||||
|
||||
@@ -830,7 +853,7 @@ defmodule File do
|
||||
case open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "open", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -842,7 +865,7 @@ defmodule File do
|
||||
case open(path, modes, function) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "open", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -854,7 +877,7 @@ defmodule File do
|
||||
"""
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{ :ok, cwd } -> { :ok, :unicode.characters_to_binary(cwd) }
|
||||
{ :ok, base } -> { :ok, String.from_char_list!(base) }
|
||||
{ :error, _ } = error -> error
|
||||
end
|
||||
end
|
||||
@@ -864,7 +887,7 @@ defmodule File do
|
||||
"""
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{ :ok, cwd } -> :unicode.characters_to_binary(cwd)
|
||||
{ :ok, cwd } -> to_string(cwd)
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -879,13 +902,13 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `cd/0`, but raises an exception if it fails.
|
||||
The same as `cd/1`, but raises an exception if it fails.
|
||||
"""
|
||||
def cd!(path) do
|
||||
case F.set_cwd(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "set current working directory to", path: :unicode.characters_to_binary(path)
|
||||
raise File.Error, reason: reason, action: "set current working directory to", path: to_string(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -915,7 +938,7 @@ defmodule File do
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, :unicode.characters_to_binary(&1)) }
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
|
||||
{ :error, _ } = error -> error
|
||||
end
|
||||
end
|
||||
@@ -928,7 +951,7 @@ defmodule File do
|
||||
case ls(dir) do
|
||||
{ :ok, value } -> value
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "list directory", path: :unicode.characters_to_binary(dir)
|
||||
raise File.Error, reason: reason, action: "list directory", path: to_string(dir)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -944,64 +967,44 @@ defmodule File do
|
||||
F.close(io_device)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def iterator(device) do
|
||||
IO.write "[WARNING] File.iterator/1 is deprecated, please use IO.stream/1 instead\n#{Exception.format_stacktrace}"
|
||||
IO.stream(device)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its stream. The returned stream will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
Opens the given `file` with the given `mode` and returns
|
||||
a stream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
"""
|
||||
def stream!(file, mode // []) do
|
||||
def stream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(acc, fun) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
IO.stream(device, acc, fun)
|
||||
IO.stream(device, line_or_bytes, acc, fun)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def iterator!(device, mode // []) do
|
||||
IO.write "[WARNING] File.iterator!/2 is deprecated, please use File.stream!/2 instead\n#{Exception.format_stacktrace}"
|
||||
stream!(device, mode)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def biniterator(device) do
|
||||
IO.write "[WARNING] File.biniterator/1 is deprecated, please use IO.binstream/1 instead\n#{Exception.format_stacktrace}"
|
||||
IO.binstream(device)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its binstream. The returned stream will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
Opens the given `file` with the given `mode` and returns
|
||||
a binstream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
"""
|
||||
def binstream!(file, mode // []) do
|
||||
def binstream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
IO.binstream(device, fun, acc)
|
||||
IO.binstream(device, line_or_bytes, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def biniterator!(device, mode // []) do
|
||||
IO.write "[WARNING] File.binstream!/2 is deprecated, please use File.binstream!/2 instead\n#{Exception.format_stacktrace}"
|
||||
binstream!(device, mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
@@ -1018,7 +1021,7 @@ defmodule File do
|
||||
case chmod(file, mode) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
|
||||
raise File.Error, reason: reason, action: "change mode for", path: to_string(file)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1038,7 +1041,7 @@ defmodule File do
|
||||
case chgrp(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
|
||||
raise File.Error, reason: reason, action: "change group for", path: to_string(file)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1058,7 +1061,7 @@ defmodule File do
|
||||
case chown(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
|
||||
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
defmodule Float do
|
||||
@moduledoc """
|
||||
Functions for working with floating point numbers.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.parse("34")
|
||||
{34.0,""}
|
||||
iex> Float.parse("34.25")
|
||||
{34.25,""}
|
||||
iex> Float.parse("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> Float.parse("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
def parse(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse(rest, char - ?0, 1, int)
|
||||
end
|
||||
|
||||
defp parse(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
|
||||
defp parse(<< ?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(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential // 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
|
||||
# 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
|
||||
end
|
||||
@@ -0,0 +1,211 @@
|
||||
defmodule GenFSM.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenFSM callbacks in Elixir.
|
||||
|
||||
A finite state machine (FSM) is responsible for reacting to events received;
|
||||
GenFSM is an OTP behaviour that encapsulates common FSM
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenFSM that runs a very simple minded
|
||||
coffee vending machine (CVM). The CVM treats all coins the same. If
|
||||
you press the request button then the CVM will brew coffee, if you
|
||||
have paid enough coins; if not, it will wait until you have inserted
|
||||
enough coins and then it will instantly brew the coffee, since you
|
||||
had already pressed the request button. As we told you - a very
|
||||
simple minded CVM! And greedy too. If you insert more coins than you
|
||||
need it will gladly eat them until you press the request button.
|
||||
|
||||
We will leave it to the service-minded reader to improve the way the CVM
|
||||
works - we hereby declare a full disclaimer for any lawsuits that the
|
||||
behaviour of the CVM in its original state might encur.
|
||||
|
||||
|
||||
defmodule MyFsm do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
|
||||
#
|
||||
# API functions
|
||||
#
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p])
|
||||
when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{:next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
|
||||
when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
{ :ok, _pid } = MyFsm.start_link()
|
||||
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
|
||||
MyFsm.request_coffee
|
||||
#=> :ok
|
||||
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
#=> Here's your coffee!
|
||||
|
||||
Notice we never call the GenFSM callbacks directly; they are called by
|
||||
OTP whenever we interact with the server throught the API. `send_event` is
|
||||
asynchronous, whereas `sync_send_event` is synchronous. For
|
||||
a GenFSM, the different values a callback can return depend
|
||||
on the type of callback.
|
||||
|
||||
State handling returns for `send_event` callbacks:
|
||||
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, new_state_data }
|
||||
|
||||
State handling returns for `sync_send_event` callbacks:
|
||||
|
||||
{ :reply, reply, next_state_name, new_state_data }
|
||||
{ :reply, reply, next_state_name, new_state_data, timeout }
|
||||
{ :reply, reply, next_state_name, new_state_data, :hibernate }
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, reply, new_state_data }
|
||||
{ :stop, reason, new_state_date }
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenFsm plus 1
|
||||
or 2 for each state. The `GenFSM.Behaviour` module defines
|
||||
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
|
||||
for you. The list of callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the FSM is started;
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
handle `sync_send_all_state_event` messages;
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
`send_all_state_event` messages;
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
messages which are normally received by processes;
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
is about to terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
|
||||
implemented by default, as it requires the next state to be returned.
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
`state_name/2` variant and vice-versa for synchronous events and
|
||||
`state_name/3`. Keep in mind that if you mix `send_event` and
|
||||
`sync_send_event` the best thing to do is to implement both
|
||||
callbacks for all states.
|
||||
|
||||
Starting and sending messages to the GenFSM is done via Erlang's
|
||||
`:gen_fsm` module. For more information, please refer to the
|
||||
following:
|
||||
|
||||
* http://www.erlang.org/doc/man/gen_fsm.html
|
||||
* http://www.erlang.org/doc/design_principles/fsm.html
|
||||
* http://learnyousomeerlang.com/finite-state-machines
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(_event, from, state_name, state_data) do
|
||||
{ :reply, :ok, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state_name, _state_data) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state_name, state_data, _extra) do
|
||||
{ :ok, state_name, state_data }
|
||||
end
|
||||
|
||||
defoverridable [handle_event: 3, handle_sync_event: 4,
|
||||
handle_info: 3, terminate: 3, code_change: 4]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ defmodule HashDict do
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
@behaviour Dict
|
||||
use Dict.Behaviour
|
||||
|
||||
# A dictionary (key-value) implementation based on dynamic hashing.
|
||||
#
|
||||
@@ -107,11 +107,7 @@ defmodule HashDict do
|
||||
dict
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
def update!(dict, key, fun) when is_function(fun, 1) do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
dict
|
||||
@@ -125,13 +121,6 @@ defmodule HashDict do
|
||||
dict
|
||||
end
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
@@ -140,17 +129,6 @@ defmodule HashDict do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
def fetch!(dict, key) when is_tuple(dict) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
@@ -171,50 +149,22 @@ defmodule HashDict do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
lc [k|v] inlist bucket, do: { k, v }
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
dict_fold(dict, [], fn [k|_], acc -> [k|acc] end)
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
dict_fold(dict, [], fn [_|v], acc -> [v|acc] end)
|
||||
end
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
update(acc, k, v1, callback.(k, v1, &1))
|
||||
update(acc, k, v1, &callback.(k, v1, &1))
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
update(acc, k, v2, callback.(k, &1, v2))
|
||||
update(acc, k, v2, &callback.(k, &1, v2))
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) do
|
||||
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, callback.(k, &1, v2))
|
||||
end
|
||||
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
|
||||
super(dict, enumerable, callback)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
@@ -232,24 +182,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
end
|
||||
end
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
@@ -321,21 +253,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_equal?(dict1, dict2) do
|
||||
try do
|
||||
dict_fold(dict1, true, fn [key|value], acc ->
|
||||
case fetch(dict2, key) do
|
||||
{ :ok, ^value } ->
|
||||
acc
|
||||
_ ->
|
||||
throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
# Get value from the bucket
|
||||
|
||||
@@ -103,7 +103,7 @@ defmodule HashSet do
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], [&1|&2]) |> :lists.reverse
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
@@ -476,13 +476,6 @@ defmodule HashSet do
|
||||
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
@doc false
|
||||
def inspect_depth(trie(depth: d)), do: d
|
||||
@doc false
|
||||
def inspect_contract(trie(contract_on: c)), do: c
|
||||
@doc false
|
||||
def inspect_expand(trie(expand_on: e)), do: e
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
|
||||
+134
-49
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts, raw: false, limit: :infinity, pretty: false, width: 80
|
||||
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
@@ -19,8 +19,8 @@ defprotocol Inspect do
|
||||
receives an `Inspect.Opts` record as the second argument, it returns
|
||||
the underlying algebra document instead of the formatted string.
|
||||
|
||||
Many times, inspecting a structure can be implemented using functions
|
||||
of the existing entities. For example, here is `HashSet`'s `inspect`
|
||||
Many times, inspecting a structure can be implemented in function
|
||||
of existing entities. For example, here is `HashSet`'s `inspect`
|
||||
implementation:
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
@@ -33,13 +33,13 @@ defprotocol Inspect do
|
||||
|
||||
The `concat` function comes from `Inspect.Algebra` and it
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
printed), the document returned by `Kernel.inspect/2` and the
|
||||
other string `">"`.
|
||||
|
||||
Since regular strings are valid entities in an algebra document,
|
||||
an implementation of inspect may simply return a binary,
|
||||
an implementation of inspect may simply return a string,
|
||||
although that will devoid it of any pretty-printing.
|
||||
"""
|
||||
|
||||
@@ -86,10 +86,12 @@ defimpl Inspect, for: Atom do
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :[], :<<>>] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
<< ?:, ?", String.escape(binary, ?") :: binary, ?" >>
|
||||
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,11 +135,10 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Bitstrings and strings that contain non-
|
||||
printable characters are printed using the bitstring
|
||||
syntax.
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -145,46 +146,96 @@ defimpl Inspect, for: BitString do
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
iex> inspect(<<0,1,2>>)
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
"""
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
<< ?", String.escape(thing, ?") :: binary, ?" >>
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
as_bitstring(thing, opts)
|
||||
inspect_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing, opts) do
|
||||
as_bitstring(thing, opts)
|
||||
inspect_bitstring(thing, opts)
|
||||
end
|
||||
|
||||
## Escaping
|
||||
|
||||
@doc false
|
||||
def escape(other, char) do
|
||||
escape(other, char, <<>>)
|
||||
end
|
||||
|
||||
defp escape(<< char, t :: binary >>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
defp escape(<<?\a, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
defp escape(<<?\b, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
end
|
||||
defp escape(<<?\d, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
end
|
||||
defp escape(<<?\e, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
end
|
||||
defp escape(<<?\f, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
end
|
||||
defp escape(<<?\n, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
end
|
||||
defp escape(<<?\r, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
end
|
||||
defp escape(<<?\\, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
end
|
||||
defp escape(<<?\t, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
end
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, h >>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp as_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
"<<" <> each_bit(bitstring, opts.limit) <> ">>"
|
||||
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
each_bit(bitstring, opts.limit, "<<") <> ">>"
|
||||
end
|
||||
|
||||
defp each_bit(_, 0) do
|
||||
"..."
|
||||
defp each_bit(_, 0, acc) do
|
||||
acc <> "..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
|
||||
integer_to_binary(h) <> ", " <> each_bit(t, decrement(counter))
|
||||
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
|
||||
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter) do
|
||||
integer_to_binary(h)
|
||||
defp each_bit(<<h :: size(8)>>, _counter, acc) do
|
||||
acc <> integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter) do
|
||||
<<>>
|
||||
defp each_bit(<<>>, _counter, acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter) do
|
||||
defp each_bit(bitstring, _counter, acc) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
@@ -192,7 +243,7 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc %B"""
|
||||
@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
|
||||
@@ -214,11 +265,11 @@ defimpl Inspect, for: List do
|
||||
def inspect(thing, Inspect.Opts[] = opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
<< ?', String.escape(:unicode.characters_to_binary(thing), ?') :: binary, ?' >>
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -266,7 +317,7 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect(tuple, opts) do
|
||||
unless opts.raw do
|
||||
record_inspect(tuple, opts)
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -280,7 +331,7 @@ defimpl Inspect, for: Tuple do
|
||||
else
|
||||
surround_record(name, fields, tail, opts)
|
||||
end
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
@@ -300,22 +351,35 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, keyword(&1, opts))
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
concat(
|
||||
atom_to_binary(k, :utf8) <> ": ",
|
||||
atom_to_binary(k) <> ": ",
|
||||
Kernel.inspect(v, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Number do
|
||||
defimpl Inspect, for: Integer do
|
||||
@doc """
|
||||
Represents the number as a string.
|
||||
Represents the integer as a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Float do
|
||||
@doc """
|
||||
Floats are represented using the shortened, correctly rounded string
|
||||
that converts to float when read back with `binary_to_float/1`. This
|
||||
is done via the Erlang implementation of _Printing Floating-Point
|
||||
@@ -324,21 +388,17 @@ defimpl Inspect, for: Number do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
iex> inspect(1.0)
|
||||
"1.0"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _opts) do
|
||||
list_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
@@ -359,31 +419,56 @@ end
|
||||
defimpl Inspect, for: Function do
|
||||
def inspect(function, _opts) do
|
||||
fun_info = :erlang.fun_info(function)
|
||||
mod = fun_info[:module]
|
||||
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
"function(#{Inspect.Atom.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
|
||||
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
|
||||
else
|
||||
'#Fun' ++ rest = :erlang.fun_to_list(function)
|
||||
"#Function" <> list_to_binary(rest)
|
||||
case atom_to_list(mod) do
|
||||
'elixir_compiler_' ++ _ ->
|
||||
if function_exported?(mod, :__RELATIVE__, 0) do
|
||||
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
|
||||
else
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
_ ->
|
||||
default_inspect(mod, fun_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
case :binary.split(atom_to_binary(name), "-", [:global]) do
|
||||
["", name | _] -> name
|
||||
_ -> name
|
||||
end
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> list_to_binary(pid_to_list(pid))
|
||||
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
list_to_binary :erlang.port_to_list(port)
|
||||
iolist_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Reference do
|
||||
def inspect(ref, _opts) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> list_to_binary(rest)
|
||||
"#Reference" <> iolist_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
|
||||
|
||||
@@ -76,11 +76,33 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type doc :: :doc_nil | :doc_cons_t | :doc_nest_t | :doc_break_t | :doc_group_t | binary
|
||||
defrecordp :doc_cons, [left: :doc_nil, right: :doc_nil]
|
||||
defrecordp :doc_nest, [indent: 1, doc: :doc_nil]
|
||||
defrecordp :doc_break, [str: " "]
|
||||
defrecordp :doc_group, [doc: :doc_nil]
|
||||
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
|
||||
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
|
||||
defrecordp :doc_break, str: " " :: binary
|
||||
defrecordp :doc_group, doc: :doc_nil :: t
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if __CALLER__.in_guard? do
|
||||
do_is_doc(doc)
|
||||
else
|
||||
var = quote do: doc
|
||||
quote do
|
||||
unquote(var) = unquote(doc)
|
||||
unquote(do_is_doc(var))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
unquote(doc) |> is_binary or
|
||||
unquote(doc) |> is_integer or
|
||||
unquote(doc) == :doc_nil or
|
||||
(unquote(doc) |> is_tuple and
|
||||
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
@@ -106,14 +128,18 @@ defmodule Inspect.Algebra do
|
||||
"TastelessArtosis"
|
||||
|
||||
"""
|
||||
@spec concat(doc, doc) :: :doc_cons_t
|
||||
def concat(x, y), do: doc_cons(left: x, right: y)
|
||||
@spec concat(t, t) :: doc_cons_t
|
||||
def concat(x, y) when is_doc(x) and is_doc(y) do
|
||||
doc_cons(left: x, right: y)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates a list of documents.
|
||||
"""
|
||||
@spec concat([doc]) :: :doc_cons_t
|
||||
def concat(docs), do: folddoc(docs, concat(&1, &2))
|
||||
@spec concat([t]) :: doc_cons_t
|
||||
def concat(docs) do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
@@ -126,11 +152,16 @@ defmodule Inspect.Algebra do
|
||||
" 6"
|
||||
|
||||
"""
|
||||
@spec nest(doc, non_neg_integer) :: :doc_nest_t
|
||||
def nest(x, 0), do: x
|
||||
def nest(x, i) when is_integer(i), do: doc_nest(indent: i, doc: x)
|
||||
@spec nest(t, non_neg_integer) :: doc_nest_t
|
||||
def nest(x, 0) when is_doc(x) do
|
||||
x
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
def nest(x, i) when is_doc(x) and is_integer(i) do
|
||||
doc_nest(indent: i, doc: x)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
@@ -151,26 +182,26 @@ defmodule Inspect.Algebra do
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
"""
|
||||
@spec break(binary) :: :doc_break_t
|
||||
@spec break(binary) :: doc_break_t
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
|
||||
@spec break() :: :doc_break_t
|
||||
@spec break() :: doc_break_t
|
||||
def break(), do: doc_break(str: @break)
|
||||
|
||||
@doc """
|
||||
Inserts a break between two docs. See `break/1` for more info.
|
||||
"""
|
||||
@spec glue(doc, doc) :: :doc_cons_t
|
||||
@spec glue(t, t) :: doc_cons_t
|
||||
def glue(x, y), do: concat(x, concat(break, y))
|
||||
|
||||
@doc """
|
||||
Inserts a break, passed as the second argument, between two docs,
|
||||
the first and the third arguments.
|
||||
"""
|
||||
@spec glue(doc, binary, doc) :: :doc_cons_t
|
||||
@spec glue(t, binary, t) :: doc_cons_t
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
@@ -197,8 +228,10 @@ defmodule Inspect.Algebra do
|
||||
"Hello,\nA B"
|
||||
|
||||
"""
|
||||
@spec group(doc) :: :doc_group_t
|
||||
def group(d), do: doc_group(doc: d)
|
||||
@spec group(t) :: doc_group_t
|
||||
def group(d) when is_doc(d) do
|
||||
doc_group(doc: d)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts a mandatory single space between two document entities.
|
||||
@@ -210,10 +243,10 @@ defmodule Inspect.Algebra do
|
||||
"Hughes Wadler"
|
||||
|
||||
"""
|
||||
@spec space(doc, doc) :: :doc_cons_t
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
@@ -223,7 +256,7 @@ defmodule Inspect.Algebra do
|
||||
"Hughes\nWadler"
|
||||
|
||||
"""
|
||||
@spec line(doc, doc) :: :doc_cons_t
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
def line(x, y), do: concat(x, concat(@newline, y))
|
||||
|
||||
@doc """
|
||||
@@ -240,14 +273,14 @@ defmodule Inspect.Algebra do
|
||||
"A!B"
|
||||
|
||||
"""
|
||||
@spec folddoc([any], ((doc, [doc]) -> doc)) :: doc
|
||||
@spec folddoc([t], ((t, t) -> t)) :: t
|
||||
def folddoc([], _), do: empty
|
||||
def folddoc([doc], _), do: doc
|
||||
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
|
||||
|
||||
# Elixir conveniences
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Surrounds a document with characters.
|
||||
|
||||
Puts the document between left and right enclosing and nesting it.
|
||||
@@ -261,28 +294,28 @@ defmodule Inspect.Algebra do
|
||||
"[a\n b]"
|
||||
|
||||
"""
|
||||
@spec surround(binary, doc, binary) :: doc
|
||||
@spec surround(binary, t, binary) :: t
|
||||
def surround(left, doc, right) do
|
||||
group concat [left, nest(doc, @nesting), right]
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> doc)) :: doc
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
|
||||
def surround_many(left, [], right, _, _fun) do
|
||||
concat(left, right)
|
||||
end
|
||||
@@ -323,7 +356,7 @@ defmodule Inspect.Algebra do
|
||||
and returns the string representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec pretty(doc, non_neg_integer) :: binary
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
|
||||
render(sdoc)
|
||||
@@ -336,15 +369,15 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Records representing __simple__ documents, already on a fixed layout
|
||||
# Those are generalized by `sdoc` type.
|
||||
@type sdoc :: :s_nil | :s_text_t | :s_line_t
|
||||
defrecordp :s_text, [str: "", sdoc: :s_nil]
|
||||
defrecordp :s_line, [indent: 1, sdoc: :s_nil] # newline + spaces
|
||||
@type sdoc :: :s_nil | s_text_t | s_line_t
|
||||
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
|
||||
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
|
||||
|
||||
# Record representing the document mode to be rendered: __flat__ or __broken__
|
||||
@type mode :: :flat | :break
|
||||
# Record representing the document mode to be rendered: flat or broken
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{ integer, mode, doc }]) :: boolean
|
||||
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
|
||||
def fits?(:infinity, _), do: true # no pretty printing
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
@@ -357,7 +390,7 @@ defmodule Inspect.Algebra do
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
|
||||
@doc false
|
||||
@spec format(integer, integer, [{ integer, mode, doc }]) :: atom | tuple
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
|
||||
def format(_, _, []), do: :s_nil
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
@@ -367,7 +400,8 @@ defmodule Inspect.Algebra do
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
if fits?(w - k, t) do
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
s_text(str: s, sdoc: format(w, k, t))
|
||||
else
|
||||
s_line(indent: i, sdoc: format(w, i, t))
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
defmodule Integer do
|
||||
@moduledoc """
|
||||
Functions for working with integers.
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
|
||||
@doc """
|
||||
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.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@doc """
|
||||
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.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.parse("34")
|
||||
{34,""}
|
||||
iex> Integer.parse("34.5")
|
||||
{34,".5"}
|
||||
iex> Integer.parse("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { integer, binary } | :error
|
||||
def parse(<< ?-, bin :: binary >>) do
|
||||
case do_parse(bin) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
end
|
||||
end
|
||||
|
||||
def parse(<< ?+, bin :: binary >>) do
|
||||
do_parse(bin)
|
||||
end
|
||||
|
||||
def parse(bin) when is_binary(bin) do
|
||||
do_parse(bin)
|
||||
end
|
||||
|
||||
defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
|
||||
defp do_parse(_), do: :error
|
||||
|
||||
defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
|
||||
do_parse rest, 10 * acc + (char - ?0)
|
||||
end
|
||||
|
||||
defp do_parse(bitstring, acc) do
|
||||
{ acc, bitstring }
|
||||
end
|
||||
end
|
||||
+92
-77
@@ -1,50 +1,61 @@
|
||||
defexception IO.StreamError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"error during streaming: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Module responsible for doing IO. Many functions in this
|
||||
module expects an IO device and an io data encoded in UTF-8.
|
||||
Functions handling IO.
|
||||
|
||||
Many functions in this module expects an IO device as argument.
|
||||
An IO device must be a pid or an atom representing a process.
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcut to Erlang's `:standard_io` and `:standard_error`.
|
||||
shortcuts to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
An io data can be:
|
||||
The majority of the functions expect data encoded in UTF-8
|
||||
and will do a conversion to string, via the `String.Chars`
|
||||
protocol (as shown in typespecs).
|
||||
|
||||
* A list of integers representing a string. Any unicode
|
||||
character must be represented with one entry in the list,
|
||||
this entry being an integer with the codepoint value;
|
||||
|
||||
* A binary in which unicode characters are represented
|
||||
with many bytes (Elixir's default representation);
|
||||
|
||||
* A list of binaries or a list of char lists (as described above);
|
||||
|
||||
* If none of the above, `to_binary` is invoked in the
|
||||
given argument;
|
||||
The functions starting with `bin*` expects iodata as arguments,
|
||||
i.e. iolists or binaries with no particular encoding.
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@type chardata :: char_list | String.Chars.t
|
||||
@type nodata :: { :error, term } | :eof
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
defmacrop is_iolist(data) do
|
||||
quote do
|
||||
is_list(unquote(data)) or is_binary(unquote(data))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
* `:eof` - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device // group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), "")
|
||||
:io.get_line(map_dev(device), '')
|
||||
end
|
||||
|
||||
def read(device, count) when count >= 0 do
|
||||
:io.get_chars(map_dev(device), "", count)
|
||||
:io.get_chars(map_dev(device), '', count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -53,12 +64,13 @@ defmodule IO do
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
* `:eof` - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
@@ -75,21 +87,6 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def readline(device // group_leader()) do
|
||||
IO.write "[WARNING] IO.readline(device) is deprecated, please use IO.read(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binreadline(device // group_leader()) do
|
||||
IO.write "[WARNING] IO.binreadline(device) is deprecated, please use IO.binread(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
By default the device is the standard output.
|
||||
@@ -107,8 +104,9 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
@spec write(device, chardata) :: :ok
|
||||
def write(device // group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_iodata(item)
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -117,23 +115,25 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
def binwrite(device // group_leader(), item) do
|
||||
:file.write map_dev(device), to_iodata(item)
|
||||
@spec binwrite(device, iodata) :: :ok | { :error, term }
|
||||
def binwrite(device // group_leader(), item) when is_iolist(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the argument to the device, similarly to write
|
||||
but adds a new line at the end. The argument is expected
|
||||
Writes the argument to the device, similar to `write/2`,
|
||||
but adds a newline at the end. The argument is expected
|
||||
to be a chardata.
|
||||
"""
|
||||
@spec puts(device, chardata) :: :ok
|
||||
def puts(device // group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_iodata(item), ?\n]
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. A set of options can be given.
|
||||
followed by a newline. A set of options can be given.
|
||||
|
||||
It sets by default pretty printing to true and the
|
||||
width to be the width of the device, with a minimum
|
||||
@@ -144,6 +144,7 @@ defmodule IO do
|
||||
IO.inspect Process.list
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
def inspect(item, opts // []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
@@ -151,6 +152,7 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
|
||||
@@ -167,18 +169,21 @@ defmodule IO do
|
||||
|
||||
@doc """
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, count implies
|
||||
io device is a unicode device, `count` implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, the number of raw bytes. It returns:
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
* `:eof` - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
def getn(prompt, count // 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
@@ -191,12 +196,13 @@ defmodule IO do
|
||||
|
||||
@doc """
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, count implies
|
||||
io device is a unicode device, `count` implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, the number of raw bytes.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
"""
|
||||
@spec getn(device, chardata, pos_integer) :: chardata | nodata
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -205,66 +211,75 @@ defmodule IO do
|
||||
* `data` - The characters in the line terminated
|
||||
by a LF (or end of file).
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
* `:eof` - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
def gets(device // group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_iodata(prompt))
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line.
|
||||
iterated line by line if `:line` is given or by a given
|
||||
number of codepoints.
|
||||
|
||||
This reads the io as utf-8. Check out
|
||||
`IO.binstream/1` to handle the IO as a raw binary.
|
||||
This reads the IO as utf-8. Check out
|
||||
`IO.binstream/2` to handle the IO as a raw binary.
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example on how we mimic an echo server
|
||||
from the command line:
|
||||
|
||||
Enum.each IO.stream(:stdio), IO.write(&1)
|
||||
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
|
||||
|
||||
"""
|
||||
def stream(device) do
|
||||
stream(map_dev(device), &1, &2)
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line.
|
||||
iterated line by line or by a number of bytes. This
|
||||
reads the IO as a raw binary.
|
||||
|
||||
This reads the io as a raw binary.
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
"""
|
||||
def binstream(device) do
|
||||
binstream(map_dev(device), &1, &2)
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device, acc, fun) do
|
||||
case read(device, :line) do
|
||||
def stream(device, what, acc, fun) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
stream(device, fun.(data, acc), fun)
|
||||
stream(device, what, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device, acc, fun) do
|
||||
case binread(device, :line) do
|
||||
def binstream(device, what, acc, fun) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
binstream(device, fun.(data, acc), fun)
|
||||
binstream(device, what, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -273,6 +288,6 @@ defmodule IO do
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other), do: other
|
||||
|
||||
defp to_iodata(io) when is_list(io) or is_binary(io), do: io
|
||||
defp to_iodata(other), do: to_binary(other)
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
end
|
||||
|
||||
+32
-24
@@ -1,14 +1,14 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
quote bind_quoted: [name: name, code: code] do
|
||||
defmacro defsequence(name, code // "", terminator // "m") do
|
||||
quote bind_quoted: [name: name, code: code, terminator: terminator] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}m"
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}m", rest }
|
||||
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,9 +16,9 @@ end
|
||||
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
This module provides functionality to render ANSI escape sequences
|
||||
Functionality to render ANSI escape sequences
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
in text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
"""
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule IO.ANSI do
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
be displayed as intended.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
@@ -121,6 +121,13 @@ defmodule IO.ANSI do
|
||||
@doc "Not overlined"
|
||||
defsequence :not_overlined, 55
|
||||
|
||||
@doc "Send cursor home"
|
||||
defsequence :home, "", "H"
|
||||
|
||||
@doc "Clear screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
|
||||
# Catch spaces between codes
|
||||
defp escape_sequence(<< ?\s, rest :: binary >>) do
|
||||
escape_sequence(rest)
|
||||
@@ -131,24 +138,25 @@ defmodule IO.ANSI do
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
It will also append a %{reset} to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
It will also append a `%{reset}` to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
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?/0` function)
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit // terminal?) do
|
||||
@@ -160,23 +168,23 @@ defmodule IO.ANSI do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
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?/0` function)
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
|
||||
## Example
|
||||
## 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.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"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
@@ -201,7 +209,7 @@ defmodule IO.ANSI do
|
||||
do_escape(rest, false, emit, emitted, [x|acc])
|
||||
end
|
||||
defp do_escape("", false, _emit, emitted, acc) do
|
||||
{list_to_binary(Enum.reverse(acc)), emitted}
|
||||
{iolist_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
|
||||
defp do_escape_sequence(rest, emit, acc) do
|
||||
|
||||
+469
-545
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,26 @@
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc """
|
||||
Module responsible for controlling Elixir's CLI
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
defrecord Config, commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: []
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false
|
||||
|
||||
# This is the API invoked by Elixir boot process.
|
||||
@doc false
|
||||
@doc """
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
|
||||
argv = lc arg inlist argv, do: String.from_char_list!(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
:elixir_code_server.cast({ :argv, argv })
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, IO.puts(:stderr, &1))
|
||||
Enum.each(errors, &IO.puts(:stderr, &1))
|
||||
System.halt(1)
|
||||
end
|
||||
end, config.halt
|
||||
@@ -41,13 +41,6 @@ defmodule Kernel.CLI do
|
||||
at_exit(0)
|
||||
System.halt(0)
|
||||
end
|
||||
rescue
|
||||
exception ->
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
System.halt(1)
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
at_exit(reason)
|
||||
@@ -57,9 +50,7 @@ defmodule Kernel.CLI do
|
||||
System.halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
@@ -72,16 +63,9 @@ defmodule Kernel.CLI do
|
||||
lc hook inlist hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
rescue
|
||||
exception ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
catch
|
||||
kind, reason ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,13 +77,45 @@ defmodule Kernel.CLI do
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|hs], _ } when h == hd(list) ->
|
||||
new_config = config.update_errors ["#{h} : Unknown option" | &1]
|
||||
new_config = config.update_errors &["#{h} : Unknown option" | &1]
|
||||
callback.(hs, new_config)
|
||||
{ new_list, new_config } ->
|
||||
callback.(new_list, new_config)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_error(:error, exception, trace) do
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, format_stacktrace(trace)
|
||||
end
|
||||
|
||||
defp print_error(kind, reason, trace) do
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts :stderr, format_stacktrace(trace)
|
||||
end
|
||||
|
||||
defp format_stacktrace(stack) do
|
||||
Exception.format_stacktrace(prune_stacktrace(stack))
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module]
|
||||
|
||||
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
end
|
||||
|
||||
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp prune_stacktrace([h|t]) do
|
||||
[h|prune_stacktrace(t)]
|
||||
end
|
||||
|
||||
defp prune_stacktrace([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Process shared options
|
||||
|
||||
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
@@ -108,17 +124,17 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands [{:app, h}|&1]
|
||||
process_shared t, config.update_commands &[{:app, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
@@ -126,21 +142,25 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval, h}|&1]
|
||||
process_shared t, config.update_commands &[{:eval, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands [{:require, h}|&1]
|
||||
process_shared t, config.update_commands &[{:require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require, h}|&1]
|
||||
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(list, config) do
|
||||
{ list, config }
|
||||
end
|
||||
@@ -160,15 +180,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
shared_option? list, config, &process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -187,33 +207,37 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, config.verbose_compile(true)
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_compiler(&1, &2)
|
||||
shared_option? list, config, &process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile [pattern|&1]
|
||||
process_compiler t, config.update_compile &[pattern|&1]
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands([{:compile, config.compile}|&1]), [] }
|
||||
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
@@ -233,15 +257,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_iex(&1, &2)
|
||||
shared_option? list, config, &process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -253,16 +277,14 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive? do
|
||||
Node.set_cookie(binary_to_atom(h))
|
||||
:ok
|
||||
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
|
||||
else
|
||||
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:eval, expr}, _config) when is_binary(expr) do
|
||||
Code.eval_string(expr, [])
|
||||
:ok
|
||||
wrapper fn -> Code.eval_string(expr, []) end
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
@@ -276,8 +298,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = find_elixir_executable(file) do
|
||||
Code.require_file(exec)
|
||||
:ok
|
||||
wrapper fn -> Code.require_file(exec) end
|
||||
else
|
||||
{ :error, "-S : Could not find executable #{file}" }
|
||||
end
|
||||
@@ -285,8 +306,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
Code.require_file(file)
|
||||
:ok
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{ :error, "No file named #{file}" }
|
||||
end
|
||||
@@ -295,11 +315,10 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Enum.map files, Code.require_file(&1)
|
||||
:ok
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
else
|
||||
{ :error, "-r : No files matched pattern #{pattern}" }
|
||||
end
|
||||
@@ -308,11 +327,10 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Kernel.ParallelRequire.files(files)
|
||||
:ok
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
else
|
||||
{ :error, "-pr : No files matched pattern #{pattern}" }
|
||||
end
|
||||
@@ -321,19 +339,26 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, Path.wildcard(&1)
|
||||
files = Enum.uniq(List.concat(files))
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
files = Enum.map patterns, &Path.wildcard(&1)
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
end
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp wrapper(fun) do
|
||||
fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
defp find_elixir_executable(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
# If we are on Windows, the executable is going to be
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# the usage of aliases, imports and requires in the Elixir scope.
|
||||
#
|
||||
# The implementation simply stores dispatch information in an
|
||||
# ETS table and then consults this table once compilation is done.
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
|
||||
@import 2
|
||||
@alias 3
|
||||
|
||||
@doc """
|
||||
Returns all remotes linked to in this lexical scope.
|
||||
"""
|
||||
def remotes(pid) do
|
||||
ets = :gen_server.call(to_pid(pid), :ets, @timeout)
|
||||
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
|
||||
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
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_import(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_import, module, line, warn })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_alias(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_alias, module, line, warn })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def remote_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :remote_dispatch, module })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def import_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :import_dispatch, module })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def alias_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :alias_dispatch, module })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def collect_unused_imports(pid) do
|
||||
unused(pid, @import)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def collect_unused_aliases(pid) do
|
||||
unused(pid, @alias)
|
||||
end
|
||||
|
||||
defp unused(pid, pos) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.foldl(fn
|
||||
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
|
||||
[{ module, :erlang.element(pos, tuple) }|acc]
|
||||
_, acc ->
|
||||
acc
|
||||
end, [], ets) |> Enum.sort
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
|
||||
def init([]) do
|
||||
{ :ok, :ets.new(:lexical, [:protected]) }
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
add_module(d, module)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :import_dispatch, module }, d) do
|
||||
add_dispatch(d, module, @import)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :alias_dispatch, module }, d) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, module, line, warn }, d) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_alias, module, line, warn }, d) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
|
||||
# In the table we keep imports and aliases.
|
||||
# If the value is false, it was not imported/aliased
|
||||
# If the value is true, it was imported/aliased
|
||||
# If the value is a line, it was imported/aliased and has a pending warning
|
||||
defp add_module(d, module) do
|
||||
:ets.insert_new(d, { module, false, false })
|
||||
end
|
||||
|
||||
defp add_dispatch(d, module, pos) do
|
||||
:ets.update_element(d, module, { pos, true })
|
||||
end
|
||||
|
||||
defp add_directive(d, module, line, warn, pos) do
|
||||
add_module(d, module)
|
||||
marker = if warn, do: line, else: true
|
||||
:ets.update_element(d, module, { pos, marker })
|
||||
end
|
||||
end
|
||||
@@ -1,14 +1,8 @@
|
||||
defexception Kernel.CompilationError, message: "compilation failed"
|
||||
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias :orddict, as: OrdDict
|
||||
|
||||
@moduledoc """
|
||||
A module responsible for compiling files in parallel.
|
||||
"""
|
||||
|
||||
defmacrop default_callback, do: quote(do: fn x -> x end)
|
||||
|
||||
@doc """
|
||||
Compiles the given files.
|
||||
|
||||
@@ -17,45 +11,58 @@ defmodule Kernel.ParallelCompiler do
|
||||
the current file stops being compiled until the dependency is
|
||||
resolved.
|
||||
|
||||
If there is any error during compilation or if warnings_as_errors
|
||||
is set to true and there is a warning, this function will fail
|
||||
If there is an error during compilation or if `warnings_as_errors`
|
||||
is set to `true` and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
A callback that is invoked every time a file is compiled
|
||||
with its name can be optionally given as argument.
|
||||
This function receives a set of callbacks as options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the file
|
||||
* `: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.
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
spawn_compilers(files, nil, callback)
|
||||
def files(files, callbacks // [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given files to the given path.
|
||||
Read files/2 for more information.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
|
||||
spawn_compilers(files, path, callback)
|
||||
def files_to_path(files, path, callbacks // [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
end
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
:elixir_code_server.cast(:reset_warnings)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], 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) do
|
||||
:ok -> result
|
||||
:error -> raise Kernel.CompilationError, [], []
|
||||
:error -> raise CompileError, [], []
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(files, output, callback, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(files, output, callback, waiting, queued, schedulers, result)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], output, callback, waiting, queued, schedulers, result) do
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
child = spawn_link fn ->
|
||||
@@ -76,40 +83,50 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _output, _callback, [], [], _schedulers, result), do: result
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
|
||||
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, but queue and waiting are not full or do not match
|
||||
defp spawn_compilers([], output, callback, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], output, callback, waiting, queued, schedulers, result)
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, output, callback, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :compiled, child, file } ->
|
||||
callback.(file)
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
new_queued = List.keydelete(queued, child, 0)
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, output, callback, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, _child, module, binary } ->
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
child <- { ref, :ack }
|
||||
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [{module, binary}|result]
|
||||
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
|
||||
new_result = [module|result]
|
||||
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
|
||||
{ :waiting, child, on } ->
|
||||
new_waiting = OrdDict.store(child, on, waiting)
|
||||
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
|
||||
new_waiting = :orddict.store(child, on, waiting)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
|
||||
@@ -10,6 +10,8 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
A callback that is invoked every time a file is required
|
||||
can be optionally given as argument.
|
||||
|
||||
Returns the modules generated by each required file.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
@@ -45,8 +47,8 @@ defmodule Kernel.ParallelRequire do
|
||||
:erlang.process_flag(:error_handler, handler)
|
||||
|
||||
try do
|
||||
new = Code.require_file(h)
|
||||
parent <- { :required, self, new, h }
|
||||
new = Code.require_file(h) || []
|
||||
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self, kind, reason, System.stacktrace }
|
||||
@@ -58,9 +60,9 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, schedulers, result) do
|
||||
receive do
|
||||
{ :required, child, new, file } ->
|
||||
{ :required, child, mods, file } ->
|
||||
callback.(file)
|
||||
spawn_requires(files, List.delete(waiting, child), callback, schedulers, (new || []) ++ result)
|
||||
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Kernel.RecordRewriter do
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, field == &1) do
|
||||
if index = Enum.find_index(fields, &(field == &1)) do
|
||||
optimize_record_accessor_call(line, res, kind, field, index, left, args)
|
||||
end
|
||||
nil ->
|
||||
@@ -352,7 +352,7 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
|
||||
defp extract_vars({ :tuple, _, args }, vars) do
|
||||
[Enum.map(args, extract_vars(&1, []))|vars]
|
||||
[Enum.map(args, &extract_vars(&1, []))|vars]
|
||||
end
|
||||
|
||||
defp extract_vars(_, vars) do
|
||||
|
||||
@@ -9,38 +9,33 @@ defmodule Kernel.SpecialForms do
|
||||
to define data structures, respectively tuples, lists and binaries.
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__MODULE__`,
|
||||
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
|
||||
information about Elixir's compilation environment and can only
|
||||
`__FILE__`, `__ENV__`, `__DIR__` and `__CALLER__`). Pseudo variables
|
||||
return information about Elixir's compilation environment and can only
|
||||
be read, never assigned to.
|
||||
|
||||
Finally, it also documents 3 special forms (`__block__`,
|
||||
`__scope__` and `__aliases__`), which are not intended to be
|
||||
called directly by the developer but they appear in quoted
|
||||
contents since they are essential in Elixir's constructions.
|
||||
contents since they are essential in Elixir's constructs.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a new tuple.
|
||||
|
||||
Only two item tuples are considered literals in Elixir.
|
||||
Therefore all other tuples are represented in the AST
|
||||
as a call to the special form `:{}`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
iex> quote do: { 1, 2, 3 }
|
||||
{ :{}, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro :{}.(args)
|
||||
|
||||
@doc """
|
||||
Defines a new list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [ 1, 2, 3 ]
|
||||
[ 1, 2, 3 ]
|
||||
|
||||
"""
|
||||
defmacro :[].(args)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
|
||||
@@ -51,41 +46,50 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Bitstring types
|
||||
|
||||
A bitstring may contain many parts and those may have
|
||||
specific types. Most of the time, Elixir will figure out
|
||||
the part's type and won't require any work from you:
|
||||
A bitstring is made of many segments. Each segment has a
|
||||
type, which defaults to integer:
|
||||
|
||||
iex> <<102, "oo">>
|
||||
"foo"
|
||||
iex> <<1, 2, 3>>
|
||||
<<1, 2, 3>>
|
||||
|
||||
Above we have two parts: the first is an integer and the
|
||||
second is a binary. If we use any other Elixir expression,
|
||||
Elixir can no longer guess the type:
|
||||
Elixir also accepts by default the segment to be a literal
|
||||
string or a literal char list, which are by expanded to integers:
|
||||
|
||||
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:
|
||||
|
||||
iex> rest = "oo"
|
||||
...> <<102, rest>>
|
||||
iex> <<102, rest>>
|
||||
** (ArgumentError) argument error
|
||||
|
||||
When a variable or expression is given as a binary part,
|
||||
Elixir defaults the type of that part to an unsigned
|
||||
little-endian integer. In the example above, since we haven't
|
||||
specified a type, Elixir expected an integer but we passed a
|
||||
binary, resulting in `ArgumentError`. We can solve this by
|
||||
explicitly tagging it as a binary:
|
||||
We can solve this by explicitly tagging it as a binary:
|
||||
|
||||
<<102, rest :: binary>>
|
||||
|
||||
The type can be integer, float, binary, bytes, bitstring,
|
||||
bits, utf8, utf16 or utf32, e.g.:
|
||||
The type can be integer, float, bitstring/bits, binary/bytes,
|
||||
utf8, utf16 or utf32, e.g.:
|
||||
|
||||
<<102 :: float, rest :: binary>>
|
||||
|
||||
Integer can be any arbitrary precision integer. A float is an
|
||||
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.
|
||||
|
||||
The utf8, utf16, and utf32 types are for UTF code points.
|
||||
The utf8, utf16, and utf32 types are for UTF code points. They
|
||||
can also be applied to literal strings and char lists:
|
||||
|
||||
iex> <<"foo" :: utf16>>
|
||||
<<0,102,0,111,0,111>>
|
||||
|
||||
The bits type is an alias for bitstring. The bytes type is an
|
||||
alias for binary.
|
||||
@@ -111,7 +115,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Signedness is only relevant on integers.
|
||||
|
||||
The endianness of a part can be big, little or native (the
|
||||
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:
|
||||
|
||||
@@ -126,9 +130,9 @@ defmodule Kernel.SpecialForms do
|
||||
Endianness only makes sense for integers and some UTF code
|
||||
point types (utf16 and utf32).
|
||||
|
||||
Finally, we can also specify size and unit for each part. The
|
||||
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 part:
|
||||
the segment:
|
||||
|
||||
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
|
||||
"foo"
|
||||
@@ -141,7 +145,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
In the example above, the first two expressions matches
|
||||
because the string "foo" takes 24 bits and we are matching
|
||||
against a part of 24 bits as well, 8 of which are taken by
|
||||
against a segment of 24 bits as well, 8 of which are taken by
|
||||
the integer 102 and the remaining 16 bits are specified on
|
||||
the rest. On the last example, we expect a rest with size 32,
|
||||
which won't match.
|
||||
@@ -170,7 +174,120 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro :<<>>.(args)
|
||||
|
||||
@doc """
|
||||
`alias` is used to setup atom aliases, often useful with modules names.
|
||||
Defines a remote call or an alias.
|
||||
|
||||
The dot (`.`) in Elixir can be used for remote calls:
|
||||
|
||||
iex> String.downcase("FOO")
|
||||
"foo"
|
||||
|
||||
In this example above, we have used `.` to invoke `downcase` in the
|
||||
`String` alias, passing "FOO" as argument. We can also use the dot
|
||||
for creating aliases:
|
||||
|
||||
iex> Hello.World
|
||||
Hello.World
|
||||
|
||||
This time, we have joined two aliases, defining the final alias
|
||||
`Hello.World`.
|
||||
|
||||
## Syntax
|
||||
|
||||
The right side of `.` may be a word starting in upcase, which represents
|
||||
an alias, a word starting with lowercase or underscore, any valid language
|
||||
operator or any name wrapped in single- or double-quotes. Those are all valid
|
||||
examples:
|
||||
|
||||
iex> Kernel.Sample
|
||||
Kernel.Sample
|
||||
iex> Kernel.length([1,2,3])
|
||||
3
|
||||
iex> Kernel.+(1, 2)
|
||||
3
|
||||
iex> Kernel."length"([1,2,3])
|
||||
3
|
||||
iex> Kernel.'+'(1, 2)
|
||||
3
|
||||
|
||||
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
|
||||
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(Kernel, :+, [1,2])
|
||||
3
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
forms. When the right side starts with a lowercase letter (or
|
||||
underscore):
|
||||
|
||||
iex> quote do: String.downcase("FOO")
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
Notice we have an inner tuple, containing the atom `:.` representing
|
||||
the dot as first element:
|
||||
|
||||
{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}
|
||||
|
||||
This tuple follows the general quoted expression structure in Elixir,
|
||||
with the name as first argument, some keyword list as metadata as second,
|
||||
and the number of arguments as third. In this case, the arguments is the
|
||||
alias `String` and the atom `:downcase`. The second argument is **always**
|
||||
an atom:
|
||||
|
||||
iex> quote do: String."downcase"("FOO")
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
The tuple containing `:.` is wrapped in another tuple, which actually
|
||||
represents the function call, and has `"FOO"` as argument.
|
||||
|
||||
When the right side is an alias (i.e. starts with uppercase), we get instead:
|
||||
|
||||
iex> quote do: Hello.World
|
||||
{:__aliases__, [alias: false], [:Hello, :World]}
|
||||
|
||||
We got into more details about aliases in the `__aliases__` special form
|
||||
documentation.
|
||||
|
||||
## Unquoting
|
||||
|
||||
We can also use unquote to generate a remote call in a quoted expression:
|
||||
|
||||
iex> x = :downcase
|
||||
iex> quote do: String.unquote(x)("FOO")
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
Similar to `Kernel."HELLO"`, `unquote(x)` will always generate a remote call,
|
||||
independent of the value of `x`. To generate an alias via the quoted expression,
|
||||
one needs to rely on `Module.concat/2`:
|
||||
|
||||
iex> x = Sample
|
||||
iex> quote do: Module.concat(String, unquote(x))
|
||||
{{:., [], [{:__aliases__, [alias: false], [:Module]}, :concat]}, [],
|
||||
[{:__aliases__, [alias: false], [:String]}, Sample]}
|
||||
|
||||
"""
|
||||
defmacro (:.).(left, right)
|
||||
|
||||
@doc """
|
||||
`alias` is used to setup aliases, often useful with modules names.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -180,12 +297,12 @@ defmodule Kernel.SpecialForms do
|
||||
alias MyKeyword, as: Keyword
|
||||
end
|
||||
|
||||
In the example above, we have set up `MyKeyword` to be alias
|
||||
In the example above, we have set up `MyKeyword` to be aliased
|
||||
as `Keyword`. So now, any reference to `Keyword` will be
|
||||
automatically replaced by `MyKeyword`.
|
||||
|
||||
In case one wants to access the original `Keyword`, it can be done
|
||||
by accessing Elixir:
|
||||
by accessing `Elixir`:
|
||||
|
||||
Keyword.values #=> uses MyKeyword.values
|
||||
Elixir.Keyword.values #=> uses Keyword.values
|
||||
@@ -204,12 +321,23 @@ defmodule Kernel.SpecialForms do
|
||||
`import`, `require` and `alias` are called directives and all
|
||||
have lexical scope. This means you can set up aliases inside
|
||||
specific functions and it won't affect the overall scope.
|
||||
|
||||
## Warnings
|
||||
|
||||
If you alias a module and you don't use the alias, Elixir is
|
||||
going to issue a warning implying the alias is not being used.
|
||||
|
||||
In case the alias is generated automatically by a macro,
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
|
||||
@doc """
|
||||
`require` is used to require the presence of external
|
||||
modules so macros can be invoked.
|
||||
Requires a given module to be compiled and loaded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -237,6 +365,8 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro require(module, opts)
|
||||
|
||||
@doc """
|
||||
Imports function and macros from other modules.
|
||||
|
||||
`import` allows one to easily access functions or macros from
|
||||
others modules without using the qualified name.
|
||||
|
||||
@@ -258,23 +388,27 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
import List
|
||||
|
||||
A developer can change this behavior to include all macros and
|
||||
functions, regardless if it starts with underscore, by passing
|
||||
`:all` as first argument:
|
||||
A developer can filter to import only macros or functions via
|
||||
the only option:
|
||||
|
||||
import :all, List
|
||||
import List, only: :functions
|
||||
import List, only: :macros
|
||||
|
||||
It can also be customized to import only all functions or
|
||||
all macros:
|
||||
|
||||
import :functions, List
|
||||
import :macros, List
|
||||
|
||||
Alternatively, Elixir allows a developer to specify `:only`
|
||||
or `:except` as a fine grained control on what to import (or
|
||||
not):
|
||||
Alternatively, Elixir allows a developer to pass pairs of
|
||||
name/arities to `:only` or `:except` as a fine grained control
|
||||
on what to import (or not):
|
||||
|
||||
import List, only: [flatten: 1]
|
||||
import String, except: [split: 2]
|
||||
|
||||
Notice that calling `except` for a previously declared `import`
|
||||
simply filters the previously imported elements. For example:
|
||||
|
||||
import List, only: [flatten: 1, keyfind: 3]
|
||||
import List, except: [flatten: 1]
|
||||
|
||||
After the two import calls above, only `List.keyfind/3` will be
|
||||
imported.
|
||||
|
||||
## Lexical scope
|
||||
|
||||
@@ -296,15 +430,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
In the example above, we imported macros from `MyMacros`,
|
||||
replacing the original `if/2` implementation by our own
|
||||
during that specific function. All other functions in that
|
||||
within that specific function. All other functions in that
|
||||
module will still be able to use the original one.
|
||||
|
||||
## Alias/Require shortcut
|
||||
|
||||
All imported modules are also required by default. `import`
|
||||
also accepts `as:` as an option so it automatically sets up
|
||||
an alias. Please check `alias` for more information.
|
||||
|
||||
## Warnings
|
||||
|
||||
If you import a module and you don't use any of the imported
|
||||
@@ -328,22 +456,25 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro import(module, opts)
|
||||
|
||||
@doc """
|
||||
Returns the current environment information as a `Macro.Env`
|
||||
record. In the environment you can access the current filename,
|
||||
Returns the current environment information as a `Macro.Env[]` record.
|
||||
|
||||
In the environment you can access the current filename,
|
||||
line numbers, set up aliases, the current function and others.
|
||||
"""
|
||||
defmacro __ENV__
|
||||
|
||||
@doc """
|
||||
Returns the current module name as an atom or nil otherwise.
|
||||
Although the module can be accessed in the __ENV__, this macro
|
||||
Returns the current module name as an atom or `nil` otherwise.
|
||||
|
||||
Although the module can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __MODULE__
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
Although the file can be accessed in the __ENV__, this macro
|
||||
|
||||
Although the file can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __FILE__
|
||||
@@ -369,7 +500,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
|
||||
The tuple above represents a function call to sum passing 1, 2 and
|
||||
The tuple above represents a function call to `sum` passing 1, 2 and
|
||||
3 as arguments. The tuple elements are:
|
||||
|
||||
* The first element of the tuple is always an atom or
|
||||
@@ -382,14 +513,14 @@ defmodule Kernel.SpecialForms do
|
||||
## Options
|
||||
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control which quote is
|
||||
inside another quote and want to control what quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
* `:location` - When set to `:keep`, keeps the current line and file from quote.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
|
||||
unquote is automatically disabled;
|
||||
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -405,7 +536,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Hygiene and context
|
||||
|
||||
Elixir macros are hygienic via means of deferred resolution.
|
||||
Elixir macros are hygienic by means of deferred resolution.
|
||||
This means variables, aliases and imports defined inside the
|
||||
quoted refer to the context that defined the macro and not
|
||||
the context where the macro is expanded.
|
||||
@@ -424,14 +555,17 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Notice how the third element of the returned tuple is the
|
||||
module name. This means that the variable is associated to the
|
||||
ContextSample module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
`ContextSample` module and only code generated by this module
|
||||
will be able to access that particular `world` variable. Not
|
||||
only variables hygiene, but also imports and aliases hygiene
|
||||
are delimited by context.
|
||||
|
||||
While this means macros from the same module could have
|
||||
conflicting variables, it also allows different quotes from
|
||||
the same module to access them.
|
||||
|
||||
The context can be disabled or changed by explicitly setting
|
||||
the context option. All hygiene mechanisms are based on such
|
||||
the `context` option. All hygiene mechanisms are based on such
|
||||
context and we are going to explore each of them in the following
|
||||
subsections.
|
||||
|
||||
@@ -469,31 +603,47 @@ defmodule Kernel.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
|
||||
It is important to understand that quoted variables are scoped
|
||||
to the module they are defined. That said, even if two modules
|
||||
define the same quoted variable `a`, their values are going
|
||||
to be independent:
|
||||
Note that you cannot even access variables defined by the same
|
||||
module unless you explicitly give it a context:
|
||||
|
||||
defmodule Hygiene1 do
|
||||
defmacro var1 do
|
||||
quote do: a = 1
|
||||
defmodule Hygiene do
|
||||
defmacro write do
|
||||
quote do
|
||||
a = 1
|
||||
end
|
||||
end
|
||||
|
||||
defmacro read do
|
||||
quote do
|
||||
a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Hygiene2 do
|
||||
defmacro var2 do
|
||||
quote do: a = 2
|
||||
Hygiene.write
|
||||
Hygiene.read
|
||||
#=> no variable a
|
||||
|
||||
For such, you can explicitly pass the current module scope as
|
||||
argument:
|
||||
|
||||
defmodule ContextHygiene do
|
||||
defmacro write do
|
||||
quote do
|
||||
var!(a, ContextHygiene) = 1
|
||||
end
|
||||
end
|
||||
|
||||
defmacro read do
|
||||
quote do
|
||||
var!(a, ContextHygiene)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Calling macros `var1` and `var2` are not going to change their
|
||||
each other values for `a`. This is useful because quoted
|
||||
variables from different modules cannot conflict. If you desire
|
||||
to explicitly access a variable from another module, we can once
|
||||
again use `var!` macro, but explicitly passing a second argument:
|
||||
|
||||
# Access the variable a from Hygiene1
|
||||
quote do: var!(a, Hygiene1) = 2
|
||||
ContextHygiene.write
|
||||
ContextHygiene.read
|
||||
#=> 1
|
||||
|
||||
Hygiene for variables can be disabled overall as:
|
||||
|
||||
@@ -550,10 +700,9 @@ defmodule Kernel.SpecialForms do
|
||||
#=> "world"
|
||||
end
|
||||
|
||||
|
||||
## Hygiene in imports
|
||||
|
||||
Similar to aliases, imports in Elixir hygienic. Consider the
|
||||
Similar to aliases, imports in Elixir are hygienic. Consider the
|
||||
following code:
|
||||
|
||||
defmodule Hygiene do
|
||||
@@ -576,7 +725,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Elixir is smart enough to delay the resolution to the latest
|
||||
moment possible. So, if you call `size("hello")` inside quote,
|
||||
but no `size/1` function is available, it is then expanded on
|
||||
but no `size/1` function is available, it is then expanded in
|
||||
the caller:
|
||||
|
||||
defmodule Lazy do
|
||||
@@ -597,45 +746,45 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Lazy.return_size #=> 2
|
||||
|
||||
As in aliases, imports expansion can be explicitly disabled
|
||||
As in aliases, import expansion can be explicitly disabled
|
||||
via the `hygiene: [imports: false]` option.
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
One of Elixir goals is to provide proper stacktrace whenever there is an
|
||||
exception. In order to work properly with macros, the default behavior
|
||||
in quote is to not set a line. When a macro is invoked and the quoted
|
||||
expressions is expanded, the call site line is inserted.
|
||||
When defining functions via macros, developers have the option of
|
||||
choosing if runtime errors will be reported from the caller or from
|
||||
inside the quote. Let's see an example:
|
||||
|
||||
This is a good behavior for the majority of the cases, except if the macro
|
||||
is defining new functions. Consider this example:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
end
|
||||
|
||||
`GenServer.Behaviour` defines new functions in our `MyServer` module.
|
||||
However, if there is an exception in any of these functions, we want
|
||||
the stacktrace to point to the `GenServer.Behaviour` and not the line
|
||||
that calls `use GenServer.Behaviour`. For this reason, there is an
|
||||
option called `:location` that when set to `:keep` keeps the original
|
||||
line and file lines instead of setting them to 0:
|
||||
|
||||
quote location: :keep do
|
||||
def handle_call(request, _from, state) do
|
||||
{ :reply, :undef, state }
|
||||
# adder.ex
|
||||
defmodule Adder do
|
||||
@doc "Defines a function that adds two numbers"
|
||||
defmacro defadd do
|
||||
quote location: :keep do
|
||||
def add(a, b), do: a + b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
It is important to warn though that `location: :keep` evaluates the
|
||||
code as if it was defined inside `GenServer.Behaviour` file, in
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
# sample.ex
|
||||
defmodule Sample do
|
||||
import Adder
|
||||
defadd
|
||||
end
|
||||
|
||||
When using `location: :keep` and invalid arguments are given to
|
||||
`Sample.add/2`, the stacktrace information will point to the file
|
||||
and line inside the quote. Without `location: :keep`, the error is
|
||||
reported to where `defadd` was invoked.
|
||||
|
||||
Note that `location: :keep` evaluates the code as if it was defined
|
||||
inside the `Adder` file, in particular, the macro `__FILE__` and
|
||||
exceptions happening inside the quote will always point to the file
|
||||
where `Added` was defined.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
Elixir quote/unquote mechanisms provides a functionality called
|
||||
unquote fragments. Unquote fragments provide an easy to generate
|
||||
unquote fragments. Unquote fragments provide an easy way to generate
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
@@ -670,7 +819,7 @@ defmodule Kernel.SpecialForms do
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practive, we want to avoid doing work at compilation time as
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
@@ -702,8 +851,8 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
In fact, the `:binding` option is recommended every time one
|
||||
desires to inject a value into the quote.
|
||||
In fact, the `:bind_quoted` option is recommended every time
|
||||
one desires to inject a value into the quote.
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@@ -718,10 +867,6 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
|
||||
If the context is false, it is not stored in any particular
|
||||
context and the variable is not shared in between clauses.
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
@@ -813,7 +958,10 @@ defmodule Kernel.SpecialForms do
|
||||
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
Note: Differently from Erlang, Elixir comprehensions filters
|
||||
Variable assignments in the `do` block of the comprehension
|
||||
are not reflected outside of the block.
|
||||
|
||||
Note: Unlike Erlang, Elixir comprehension filters
|
||||
never behave as guards when it comes to errors. Errors in
|
||||
list comprehensions will always be raised. Consider this
|
||||
Erlang example:
|
||||
@@ -830,10 +978,11 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro lc(args)
|
||||
|
||||
@doc """
|
||||
Defines a bit comprehension. It follows the same syntax as
|
||||
a list comprehension but expects each element returned to
|
||||
be a bitstring. For example, here is how to remove all
|
||||
spaces from a string:
|
||||
Defines a bit comprehension.
|
||||
|
||||
It follows the same syntax and behaviour as a list comprehension
|
||||
but expects each element returned to be a bitstring. For example,
|
||||
here is how to remove all spaces from a string:
|
||||
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
"helloworld"
|
||||
@@ -842,6 +991,21 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro bc(args)
|
||||
|
||||
@doc """
|
||||
Defines an anonymous function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> add = fn a, b -> a + b end
|
||||
iex> add.(1, 2)
|
||||
3
|
||||
|
||||
"""
|
||||
name = :fn
|
||||
defmacro unquote(name)(clauses)
|
||||
|
||||
@doc """
|
||||
Internal special form for block expressions.
|
||||
|
||||
This is the special form used whenever we have a block
|
||||
of expressions in Elixir. This special form is private
|
||||
and should not be invoked directly:
|
||||
@@ -853,34 +1017,113 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro __block__(args)
|
||||
|
||||
@doc """
|
||||
This is the special form used whenever we have to temporarily
|
||||
change the scope information of a block. Used when `quote` is
|
||||
invoked with `location: :keep` to execute a given block as if
|
||||
it belonged to another file.
|
||||
Captures an anonymous function.
|
||||
|
||||
The most common format to capture a function is via module,
|
||||
name and arity:
|
||||
|
||||
iex> fun = &Kernel.is_atom/1
|
||||
iex> fun.(:atom)
|
||||
true
|
||||
iex> fun.("string")
|
||||
false
|
||||
|
||||
Local functions, including private ones, and imported ones
|
||||
can also be captured by omitting the module name:
|
||||
|
||||
&local_function/1
|
||||
|
||||
A capture also allows the captured functions to be partially
|
||||
applied, for example:
|
||||
|
||||
iex> fun = &is_record(&1, Range)
|
||||
iex> fun.(1..3)
|
||||
true
|
||||
|
||||
In the example above, we use &1 as a placeholder, generating
|
||||
a function with one argument. We could also use `&2` and `&3`
|
||||
to delimit more arguments:
|
||||
|
||||
iex> fun = &is_record(&1, &2)
|
||||
iex> fun.(1..3, Range)
|
||||
true
|
||||
|
||||
Since operators are calls, they are also supported, although
|
||||
they require explicit parentheses around:
|
||||
|
||||
iex> fun = &(&1 + &2)
|
||||
iex> fun.(1, 2)
|
||||
3
|
||||
|
||||
And even more complex call expressions:
|
||||
|
||||
iex> fun = &(&1 + &2 + &3)
|
||||
iex> fun.(1, 2, 3)
|
||||
6
|
||||
|
||||
Record-like calls are also allowed:
|
||||
|
||||
iex> fun = &(&1.first)
|
||||
iex> fun.(1..3)
|
||||
1
|
||||
|
||||
Remember tuple and lists are represented as calls in the AST and
|
||||
therefore are also allowed:
|
||||
|
||||
iex> fun = &{&1, &2}
|
||||
iex> fun.(1, 2)
|
||||
{ 1, 2 }
|
||||
|
||||
iex> fun = &[&1|&2]
|
||||
iex> fun.(1, 2)
|
||||
[1|2]
|
||||
|
||||
Anything that is not a call is not allowed though, examples:
|
||||
|
||||
# An atom is not a call
|
||||
&:foo
|
||||
|
||||
# A var is not a call
|
||||
var = 1
|
||||
&var
|
||||
|
||||
# A block expression is not a call
|
||||
&(foo(&1, &2); &3 + &4)
|
||||
|
||||
"""
|
||||
name = :&
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
Internal special form for modifying scope information.
|
||||
|
||||
Used when `quote` is invoked with `location: :keep` to execute
|
||||
a given block as if it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro __scope__(opts, args)
|
||||
|
||||
@doc """
|
||||
This is the special form used to hold aliases information.
|
||||
Internal special form to hold aliases information.
|
||||
|
||||
It is usually compiled to an atom:
|
||||
|
||||
quote do: Foo.Bar #=>
|
||||
{ :__aliases__, [], [:Foo,:Bar] }
|
||||
iex> quote do: Foo.Bar
|
||||
{:__aliases__, [alias: false], [:Foo, :Bar]}
|
||||
|
||||
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
|
||||
unambiguously identified by the operator `:.`. For example:
|
||||
|
||||
quote do: Foo.bar #=>
|
||||
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
|
||||
iex> quote do: Foo.bar
|
||||
{{:., [], [{:__aliases__, [alias: false], [:Foo]}, :bar]}, [], []}
|
||||
|
||||
Whenever an expression iterator sees a `:.` as the tuple key,
|
||||
it can be sure that it represents a call and the second argument
|
||||
is the list is an atom.
|
||||
in the list is an atom.
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
@@ -888,12 +1131,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
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;
|
||||
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,[],:quoted},:Bar]}
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
@@ -159,7 +159,7 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_type(module, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
{ kind, export } =
|
||||
case kind do
|
||||
:type -> { :type, true }
|
||||
@@ -167,12 +167,35 @@ defmodule Kernel.Typespec do
|
||||
:opaque -> { :opaque, true }
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
arity = length(vars)
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
if export, do:
|
||||
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{ name, arity }])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
type
|
||||
end
|
||||
|
||||
defp define_doc(caller, kind, name, arity, export) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
else
|
||||
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
"""
|
||||
@@ -193,7 +216,7 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
@@ -204,7 +227,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -213,7 +236,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -255,6 +278,26 @@ defmodule Kernel.Typespec do
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all type docs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first element is the pair of type
|
||||
name and arity and the second element is the documentation.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
@@ -264,7 +307,8 @@ defmodule Kernel.Typespec do
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
def beam_types(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
@@ -278,7 +322,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -291,7 +335,8 @@ defmodule Kernel.Typespec do
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
@spec beam_specs(module | binary) :: [tuple] | nil
|
||||
def beam_specs(module) when is_atom(module) or is_binary(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@@ -301,10 +346,11 @@ defmodule Kernel.Typespec do
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
The module has to have a corresponding beam file on the disk
|
||||
which can be located by the runtime system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
@spec beam_callbacks(module | binary) :: [tuple] | nil
|
||||
def beam_callbacks(module) when is_atom(module) or is_binary(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
end
|
||||
|
||||
@@ -312,8 +358,8 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
_ ->
|
||||
[]
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -322,7 +368,7 @@ defmodule Kernel.Typespec do
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
[]
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -368,7 +414,7 @@ defmodule Kernel.Typespec do
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
define_type(caller.module, kind, type)
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
+151
-21
@@ -4,13 +4,23 @@ defmodule Keyword do
|
||||
of the tuple is an atom and the second element can be
|
||||
any value.
|
||||
|
||||
A keyword may have duplicated keys, so it is not strictly
|
||||
A keyword may have duplicated keys so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
allows it to behave exactly as a dictionary. For example,
|
||||
`Keyword.get` will get the first entry matching the given
|
||||
key, regardless if duplicated entries exist. Similarly,
|
||||
`Keyword.put` and `Keyword.delete` ensure all duplicated
|
||||
entries for a given key are removed when invoked.
|
||||
behaves exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` 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
|
||||
entries.
|
||||
|
||||
Since a keyword list is simply a list, all the operations defined
|
||||
in `Enum` and `List` can also be applied.
|
||||
"""
|
||||
|
||||
@type key :: atom
|
||||
@@ -23,16 +33,12 @@ defmodule Keyword do
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) when is_list(enum) do
|
||||
enum
|
||||
end
|
||||
|
||||
def from_enum(enum) do
|
||||
Enum.map(enum, fn(x) -> x end)
|
||||
Enum.to_list(enum)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not
|
||||
Checks if the given argument is a keywords list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
@@ -362,23 +368,23 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, &1 * 2)
|
||||
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, &1 * 2)
|
||||
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec update(t, key, (value -> value)) :: t | no_return
|
||||
def update([{key, value}|keywords], key, fun) do
|
||||
@spec update!(t, key, (value -> value)) :: t | no_return
|
||||
def update!([{key, value}|keywords], key, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|keywords], key, fun) do
|
||||
[e|update(keywords, key, fun)]
|
||||
def update!([{_, _} = e|keywords], key, fun) do
|
||||
[e|update!(keywords, key, fun)]
|
||||
end
|
||||
|
||||
def update([], key, _fun) when is_atom(key) do
|
||||
def update!([], key, _fun) when is_atom(key) do
|
||||
raise(KeyError, key: key)
|
||||
end
|
||||
|
||||
@@ -388,10 +394,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
iex> Keyword.update([a: 1], :b, 11, &(&1 * 2))
|
||||
[a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@@ -407,4 +413,128 @@ defmodule Keyword do
|
||||
def update([], key, initial, _fun) when is_atom(key) do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the given keywords in two given the given keys.
|
||||
Duplicated keys are preserved in the split keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
|
||||
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { [], [] }
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
case :lists.member(k, keys) do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
|
||||
{ Enum.reverse(take), Enum.reverse(drop) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the given keys from the dict.
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.take(d, [:a, :c, :e])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.take(d, [:a, :c, :e])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
removing only the first entry.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.pop [a: 1], :a
|
||||
{1,[]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1, a: 2], :a
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without that particular ocurrence
|
||||
of `key`.
|
||||
|
||||
Duplicated entries are not removed.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :a
|
||||
{1,[]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1, a: 2], :a
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
end
|
||||
end
|
||||
|
||||
+107
-53
@@ -5,43 +5,13 @@ defmodule List do
|
||||
favor using the Enum API instead of List.
|
||||
|
||||
A decision was taken to delegate most functions to
|
||||
Erlang's standard lib but following Elixir's convention
|
||||
Erlang's standard library but follow Elixir's convention
|
||||
of receiving the target (in this case, a list) as the
|
||||
first argument.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Given a list of lists, concatenates the sublists into a single list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat([[1, [2], 3], [4], [5, 6]])
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
:lists.append(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list on the right with the list on the left.
|
||||
|
||||
This function produces the same result the `++` operator. The only difference
|
||||
is a minor optimization: when the first list contains only one element, we
|
||||
simply add it as a head to the second list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat([1, 2, 3], [4, 5, 6])
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat(list, elements) when is_list(list) and is_list(elements) do
|
||||
list ++ elements
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the given item from the list. Returns a list without the item.
|
||||
If the item occurs more than once in the list, just the first occurrence
|
||||
@@ -52,13 +22,16 @@ defmodule List do
|
||||
iex> List.delete([1, 2, 3], 1)
|
||||
[2,3]
|
||||
|
||||
iex> List.delete([1, 2, 2, 3], 2)
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
def delete(list, item) do
|
||||
:lists.delete(item, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Duplicates the given element n times in a list.
|
||||
Duplicates the given element `n` times in a list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -73,23 +46,29 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Flattens the given `list` of nested lists. An optional
|
||||
tail can be given that will be added at the end of
|
||||
the flattened list.
|
||||
Flattens the given `list` of nested lists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten([1, [[2], 3]])
|
||||
[1,2,3]
|
||||
|
||||
iex> List.flatten([1, [[2], 3]], [4, 5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
def flatten(list) do
|
||||
:lists.flatten(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Flattens the given `list` of nested lists.
|
||||
The list `tail` will be added at the end of
|
||||
the flattened list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten([1, [[2], 3]], [4, 5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
def flatten(list, tail) do
|
||||
:lists.flatten(list, tail)
|
||||
end
|
||||
@@ -126,7 +105,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last element in `list` or nil if the `list` is empty.
|
||||
Returns the last element in `list` or `nil` if `list` is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -146,7 +125,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
where the item at position `position` matches with the
|
||||
where the item at `position` in the tuple matches the
|
||||
given `item`.
|
||||
|
||||
## Examples
|
||||
@@ -166,9 +145,9 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns true if there is
|
||||
a tuple where the item at position `position` matches
|
||||
with the given `item`.
|
||||
Receives a list of tuples and returns `true` if there is
|
||||
a tuple where the item at `position` in the tuple matches
|
||||
the given `item`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -188,7 +167,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and replaces the item
|
||||
identified by `key` at position `pos` if it exists.
|
||||
identified by `key` at `position` if it exists.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -200,9 +179,26 @@ defmodule List do
|
||||
:lists.keyreplace(key, position + 1, list, new_tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and sorts the items
|
||||
at `position` of the tuples. The sort is stable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.keysort([a: 5, b: 1, c: 3], 1)
|
||||
[b: 1, c: 3, a: 5]
|
||||
|
||||
iex> List.keysort([a: 5, c: 1, b: 3], 0)
|
||||
[a: 5, b: 3, c: 1]
|
||||
|
||||
"""
|
||||
def keysort(list, position) do
|
||||
:lists.keysort(position + 1, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and replaces the item
|
||||
identified by `key` at position `pos`. If the item
|
||||
identified by `key` at `position`. If the item
|
||||
does not exist, it is added to the end of the list.
|
||||
|
||||
## Examples
|
||||
@@ -210,6 +206,9 @@ defmodule List do
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
@@ -217,8 +216,8 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and deletes the first tuple
|
||||
where the item at position `position` matches with the
|
||||
given `item`. Returns the new tuple.
|
||||
where the item at `position` matches the
|
||||
given `item`. Returns the new list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -239,13 +238,19 @@ defmodule List do
|
||||
@doc """
|
||||
Wraps the argument in a list.
|
||||
If the argument is already a list, returns the list.
|
||||
If the argument is nil, returns an empty list.
|
||||
If the argument is `nil`, returns an empty list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.wrap("hello")
|
||||
["hello"]
|
||||
|
||||
iex> List.wrap([1, 2, 3])
|
||||
[1,2,3]
|
||||
|
||||
iex> List.wrap(nil)
|
||||
[]
|
||||
|
||||
"""
|
||||
def wrap(list) when is_list(list) do
|
||||
list
|
||||
@@ -290,12 +295,12 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map tuple_to_list(&1), zip(list)
|
||||
:lists.map &tuple_to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with an inserted value at specified index. Note that the index
|
||||
is capped at the list length and that negative indicies wraps around at the
|
||||
Returns a list with `value` inserted at the specified `index`. Note that `index`
|
||||
is capped at the list length. Negative indices indicate an offset from the
|
||||
end of the list.
|
||||
|
||||
## Examples
|
||||
@@ -307,19 +312,68 @@ defmodule List do
|
||||
[1, 2, 3, 0]
|
||||
|
||||
iex> List.insert_at([1, 2, 3], -1, 0)
|
||||
[1, 2, 0, 3]
|
||||
[1, 2, 3, 0]
|
||||
|
||||
iex> List.insert_at([1, 2, 3], -10, 0)
|
||||
[0, 1, 2, 3]
|
||||
|
||||
"""
|
||||
def insert_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_insert_at(list, length(list) + index, value)
|
||||
do_insert_at(list, length(list) + index + 1, value)
|
||||
else
|
||||
do_insert_at(list, index, value)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with a replaced value at the specified `index`. Negative indices
|
||||
indicate an offset from the end of the list. If `index` is out of bounds, the
|
||||
original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.replace_at([1, 2, 3], 0, 0)
|
||||
[0, 2, 3]
|
||||
|
||||
iex> List.replace_at([1, 2, 3], 10, 0)
|
||||
[1, 2, 3]
|
||||
|
||||
iex> List.replace_at([1, 2, 3], -1, 0)
|
||||
[1, 2, 0]
|
||||
|
||||
iex> List.replace_at([1, 2, 3], -10, 0)
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
def replace_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_replace_at(list, length(list) + index, value)
|
||||
else
|
||||
do_replace_at(list, index, value)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# replace_at
|
||||
|
||||
defp do_replace_at([], _index, _value) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_replace_at(list, index, _value) when index < 0 do
|
||||
list
|
||||
end
|
||||
|
||||
defp do_replace_at([_old|rest], 0, value) do
|
||||
[ value | rest ]
|
||||
end
|
||||
|
||||
defp do_replace_at([h|t], index, value) do
|
||||
[ h | do_replace_at(t, index - 1, value) ]
|
||||
end
|
||||
|
||||
# insert_at
|
||||
|
||||
defp do_insert_at([], _index, value) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defprotocol List.Chars do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
The List.Chars protocol is responsible for
|
||||
converting a structure to a list (only if applicable).
|
||||
The only function required to be implemented is
|
||||
@@ -9,8 +9,6 @@ defprotocol List.Chars do
|
||||
by Kernel invokes this protocol.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Atom, Number, Record]
|
||||
|
||||
def to_char_list(thing)
|
||||
end
|
||||
|
||||
@@ -19,20 +17,34 @@ defimpl List.Chars, for: Atom do
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: BitString do
|
||||
def to_char_list(bitstring), do: bitstring_to_list(bitstring)
|
||||
@doc """
|
||||
Returns the given binary converted to a char list.
|
||||
"""
|
||||
def to_char_list(thing) when is_binary(thing) do
|
||||
String.to_char_list!(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: thing,
|
||||
description: "cannot convert a bitstring to a char list"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: List do
|
||||
def to_char_list(list), do: list
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Number do
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when is_integer(thing) do
|
||||
defimpl List.Chars, for: Integer do
|
||||
def to_char_list(thing) do
|
||||
integer_to_list(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Float do
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
|
||||
+38
-35
@@ -2,6 +2,10 @@ defmodule ListDict do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on lists of two-items tuples.
|
||||
|
||||
This dictionary is only recommended for keeping a small amount
|
||||
of values. Other dict alternatives are more viable for keeping
|
||||
any other amount than a handful.
|
||||
|
||||
For more information about the functions and their APIs, please
|
||||
consult the `Dict` module.
|
||||
"""
|
||||
@@ -38,28 +42,25 @@ defmodule ListDict do
|
||||
length(dict)
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
:lists.keymember(key, 1, dict)
|
||||
end
|
||||
def has_key?(dict, key)
|
||||
def has_key?([{ key, _ }|_], key), do: true
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
end
|
||||
end
|
||||
def get(dict, key, default // nil)
|
||||
def get([{ key, value }|_], key, _default), do: value
|
||||
def get([{ _, _ }|t], key, default), do: get(t, key, default)
|
||||
def get([], _key, default), do: default
|
||||
|
||||
def fetch(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
def fetch(dict, key)
|
||||
def fetch([{ key, value }|_], key), do: { :ok, value }
|
||||
def fetch([{ _, _ }|t], key), do: fetch(t, key)
|
||||
def fetch([], _key), do: :error
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,33 +73,35 @@ defmodule ListDict do
|
||||
end
|
||||
|
||||
def put_new(dict, key, val) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, _ } -> dict
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
lc { k, _ } = tuple inlist dict, key != k, do: tuple
|
||||
end
|
||||
def delete(dict, key)
|
||||
def delete([{ key, _ }|t], key), do: t
|
||||
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
|
||||
def delete([], _key), do: []
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, fun) do
|
||||
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end)
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce enum, dict, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
@@ -109,15 +112,15 @@ defmodule ListDict do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
def update([{key, value}|dict], key, fun) do
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|dict], key, fun) do
|
||||
[e|update(dict, key, fun)]
|
||||
def update!([{_, _} = e|dict], key, fun) do
|
||||
[e|update!(dict, key, fun)]
|
||||
end
|
||||
|
||||
def update([], key, _fun) do
|
||||
def update!([], key, _fun) do
|
||||
raise(KeyError, key: key)
|
||||
end
|
||||
|
||||
@@ -136,7 +139,7 @@ defmodule ListDict do
|
||||
def empty(_dict), do: []
|
||||
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) == :lists.keysort(1, other)
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
def to_list(dict), do: dict
|
||||
|
||||
+236
-169
@@ -5,40 +5,89 @@ defmodule Macro do
|
||||
This module provides conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST) node"
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
|
||||
@doc """
|
||||
Returns a list of binary operators. This is available
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
@doc false
|
||||
defmacro binary_ops do
|
||||
[
|
||||
:===, :!==,
|
||||
[ :===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^
|
||||
]
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of unary operators. This is available
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
@doc false
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
::: -> {:right, 30}
|
||||
:when -> {:right, 40}
|
||||
o when o in [:inlist, :inbits] -> {:left, 50}
|
||||
:// -> {:right, 60}
|
||||
:| -> {:left, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
:.. -> {:left, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
o when o in [:<>] -> {:right, 230}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expresion representing a possible definition
|
||||
Breaks a pipeline expression into a list. Raises if
|
||||
the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({ :|> , _, [left, right] }) do
|
||||
[left|unpipe(right)]
|
||||
end
|
||||
|
||||
def unpipe(other) do
|
||||
[other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pipes the given `expr` in to the `call_expr` as the
|
||||
argument in the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer // 0)
|
||||
|
||||
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
|
||||
{ call, line, List.insert_at([], integer, expr) }
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, args }, integer) when is_list(args) do
|
||||
{ call, line, List.insert_at(args, integer, expr) }
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
raise ArgumentError,
|
||||
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representing a possible definition
|
||||
and extracts its arguments. It returns a tuple with the
|
||||
function name and the arguments list or `:error` if not
|
||||
a valid call syntax.
|
||||
|
||||
This is useful for macros that want to provide the same
|
||||
arguments syntax available in def/defp/defmacro and friends.
|
||||
argument syntax available in def/defp/defmacro and friends.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -48,6 +97,7 @@ defmodule Macro do
|
||||
extract_args(quote do: 1.(1, 2, 3)) == :error
|
||||
|
||||
"""
|
||||
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
|
||||
def extract_args(expr) do
|
||||
:elixir_clauses.extract_args(expr)
|
||||
end
|
||||
@@ -56,7 +106,7 @@ defmodule Macro do
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `Macro.escape/2`
|
||||
One may pass `unquote: true` to `escape/2`
|
||||
which leaves unquote statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
|
||||
@@ -72,14 +122,16 @@ defmodule Macro do
|
||||
1
|
||||
|
||||
"""
|
||||
@spec escape(term) :: Macro.t
|
||||
@spec escape(term, Keyword.t) :: Macro.t
|
||||
def escape(expr, opts // []) do
|
||||
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_binary/2` for information on how to customize
|
||||
Check `unescape_string/2` for information on how to customize
|
||||
the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
@@ -87,23 +139,25 @@ defmodule Macro do
|
||||
also escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%b` and others).
|
||||
(like `%r`, `%b` and others) which receive a raw, unescaped
|
||||
string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.unescape_binary("example\\n")
|
||||
iex> Macro.unescape_string("example\\n")
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
"""
|
||||
def unescape_binary(chars) do
|
||||
@spec unescape_string(String.t) :: String.t
|
||||
def unescape_string(chars) do
|
||||
:elixir_interpolation.unescape_chars(chars)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Unescape the given chars according to the map given.
|
||||
Check `unescape_binary/1` if you want to use the same map
|
||||
Check `unescape_string/1` if you want to use the same map
|
||||
as Elixir single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
@@ -124,7 +178,7 @@ defmodule Macro do
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(e), do: e
|
||||
|
||||
If the `unescape_map` function returns false. The char is
|
||||
If the `unescape_map` function returns `false`. The char is
|
||||
not escaped and `\` is kept in the char list.
|
||||
|
||||
## Octals
|
||||
@@ -134,23 +188,24 @@ defmodule Macro do
|
||||
|
||||
## Hex
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
Hexadecimals will by default be escaped unless the map function
|
||||
returns false for ?x.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the unescape_map defined above is easy:
|
||||
|
||||
Macro.unescape_binary "example\\n", unescape_map(&1)
|
||||
Macro.unescape_string "example\\n", &unescape_map(&1)
|
||||
|
||||
"""
|
||||
def unescape_binary(chars, map) do
|
||||
@spec unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
|
||||
def unescape_string(chars, map) do
|
||||
:elixir_interpolation.unescape_chars(chars, map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
Check `unescape_binary/1` and `unescape_binary/2` for more
|
||||
Check `unescape_string/1` and `unescape_string/2` for more
|
||||
information about unescaping.
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
@@ -158,24 +213,20 @@ defmodule Macro do
|
||||
sigils. Check the implementation of `Kernel.sigil_b`
|
||||
for examples.
|
||||
"""
|
||||
@spec unescape_tokens([Macro.t]) :: [Macro.t]
|
||||
def unescape_tokens(tokens) do
|
||||
:elixir_interpolation.unescape_tokens(tokens)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
Check `unescape_tokens/1` and `unescape_binary/2` for more information.
|
||||
Check `unescape_tokens/1` and `unescape_string/2` for more information.
|
||||
"""
|
||||
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
|
||||
def unescape_tokens(tokens, map) do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_binary(tree) do
|
||||
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
|
||||
to_string(tree)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given expression to a binary.
|
||||
|
||||
@@ -185,176 +236,213 @@ defmodule Macro do
|
||||
"foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_string(tree)
|
||||
@spec to_string(Macro.t) :: String.t
|
||||
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
|
||||
def to_string(tree, fun // fn(_ast, string) -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom }) when is_atom(atom) do
|
||||
atom_to_binary(var, :utf8)
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
fun.(ast, atom_to_binary(var))
|
||||
end
|
||||
|
||||
# Aliases
|
||||
def to_string({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_string(&1))
|
||||
def to_string({ :__aliases__, _, refs } = ast, fun) do
|
||||
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_string({ :__block__, _, [expr] }) do
|
||||
to_string(expr)
|
||||
def to_string({ :__block__, _, [expr] } = ast, fun) do
|
||||
fun.(ast, to_string(expr, fun))
|
||||
end
|
||||
|
||||
def to_string({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_string(expr), "\n "
|
||||
"(\n " <> block <> "\n)"
|
||||
def to_string({ :__block__, _, _ } = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(ast, fun), "\n "
|
||||
fun.(ast, "(\n " <> block <> "\n)")
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_string({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
|
||||
def to_string({ :<<>>, _, args } = ast, fun) do
|
||||
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
|
||||
"<" <> rest -> "<< <" <> rest <> " >>"
|
||||
rest -> "<<" <> rest <> ">>"
|
||||
end)
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_string({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
|
||||
end
|
||||
|
||||
# List containers
|
||||
def to_string({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] })
|
||||
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
"fn " <> arrow_to_string(arrow) <> " end"
|
||||
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_string(block) <> "\nend"
|
||||
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
|
||||
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_string(block), "\n "
|
||||
"fn\n " <> block <> "\nend"
|
||||
end
|
||||
|
||||
# Partial call
|
||||
def to_string({ :&, _, [num] }) do
|
||||
"&#{num}"
|
||||
def to_string({ :fn, _, [block] } = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(block, fun), "\n "
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_string({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_string(arrow, true) <> ")"
|
||||
def to_string({ :->, _, _ } = ast, fun) do
|
||||
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_string(left) <> " #{op} " <> op_to_string(right)
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in binary_ops do
|
||||
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
|
||||
end
|
||||
|
||||
# Splat when
|
||||
def to_string({ :when, _, args }) do
|
||||
{ left, right } = :elixir_tree_helpers.split_last(args)
|
||||
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
|
||||
def to_string({ :when, _, args } = ast, fun) do
|
||||
{ left, right } = :elixir_utils.split_last(args)
|
||||
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_string({ :not, _, [arg] }) do
|
||||
"not " <> to_string(arg)
|
||||
def to_string({ :not, _, [arg] } = ast, fun) do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_string(arg)
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_string({ target, _, args }) when is_list(args) do
|
||||
{ list, last } = :elixir_tree_helpers.split_last(args)
|
||||
case is_kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
|
||||
false -> call_to_string_with_args(target, args)
|
||||
end
|
||||
def to_string({ target, _, args } = ast, fun) when is_list(args) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
fun.(ast, case is_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
|
||||
|
||||
# Two-item tuples
|
||||
def to_string({ left, right }) do
|
||||
to_string({ :{}, [], [left, right] })
|
||||
def to_string({ left, right }, fun) do
|
||||
to_string({ :{}, [], [left, right] }, fun)
|
||||
end
|
||||
|
||||
# Lists
|
||||
def to_string(list) when is_list(list) do
|
||||
to_string({ :[], [], list })
|
||||
def to_string(list, fun) when is_list(list) do
|
||||
if Keyword.keyword?(list) do
|
||||
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
|
||||
else
|
||||
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
|
||||
end
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other), do: inspect(other, raw: true)
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
|
||||
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(other), do: call_to_string(other)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
|
||||
defp call_to_string(other), do: to_string(other)
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(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)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_string(&1))
|
||||
call_to_string(target) <> "(" <> args <> ")"
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
target = call_to_string(target, fun)
|
||||
|
||||
case last != [] and Keyword.keyword?(last) do
|
||||
true ->
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
if list != [], do: args = args <> ", "
|
||||
args = args <> kw_list_to_string(last, fun)
|
||||
target <> "(" <> args <> ")"
|
||||
false ->
|
||||
args = Enum.map_join(args, ", ", &to_string(&1, fun))
|
||||
target <> "(" <> args <> ")"
|
||||
end
|
||||
end
|
||||
|
||||
defp kw_blocks_to_string(kw) do
|
||||
defp kw_blocks_to_string(kw, fun) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
|
||||
false -> acc
|
||||
end
|
||||
end) <> "end"
|
||||
end
|
||||
|
||||
defp kw_block_to_string(key, value) do
|
||||
block = adjust_new_lines block_to_string(value), "\n "
|
||||
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
|
||||
defp kw_block_to_string(key, value, fun) do
|
||||
block = adjust_new_lines block_to_string(value, fun), "\n "
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string({ :->, _, exprs }) do
|
||||
defp block_to_string({ :->, _, exprs }, fun) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
|
||||
left = comma_join_or_empty_paren(left, fun, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_string({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_string(&1))
|
||||
defp block_to_string({ :__block__, _, exprs }, fun) do
|
||||
Enum.map_join(exprs, "\n", &to_string(&1, fun))
|
||||
end
|
||||
|
||||
defp block_to_string(other), do: to_string(other)
|
||||
defp block_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_string(expr) <> ")"
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp op_to_string(expr), do: to_string(expr)
|
||||
defp parenthise(expr, fun) do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp arrow_to_string({ :->, _, pairs }, paren // false) do
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
|
||||
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
|
||||
{ _, prec } = binary_op_props(op)
|
||||
cond do
|
||||
parent_prec < prec -> to_string(expr, fun)
|
||||
parent_prec > prec -> parenthise(expr, fun)
|
||||
true ->
|
||||
# parent_prec == prec, so look at associativity.
|
||||
if parent_assoc == side do
|
||||
to_string(expr, fun)
|
||||
else
|
||||
parenthise(expr, fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, paren)
|
||||
left <> "-> " <> to_string(right)
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp comma_join_or_empty_paren([], true), do: "() "
|
||||
defp comma_join_or_empty_paren([], false), do: ""
|
||||
defp comma_join_or_empty_paren([], _fun, true), do: "() "
|
||||
defp comma_join_or_empty_paren([], _fun, false), do: ""
|
||||
|
||||
defp comma_join_or_empty_paren(left, _) do
|
||||
Enum.map_join(left, ", ", to_string(&1)) <> " "
|
||||
defp comma_join_or_empty_paren(left, fun, _) do
|
||||
Enum.map_join(left, ", ", &to_string(&1, fun)) <> " "
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
@@ -371,13 +459,13 @@ defmodule Macro do
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (__FILE__, __MODULE__, etc);
|
||||
* Module attributes reader (@foo);
|
||||
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
|
||||
* Module attributes reader (`@foo`);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `Macro.expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `Macro.expand/2` for expansion
|
||||
until the node no longer represents a macro and `Macro.expand_all/2`
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `expand/2` for expansion
|
||||
until the node no longer represents a macro and `expand_all/2`
|
||||
for recursive expansion.
|
||||
|
||||
## Examples
|
||||
@@ -441,18 +529,23 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand_once(aliases, env) do
|
||||
expand_once(aliases, env, nil) |> elem(0)
|
||||
def expand_once(ast, env) do
|
||||
elem(expand_once(ast, env, nil), 0)
|
||||
end
|
||||
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
atom when is_atom(atom) -> { atom, true, cache }
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
|
||||
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> { :elixir_aliases.concat(aliases), true, cache }
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
@@ -530,16 +623,16 @@ defmodule Macro do
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
:lists.any(&match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Check `Macro.expand_once/2` for more information on how
|
||||
expansion works and `Macro.expand_all/2` for recursive expansion.
|
||||
a macro. Check `expand_once/2` for more information on how
|
||||
expansion works and `expand_all/2` for recursive expansion.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand(tree, env, nil) |> elem(0)
|
||||
elem(expand(tree, env, nil), 0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
@@ -555,36 +648,10 @@ defmodule Macro do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Then it expands all of its children recursively.
|
||||
|
||||
The documentation for `Macro.expand_once/2` goes into more detail
|
||||
on how expansion works. Keep in mind that `Macro.expand_all/2` is
|
||||
naive as it doesn't mutate the environment. Take this example:
|
||||
|
||||
quoted = quote do: __MODULE__
|
||||
Macro.to_string Macro.expand_all(quoted, __ENV__)
|
||||
#=> "nil"
|
||||
|
||||
The example above, works as expected. Outside of a module,
|
||||
`__MODULE__` returns nil. Now consider this variation:
|
||||
|
||||
quoted = quote do
|
||||
defmodule Foo, do: __MODULE__
|
||||
end
|
||||
Macro.to_string Macro.expand_all(quoted, __ENV__)
|
||||
#=> "defmodule(Foo) do\n nil\nend"
|
||||
|
||||
One would expect `__MODULE__` to be expanded to `Foo`
|
||||
but that is not the case since all the expansion is done
|
||||
based on the environment `__ENV__`. This behaviour is on
|
||||
purpose, as the proper expansion would require passing through
|
||||
the whole Elixir compiler by actually executing the code, which
|
||||
is beyond the scope of this function.
|
||||
"""
|
||||
@doc false
|
||||
def expand_all(tree, env) do
|
||||
expand_all(tree, env, nil) |> elem(0)
|
||||
IO.write "Macro.expand_all/2 is deprecated, please avoid recursive code expansion\n#{Exception.format_stacktrace}"
|
||||
elem(expand_all(tree, env, nil), 0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
@@ -605,7 +672,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
|
||||
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
@@ -613,8 +680,8 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
Recurs the quoted expression checking if all sub-terms are
|
||||
safe (i.e. they represent data structures and don't actually
|
||||
evaluate code) and returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{ :unsafe, term }`.
|
||||
"""
|
||||
@@ -622,7 +689,7 @@ defmodule Macro do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
@@ -631,7 +698,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
@@ -34,14 +34,15 @@ defmodule Macro.Env do
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom }]
|
||||
@type lexical_tracker :: pid
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases, :vars]
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires, :functions,
|
||||
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases, vars: vars]
|
||||
macro_aliases: aliases, vars: vars, lexical_tracker: lexical_tracker]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+45
-28
@@ -5,7 +5,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc %B'''
|
||||
@moduledoc %S'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
@@ -29,7 +29,7 @@ defmodule Module do
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__after_compile__/2`.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@after_compile __MODULE__
|
||||
@@ -51,7 +51,7 @@ defmodule Module do
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@before_compile __MODULE__
|
||||
@@ -67,7 +67,7 @@ defmodule Module do
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@behaviour gen_event
|
||||
@@ -85,13 +85,13 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
|
||||
def myfun(arg) do
|
||||
to_binary(arg)
|
||||
to_string(arg)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -106,7 +106,7 @@ defmodule Module do
|
||||
|
||||
Can be invoked more than once.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@@ -129,7 +129,7 @@ defmodule Module do
|
||||
|
||||
Accepts a string. Can be used more than once.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@@ -147,7 +147,7 @@ defmodule Module do
|
||||
`@moduledoc false` will make the module invisible to the
|
||||
documentation extraction tools like ExDoc.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
@@ -159,8 +159,7 @@ defmodule Module do
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked after each function or macro in the current
|
||||
module is defined. This makes it easy to annotate and customize
|
||||
functions.
|
||||
module is defined. Useful when annotating functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take 6 arguments:
|
||||
@@ -178,10 +177,13 @@ defmodule Module do
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can't provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time.
|
||||
Note that you can\'t provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time. Finally, since
|
||||
the `on_definition` hook is executed inside the context of the defined
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
can only be a function, not a macro.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
@@ -198,7 +200,7 @@ defmodule Module do
|
||||
@on_definition { H, :on_def }
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_binary(arg)
|
||||
"Hello" <> to_string(arg)
|
||||
end
|
||||
|
||||
def hello(_) do
|
||||
@@ -214,7 +216,7 @@ defmodule Module do
|
||||
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
|
||||
loading of the module will be aborted.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
@@ -236,7 +238,7 @@ defmodule Module do
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
@@ -266,7 +268,6 @@ defmodule Module do
|
||||
|
||||
It is possible to query a module at runtime to find out which functions and
|
||||
macros it defines, extract its docstrings, etc. See `__info__/1`.
|
||||
|
||||
'''
|
||||
|
||||
@doc """
|
||||
@@ -463,10 +464,24 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function. It expects
|
||||
the module the function belongs to, the line (a non negative
|
||||
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
|
||||
integer), the kind (def or defmacro), a tuple representing
|
||||
the function and its arity and the documentation, which should
|
||||
the function and its arity, the function signature (the signature
|
||||
should be omitted for types) and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
|
||||
## Examples
|
||||
@@ -477,12 +492,14 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop] do
|
||||
def add_doc(module, line, kind, tuple, signature // [], doc)
|
||||
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
kind in [:def, :defmacro] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
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)
|
||||
|
||||
@@ -825,7 +842,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
tl(String.split(String.Chars.to_string(module), "."))
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -875,9 +892,9 @@ defmodule Module do
|
||||
atom
|
||||
end
|
||||
|
||||
defp normalize_attribute(:file, env) when is_env(env), do: { binary_to_list(env.file), env.line }
|
||||
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
|
||||
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
|
||||
defp normalize_attribute(:file, file) when is_binary(file) do
|
||||
file
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, atom) when is_atom(atom) and
|
||||
key in [:before_compile, :after_compile, :on_definition] do
|
||||
@@ -885,7 +902,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
raise ArgumentError, message: "Attributes type, typep, export_type, opaque and callback " <>
|
||||
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
|
||||
"must be set via Kernel.Typespec"
|
||||
end
|
||||
|
||||
|
||||
@@ -5,15 +5,11 @@
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with three main
|
||||
# vertices:
|
||||
# all dependencies. The graph starts with one main vertice:
|
||||
#
|
||||
# * `:local` - points to local functions
|
||||
# * `:import` - points to imported modules
|
||||
# * `:warn` - points to imported modules that should be warned
|
||||
# * `:remote` - points to remote modules
|
||||
#
|
||||
# Besides those, we have can the following vertices:
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
@@ -24,9 +20,7 @@
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `:import`, `:remote`, `:warn`,
|
||||
# `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `:warn`
|
||||
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
@@ -92,17 +86,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were imported.
|
||||
All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec imports(ref) :: [module]
|
||||
def imports(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :import)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
@@ -113,17 +96,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched
|
||||
to. All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec remotes(ref) :: [module]
|
||||
def remotes(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :remote)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
@@ -152,11 +124,15 @@ defmodule Module.DispatchTracker do
|
||||
neighbours = (lc { _, _ } = t inlist 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)
|
||||
:lists.foldl(&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: Module.get_attribute(mod, :__dispatch_tracker)
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__dispatch_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
defp only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
@@ -200,41 +176,13 @@ defmodule Module.DispatchTracker do
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_remote, function, module, target })
|
||||
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
end
|
||||
|
||||
# Associates a module with a warn. This adds the given
|
||||
# module and associates it with the `:import` vertex
|
||||
# permanently, even if warn is false.
|
||||
@doc false
|
||||
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
|
||||
:gen_server.cast(pid, { :add_warnable, module, warn, line })
|
||||
end
|
||||
|
||||
# Collect all unused imports where warn has been set to true.
|
||||
def collect_unused_imports(pid) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
warnable = :digraph.out_neighbours(d, :warn)
|
||||
|
||||
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
|
||||
{ mod, line }
|
||||
end
|
||||
end
|
||||
|
||||
defp get_warn_line(d, mod) do
|
||||
[edge] = :digraph.out_edges(d, mod)
|
||||
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
|
||||
line
|
||||
end
|
||||
|
||||
defp has_imports?(d, mod) do
|
||||
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
|
||||
:gen_server.cast(pid, { :add_external, :import, function, module, target })
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@@ -243,6 +191,8 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
|
||||
end
|
||||
|
||||
# Reattach a previously yanked node
|
||||
@doc false
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
@@ -267,7 +217,7 @@ defmodule Module.DispatchTracker do
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
|
||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
@@ -307,9 +257,6 @@ defmodule Module.DispatchTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :import)
|
||||
:digraph.add_vertex(d, :remote)
|
||||
:digraph.add_vertex(d, :warn)
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
@@ -337,26 +284,8 @@ defmodule Module.DispatchTracker do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :remote, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :import, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_warnable, module, warn, line }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :import, module)
|
||||
|
||||
if warn do
|
||||
:digraph.add_edge(d, :warn, module, line)
|
||||
:digraph.add_edge(d, module, :warn, line)
|
||||
else
|
||||
:digraph.del_path(d, :warn, module)
|
||||
end
|
||||
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, kind, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
@@ -397,7 +326,6 @@ defmodule Module.DispatchTracker do
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, kind, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
|
||||
+17
-17
@@ -6,7 +6,7 @@ defmodule Node do
|
||||
@type t :: atom
|
||||
|
||||
@doc """
|
||||
Returns the current node. It returns the same as the built-in node().
|
||||
Returns the current node. It returns the same as the built-in `node()`.
|
||||
"""
|
||||
@spec self :: t
|
||||
def self do
|
||||
@@ -14,8 +14,8 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the local node is alive; that is, if the node can be
|
||||
part of a distributed system. Otherwise, it returns false.
|
||||
Returns `true` if the local node is alive; that is, if the node can be
|
||||
part of a distributed system. Otherwise, it returns `false`.
|
||||
"""
|
||||
@spec alive? :: boolean
|
||||
def alive? do
|
||||
@@ -24,7 +24,7 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns a list of all visible nodes in the system, excluding
|
||||
the local node. Same as list(visible).
|
||||
the local node. Same as `list(:visible)`.
|
||||
"""
|
||||
@spec list :: [t]
|
||||
def list do
|
||||
@@ -45,8 +45,8 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Monitors the status of the node. If flag is true, monitoring is
|
||||
turned on. If flag is false, monitoring is turned off.
|
||||
Monitors the status of the node. If `flag` is `true`, monitoring is
|
||||
turned on. If `flag` is `false`, monitoring is turned off.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
|
||||
"""
|
||||
@@ -56,8 +56,8 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Behaves as monitor_node/2 except that it allows an extra
|
||||
option to be given, namely :allow_passive_connect.
|
||||
Behaves as `monitor/2` except that it allows an extra
|
||||
option to be given, namely `:allow_passive_connect`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor_node-3 for more info.
|
||||
"""
|
||||
@@ -69,8 +69,8 @@ defmodule Node do
|
||||
@doc """
|
||||
Forces the disconnection of a node. This will appear to the `node` as if
|
||||
the local node has crashed. This BIF is mainly used in the Erlang network
|
||||
authentication protocols. Returns true if disconnection succeeds, otherwise
|
||||
false. If the local node is not alive, the function returns ignored.
|
||||
authentication protocols. Returns `true` if disconnection succeeds, otherwise
|
||||
`false`. If the local node is not alive, the function returns `:ignored`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
|
||||
"""
|
||||
@@ -80,8 +80,8 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Establishes a connection to Node. Returns true if successful,
|
||||
false if not, and the atom `:ignored` if the local node is not
|
||||
Establishes a connection to `node`. Returns `true` if successful,
|
||||
`false` if not, and the atom `:ignored` if the local node is not
|
||||
alive.
|
||||
|
||||
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
|
||||
@@ -145,7 +145,7 @@ defmodule Node do
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. A link is created between the calling process and the
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason :noconnection will be
|
||||
(and due to the link, an exit signal with exit reason `:noconnection` will be
|
||||
received).
|
||||
"""
|
||||
@spec spawn_link(t, (() -> any)) :: pid
|
||||
@@ -158,7 +158,7 @@ defmodule Node do
|
||||
`module.function(args)` on `node`. A link is created between the calling
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
:noconnection will be received).
|
||||
`:noconnection` will be received).
|
||||
"""
|
||||
@spec spawn_link(t, module, atom, [any]) :: pid
|
||||
def spawn_link(node, module, fun, args) do
|
||||
@@ -167,10 +167,10 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Sets the magic cookie of `node` to the atom `cookie`. The default node
|
||||
is `Node.self`, the local node. If node is the local node, the function also
|
||||
is `Node.self`, the local node. If `node` is the local node, the function also
|
||||
sets the cookie of all other unknown nodes to `cookie`.
|
||||
|
||||
This function will raise `FunctionClauseError` if the given node is not alive.
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
@@ -183,4 +183,4 @@ defmodule Node do
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+103
-67
@@ -4,26 +4,30 @@ defmodule OptionParser do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses `argv` and returns a tuple with parsed options
|
||||
and arguments.
|
||||
Parses `argv` and returns a tuple with the parsed options, its
|
||||
arguments, and a list options that couldn't be parsed.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{ [debug: true], [] }
|
||||
{ [debug: true], [], [] }
|
||||
|
||||
iex> OptionParser.parse(["--source", "lib"])
|
||||
{ [source: "lib"], [] }
|
||||
{ [source: "lib"], [], [] }
|
||||
|
||||
iex> OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib", verbose: true], ["test/enum_test.exs"] }
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
|
||||
|
||||
Notice how Elixir automatically translates the "--source-path"
|
||||
switch to the underscored atom `:source_path`, which better follows
|
||||
Elixir conventions.
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as the second argument:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [debug: true], [] }
|
||||
{ [debug: true], [], [] }
|
||||
|
||||
## Switches
|
||||
|
||||
@@ -39,6 +43,9 @@ defmodule OptionParser do
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
|
||||
The following extra options are supported:
|
||||
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
@@ -46,10 +53,17 @@ defmodule OptionParser do
|
||||
Examples:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: true], ["path/to/file"] }
|
||||
{ [unlock: true], ["path/to/file"], [] }
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "false", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: false], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
{ [unlock: true, limit: 0], ["path/to/file"], [] }
|
||||
|
||||
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
|
||||
{ [limit: 3], [], [] }
|
||||
|
||||
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
|
||||
{ [], [], [limit: "yyz"] }
|
||||
|
||||
## Negation switches
|
||||
|
||||
@@ -57,7 +71,12 @@ defmodule OptionParser do
|
||||
booleans and never parse the next value:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{ [no_op: true], ["path/to/file"] }
|
||||
{ [no_op: true], ["path/to/file"], [] }
|
||||
|
||||
In case the negated switch exists as a boolean, it sets the boolean to false:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{ [op: false], ["path/to/file"], [] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
@@ -73,10 +92,10 @@ defmodule OptionParser do
|
||||
## Example
|
||||
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"]}
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
@@ -91,90 +110,106 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp parse(argv, aliases, switches, all) do
|
||||
parse(argv, aliases, switches, [], [], all)
|
||||
parse(argv, aliases, switches, [], [], [], all)
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
|
||||
{ option, value } = normalize_option(option, aliases)
|
||||
kind = switches[option]
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
|
||||
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
|
||||
end
|
||||
|
||||
if value == nil do
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{ option, kinds, value } = normalize_option(option, switches, aliases)
|
||||
|
||||
if nil?(value) do
|
||||
{ value, t } =
|
||||
if is_switch_a? :boolean, kind do
|
||||
boolean_from_tail(t)
|
||||
if :boolean in kinds do
|
||||
{ true, t }
|
||||
else
|
||||
value_from_tail(t)
|
||||
end
|
||||
end
|
||||
|
||||
dict = store_option dict, option, value, kind
|
||||
parse(t, aliases, switches, dict, args, all)
|
||||
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
|
||||
|
||||
parse(t, aliases, switches, dict, args, invalid, all)
|
||||
end
|
||||
|
||||
defp parse([h|t], aliases, switches, dict, args, true) do
|
||||
parse(t, aliases, switches, dict, [h|args], true)
|
||||
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
|
||||
parse(t, aliases, switches, dict, [h|args], invalid, true)
|
||||
end
|
||||
|
||||
defp parse([], _, switches, dict, args, true) do
|
||||
{ reverse_dict(dict, switches), Enum.reverse(args) }
|
||||
defp parse([], _, _switches, dict, args, invalid, true) do
|
||||
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
|
||||
end
|
||||
|
||||
defp parse(value, _, switches, dict, _args, false) do
|
||||
{ reverse_dict(dict, switches), value }
|
||||
defp parse(value, _, _switches, dict, _args, invalid, false) do
|
||||
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
|
||||
end
|
||||
|
||||
defp boolean_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
|
||||
defp boolean_from_tail(t) , do: { true, t }
|
||||
|
||||
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
|
||||
defp value_from_tail([h|t]), do: { h, t }
|
||||
defp value_from_tail([]), do: { true, [] }
|
||||
|
||||
defp store_option(dict, option, value, switches) when value in ["true", "false"] do
|
||||
store_option dict, option, binary_to_atom(value), switches
|
||||
defp store_option(dict, invalid, option, value, kinds) do
|
||||
{ invalid_option, value } =
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{ nil, value in [true, "true"] }
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
end
|
||||
true ->
|
||||
{ nil, value }
|
||||
end
|
||||
|
||||
if invalid_option do
|
||||
{ dict, [{ option, value }|invalid] }
|
||||
else
|
||||
{ do_store_option(dict, option, value, kinds), invalid }
|
||||
end
|
||||
end
|
||||
|
||||
defp store_option(dict, option, value, kind) do
|
||||
case kind do
|
||||
:keep ->
|
||||
defp do_store_option(dict, option, value, kinds) do
|
||||
cond do
|
||||
:keep in kinds ->
|
||||
[{ option, value }|dict]
|
||||
:integer ->
|
||||
case String.to_integer(value) do
|
||||
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
|
||||
_ -> dict
|
||||
end
|
||||
:float ->
|
||||
case String.to_float(value) do
|
||||
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
|
||||
_ -> dict
|
||||
end
|
||||
_ ->
|
||||
true ->
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
defp reverse_dict(dict, switches) do
|
||||
switches = lc { k, v } inlist switches,
|
||||
is_switch_a?(:boolean, v),
|
||||
not Keyword.has_key?(dict, k), do: { k, false }
|
||||
Enum.reverse switches ++ dict
|
||||
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
|
||||
normalize_option(option, switches, aliases)
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, aliases) do
|
||||
normalize_option(option, aliases)
|
||||
end
|
||||
|
||||
defp normalize_option(option, aliases) do
|
||||
defp normalize_option(option, switches, aliases) do
|
||||
{ option, value } = split_option(option)
|
||||
if is_no?(option), do: value = true
|
||||
atom = option |> to_underscore |> binary_to_atom
|
||||
{ aliases[atom] || atom, value }
|
||||
|
||||
if non_neg = get_non_negated(option, aliases) do
|
||||
kinds = List.wrap(switches[non_neg])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ non_neg, kinds, false }
|
||||
else
|
||||
{ get_aliased(option, aliases), [:boolean], true }
|
||||
end
|
||||
else
|
||||
atom = get_aliased(option, aliases)
|
||||
{ atom, List.wrap(switches[atom]), value }
|
||||
end
|
||||
end
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> { h, nil }
|
||||
[h|t] -> { h, Enum.join(t, "=") }
|
||||
[h] -> { h, nil }
|
||||
[h, t] -> { h, t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,10 +217,11 @@ defmodule OptionParser do
|
||||
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
|
||||
defp is_no?("no-" <> _), do: true
|
||||
defp is_no?(_), do: false
|
||||
defp get_aliased(option, aliases) do
|
||||
atom = option |> to_underscore |> binary_to_atom
|
||||
aliases[atom] || atom
|
||||
end
|
||||
|
||||
defp is_switch_a?(kind, list) when is_list(list), do: kind in list
|
||||
defp is_switch_a?(kind, kind), do: true
|
||||
defp is_switch_a?(_, _), do: false
|
||||
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
|
||||
defp get_non_negated(_, _), do: nil
|
||||
end
|
||||
|
||||
+27
-10
@@ -9,7 +9,7 @@ defmodule Path do
|
||||
|
||||
The majority of the functions in this module do not
|
||||
interact with the file system, except for a few functions
|
||||
that require it (like `Path.wildcard` and `Path.expand`).
|
||||
that require it (like `wildcard/1` and `expand/1`).
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
@@ -19,7 +19,7 @@ defmodule Path do
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Unlike
|
||||
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
|
||||
`expand/1`, no attempt is made to resolve `..`, `.` or `~`.
|
||||
|
||||
## Unix examples
|
||||
|
||||
@@ -43,9 +43,9 @@ defmodule Path do
|
||||
|
||||
@doc """
|
||||
Builds a path from `relative_to` to `path`. If `path` is already
|
||||
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
|
||||
an absolute path, `relative_to` is ignored. See also `relative_to/2`.
|
||||
|
||||
Unlike `Path.expand/2`, no attempt is made to
|
||||
Unlike `expand/2`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
|
||||
## Examples
|
||||
@@ -80,7 +80,7 @@ defmodule Path do
|
||||
expanding any `.` and `..` characters. If the path is already an
|
||||
absolute path, `relative_to` is ignored.
|
||||
|
||||
Note, that this function treats `path` with leading `~` as
|
||||
Note, that this function treats `path` with a leading `~` as
|
||||
an absolute one.
|
||||
|
||||
The second argument is first expanded to an absolute path.
|
||||
@@ -236,6 +236,18 @@ defmodule Path do
|
||||
original
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convenience to get the path relative to the current working
|
||||
directory. If, for some reason, the current working directory
|
||||
cannot be retrieved, returns the full path.
|
||||
"""
|
||||
def relative_to_cwd(path) do
|
||||
case :file.get_cwd do
|
||||
{ :ok, base } -> relative_to(path, base)
|
||||
_ -> path
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last component of the path or the path
|
||||
itself if it does not contain any directory separators.
|
||||
@@ -397,7 +409,7 @@ defmodule Path do
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
list_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
iolist_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:
|
||||
@@ -461,7 +473,7 @@ defmodule Path do
|
||||
|
||||
Imagine you have a directory called `projects` with three Elixir projects
|
||||
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
|
||||
inside the ebin directory of each project as follows:
|
||||
inside the `ebin` directory of each project as follows:
|
||||
|
||||
Path.wildcard("projects/*/ebin/**/*.beam")
|
||||
|
||||
@@ -471,8 +483,9 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = :filelib.wildcard :unicode.characters_to_list(glob)
|
||||
Enum.map paths, :unicode.characters_to_binary(&1)
|
||||
paths = :filelib.wildcard binary_to_filename_string(glob)
|
||||
encoding = :file.native_name_encoding()
|
||||
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
@@ -494,7 +507,11 @@ defmodule Path do
|
||||
end
|
||||
|
||||
defp filename_string_to_binary(list) do
|
||||
case :unicode.characters_to_binary(:filename.flatten(list), :unicode, :file.native_name_encoding()) do
|
||||
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
|
||||
end
|
||||
|
||||
defp flatten_filename_to_binary(list, encoding) do
|
||||
case :unicode.characters_to_binary(list, :unicode, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
:erlang.error(:badarg)
|
||||
bin when is_binary(bin) ->
|
||||
|
||||
@@ -286,7 +286,7 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Returns the pid or port identifier with the registered name.
|
||||
Returns undefined 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.
|
||||
"""
|
||||
@@ -304,11 +304,11 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
|
||||
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`.
|
||||
"""
|
||||
@spec group_leader(leader :: pid, pid) :: true
|
||||
def group_leader(leader, pid) do
|
||||
@spec group_leader(pid, leader :: pid) :: true
|
||||
def group_leader(pid, leader) do
|
||||
:erlang.group_leader(leader, pid)
|
||||
end
|
||||
|
||||
@@ -347,25 +347,26 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid.
|
||||
Returns information about the process identified by pid or nil if the process
|
||||
is not alive.
|
||||
Use this only for debugging information.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
|
||||
"""
|
||||
@spec info(pid) :: Keyword.t
|
||||
def info(pid) do
|
||||
:erlang.process_info(pid)
|
||||
nillify :erlang.process_info(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid
|
||||
or undefined if the process is not alive.
|
||||
or nil if the process is not alive.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
||||
"""
|
||||
@spec info(pid, atom) :: {atom, term}
|
||||
def info(pid, spec) do
|
||||
:erlang.process_info(pid, spec)
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@compile { :inline, nillify: 1 }
|
||||
|
||||
+182
-306
@@ -1,58 +1,106 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :lists, as: L
|
||||
|
||||
@doc """
|
||||
Handle `defprotocol`. It will define a function for each
|
||||
protocol plus two extra functions:
|
||||
|
||||
* `__protocol__/1` - returns the protocol name when :name is given,
|
||||
and a keyword list with the protocol functions
|
||||
when :functions is given;
|
||||
|
||||
* `__impl_for__/1` - receives one argument and returns a module
|
||||
that implements the protocol for the given
|
||||
data type. If no implementation matches, returns nil;
|
||||
|
||||
* `__impl_for__!/1` - same as above but raises an error if an implementation is not found
|
||||
"""
|
||||
# Callback for defprotocol.
|
||||
@doc false
|
||||
def defprotocol(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacrop: 4,
|
||||
defmacro: 1, defmacro: 2, defmacro: 4,
|
||||
defp: 1, defp: 2, defp: 4,
|
||||
def: 1, def: 2, def: 4
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import :macros, Protocol.DSL
|
||||
import Protocol.DSL, only: :macros
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Deprecated
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Define callbacks and meta information
|
||||
{ conversions, fallback, returns_nil } = Protocol.conversions_for(__MODULE__, @only, @except)
|
||||
Protocol.impl_for(conversions, fallback, returns_nil, __ENV__)
|
||||
Protocol.meta(@functions, conversions, fallback, returns_nil, __ENV__)
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implement the given protocol for the given module.
|
||||
It also defines a `__impl__` function which
|
||||
returns the protocol being implemented.
|
||||
"""
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
msg = "is deprecated and should be removed. " <>
|
||||
"Note if you want to fallback to Any, you have to set @fallback_to_any true"
|
||||
|
||||
if @only do
|
||||
IO.puts "warning: @only in protocol #{inspect __MODULE__} " <> msg
|
||||
@fallback_to_any @fallback_to_any || Any in @only
|
||||
end
|
||||
|
||||
if @except do
|
||||
IO.puts "warning: @except in protocol #{inspect __MODULE__} " <> msg
|
||||
@fallback_to_any @fallback_to_any || not(Any in @except)
|
||||
end
|
||||
|
||||
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
lc { guard, body } inlist bodies do
|
||||
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Handle special Record type
|
||||
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
|
||||
|
||||
# Handle special Any type
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
try do
|
||||
__MODULE__.Any.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
# Callback for defimpl.
|
||||
@doc false
|
||||
def defimpl(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
@@ -71,8 +119,18 @@ defmodule Protocol do
|
||||
|
||||
defmodule name do
|
||||
@behaviour unquote(protocol)
|
||||
@protocol unquote(protocol)
|
||||
@for unquote(for)
|
||||
|
||||
unquote(block)
|
||||
def __impl__, do: unquote(protocol)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
def __impl__(:name), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -94,203 +152,88 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and returns
|
||||
# the module to dispatch to. Returns module.Record for records
|
||||
# which should be properly handled by the dispatching function.
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def impl_for(conversions, fallback, returns_nil, env) do
|
||||
contents = lc kind inlist conversions do
|
||||
each_impl_for(kind, conversions, fallback)
|
||||
end
|
||||
|
||||
if returns_nil do
|
||||
contents = contents ++ [quote do
|
||||
defp __raw_impl__(_) do
|
||||
nil
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
Module.eval_quoted env.module, contents, [], env.location
|
||||
def builtin do
|
||||
[ Tuple, Atom, List, BitString, Integer, Float,
|
||||
Function, PID, Port, Reference, Any ]
|
||||
end
|
||||
|
||||
# Defines meta information about the protocol and internal callbacks.
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def meta(functions, conversions, fallback, returns_nil, env) do
|
||||
any = L.keyfind(Any, 1, conversions) != false
|
||||
records = L.keyfind(Record, 1, conversions) != false
|
||||
def impl_for(current) do
|
||||
arg = quote(do: arg)
|
||||
all = [Record|builtin]
|
||||
|
||||
meta = quote location: :keep do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: unquote(generate_type(conversions, any))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
end
|
||||
|
||||
impl_for = cond do
|
||||
records and fallback ->
|
||||
quote location: :keep do
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, arg))
|
||||
try do
|
||||
target.__impl__
|
||||
target
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
|
||||
unquote(fallback)
|
||||
end
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
records ->
|
||||
quote location: :keep do
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
Module.concat(__MODULE__, :erlang.element(1, arg))
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
true ->
|
||||
quote location: :keep do
|
||||
def __impl_for__(arg), do: __raw_impl__(arg)
|
||||
end
|
||||
end
|
||||
|
||||
impl_bang = if returns_nil do
|
||||
quote do
|
||||
def __impl_for__!(arg) do
|
||||
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, structure: arg)
|
||||
end
|
||||
end
|
||||
else
|
||||
quote do
|
||||
def __impl_for__!(arg), do: __impl_for__(arg)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted env.module, [meta, impl_for, impl_bang], [], env.location
|
||||
{ arg,
|
||||
lc(mod inlist all, do: impl_for(current, mod, arg)),
|
||||
rec_impl_for(current, arg) }
|
||||
end
|
||||
|
||||
# Returns the default conversions according to the given
|
||||
# only/except options.
|
||||
@doc false
|
||||
def conversions_for(module, only, except) do
|
||||
kinds = all_types
|
||||
defp rec_impl_for(current, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
conversions =
|
||||
if only do
|
||||
L.map(fn i -> L.keyfind(i, 1, kinds) end, only)
|
||||
else
|
||||
except = except || [Any]
|
||||
L.foldl(fn i, list -> L.keydelete(i, 1, list) end, kinds, except)
|
||||
end
|
||||
|
||||
fallback = cond do
|
||||
L.keyfind(Tuple, 1, conversions) ->
|
||||
Module.concat module, Tuple
|
||||
L.keyfind(Any, 1, conversions) ->
|
||||
Module.concat module, Any
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
|
||||
# If any is not in the list and we don't implement all
|
||||
# protocols, we need to handle nil cases.
|
||||
returns_nil = L.keyfind(Any, 1, conversions) == false and length(conversions) < 10
|
||||
{ conversions, fallback, returns_nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp generate_type(_conversions, true) do
|
||||
quote(do: any)
|
||||
end
|
||||
|
||||
defp generate_type(conversions, false) do
|
||||
or_function = fn({ _, _, x }, acc) -> { :|, [], [acc, x] } end
|
||||
{ _, _, first } = hd(conversions)
|
||||
:lists.foldl(or_function, first, tl(conversions))
|
||||
end
|
||||
|
||||
defp all_types do
|
||||
[ { Record, :is_record, quote do: tuple },
|
||||
{ Tuple, :is_tuple, quote do: tuple },
|
||||
{ Atom, :is_atom, quote do: atom },
|
||||
{ List, :is_list, quote do: list },
|
||||
{ BitString, :is_bitstring, quote do: <<>> },
|
||||
{ Number, :is_number, quote do: number },
|
||||
{ Function, :is_function, quote do: (... -> any) },
|
||||
{ PID, :is_pid, quote do: pid },
|
||||
{ Port, :is_port, quote do: port },
|
||||
{ Reference, :is_reference, quote do: reference },
|
||||
{ Any, :is_any, quote do: any } ]
|
||||
end
|
||||
|
||||
# Returns a quoted expression that allows to check
|
||||
# if the first item in the tuple is a built-in or not.
|
||||
defp is_builtin?([{h, _, _}]) do
|
||||
quote do
|
||||
first == unquote(h)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_builtin?([{h, _, _}|t]) do
|
||||
quote do
|
||||
first == unquote(h) or unquote(is_builtin?(t))
|
||||
end
|
||||
end
|
||||
|
||||
# We don't have a fallback, so we assume what was given
|
||||
# as a record is indeed a record
|
||||
defp each_impl_for({ _, :is_record, _ }, _conversions, nil) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_record(arg) do
|
||||
__MODULE__.Record
|
||||
target = Module.concat(unquote(current), unquote(arg))
|
||||
try do
|
||||
target.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Specially handle records in the case we have fallbacks.
|
||||
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_record(arg) do
|
||||
first = :erlang.element(1, arg)
|
||||
case unquote(is_builtin?(conversions)) do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
defp impl_for(current, Record, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
dispatch = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true -> rec_impl_for(atom)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
{ is_record(unquote(arg)), unquote(dispatch) }
|
||||
end
|
||||
end
|
||||
|
||||
# Special case any as we don't need to generate a guard.
|
||||
defp each_impl_for({ _, :is_any, _ }, _, _) do
|
||||
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, Any, arg)
|
||||
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, Any, arg)
|
||||
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, Any, arg)
|
||||
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, Any, arg)
|
||||
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, Any, arg)
|
||||
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, Any, arg)
|
||||
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, Any, arg)
|
||||
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, Any, arg)
|
||||
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, Any, arg)
|
||||
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, Any, arg)
|
||||
|
||||
defp impl_for(_current, Any, _arg) do
|
||||
{ true, quote(do: any_impl_for) }
|
||||
end
|
||||
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, fallback, arg) do
|
||||
quote do
|
||||
defp __raw_impl__(_) do
|
||||
__MODULE__.Any
|
||||
end
|
||||
{ unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod), current, fallback, arg)) }
|
||||
end
|
||||
end
|
||||
|
||||
# Generate all others protocols.
|
||||
defp each_impl_for({ kind, fun, _ }, _, _) do
|
||||
# Tries to dispatch to a given target, fallbacks to the
|
||||
# given `fallback` implementation if the target does not exist.
|
||||
defp with_fallback(target, current, fallback, arg) when is_atom(target) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when unquote(fun)(arg) do
|
||||
__MODULE__.unquote(kind)
|
||||
try do
|
||||
unquote(target).__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
|
||||
unquote(impl_for(current, fallback, arg) |> elem(1))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -299,36 +242,45 @@ end
|
||||
defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :lists, as: L
|
||||
|
||||
@doc false
|
||||
def args_and_body(module, name, arity) do
|
||||
# Generate arguments according the arity. The arguments
|
||||
# are named xa, xb and so forth. We cannot use string
|
||||
# interpolation to generate the arguments because of compile
|
||||
# dependencies, so we use the <<>> instead.
|
||||
args = lc i inlist :lists.seq(1, arity) do
|
||||
{ binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
end
|
||||
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
end
|
||||
|
||||
{ conversions, fallback, returns_nil } = conversions_for(module)
|
||||
clauses = [default_clause(name, args)]
|
||||
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
if returns_nil do
|
||||
clauses = [nil_clause()|clauses]
|
||||
end
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
if L.keyfind(Record, 1, conversions) do
|
||||
clauses = [record_clause(name, args, fallback)|clauses]
|
||||
end
|
||||
call_args = lc i inlist :lists.seq(2, arity),
|
||||
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
body =
|
||||
quote do
|
||||
case __raw_impl__(xA), do: unquote({ :->, [], clauses })
|
||||
quote do
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@functions [{name, arity}|@functions]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
# Generate the actual implementation
|
||||
Kernel.def unquote(name)(unquote_splicing(call_args)) do
|
||||
impl_for!(t).unquote(name)(unquote_splicing(call_args))
|
||||
end
|
||||
|
||||
{ args, body }
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
|
||||
defmacro def(_) do
|
||||
raise ArgumentError, message: "invalid args for def inside defprotocol"
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -343,80 +295,4 @@ defmodule Protocol.DSL do
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
defp conversions_for(module) do
|
||||
only = Module.get_attribute(module, :only)
|
||||
except = Module.get_attribute(module, :except)
|
||||
Protocol.conversions_for(module, only, except)
|
||||
end
|
||||
|
||||
defp default_clause(name, args) do
|
||||
{ [quote do: other],
|
||||
[],
|
||||
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
|
||||
end
|
||||
|
||||
defp nil_clause() do
|
||||
{ [nil],
|
||||
[],
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, nil) do
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, fallback) do
|
||||
arity = length(args)
|
||||
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote do
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(args))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
|
||||
name == unquote(name) and length(args) == unquote(arity) ->
|
||||
apply unquote(fallback), name, [unquote_splicing(args)]
|
||||
end
|
||||
end }
|
||||
end
|
||||
|
||||
defmacro def(expression) do
|
||||
case expression do
|
||||
{ _, _, args } when args == [] or is_atom(args) ->
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
{ name, _, args } when is_atom(name) and is_list(args) ->
|
||||
:ok
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid args for defprotocol"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
quote do
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@functions [{name, arity}|@functions]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
|
||||
Kernel.def unquote(name), args, [], do: body
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
defmodule Protocol.Consolidation do
|
||||
@moduledoc """
|
||||
Module responsible for consolidating protocols and helpers for
|
||||
extracting protocols and implementations from code paths for
|
||||
consolidation.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = atom_to_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
lc path inlist paths,
|
||||
file inlist list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{ :ok, files } -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
|
||||
extract_from_beam(Path.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, { module, [attributes: attributes] } } ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol. Consolidation happens
|
||||
by changing the protocol `impl_for` in the abstract
|
||||
format to have fast lookup rules.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Enumerable.__info__(:attributes)[:protocol]
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{ :ok, binary } |
|
||||
{ :error, :not_a_protocol } |
|
||||
{ :error, :no_beam_info }
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
# Ensure the given module is loaded and is a protocol.
|
||||
defp ensure_protocol(protocol) do
|
||||
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
|
||||
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
|
||||
attributes: attributes] } } ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{ :ok, { protocol, any, abstract_code } }
|
||||
_ ->
|
||||
{ :error, :not_a_protocol }
|
||||
end
|
||||
_ ->
|
||||
{ :error, :no_beam_info }
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({ protocol, any, code }, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
records = types -- Protocol.builtin
|
||||
builtin = Protocol.builtin -- (Protocol.builtin -- types)
|
||||
builtin = if records != [], do: [Record|builtin], else: builtin
|
||||
change_impl_for(code, protocol, builtin, records, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
|
||||
attr = [fallback_to_any: Any in builtin, consolidated: true]
|
||||
change_impl_for(t, protocol, builtin, records, true,
|
||||
[{ :attribute, line, :protocol, attr }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
|
||||
|
||||
unless Any in builtin do
|
||||
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
|
||||
end
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
|
||||
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{ :ok, { protocol, Enum.reverse(acc) } }
|
||||
else
|
||||
{ :error, :not_a_protocol }
|
||||
end
|
||||
end
|
||||
|
||||
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
|
||||
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
|
||||
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
|
||||
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
|
||||
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
|
||||
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
|
||||
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
|
||||
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
|
||||
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
|
||||
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
|
||||
|
||||
defp clause_for(Any, protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, Module.concat(protocol, Any) }]}
|
||||
end
|
||||
|
||||
defp clause_for(Record, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :x}],
|
||||
[[{:op, line, :andalso,
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
|
||||
[{:var, line, :x}]},
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]
|
||||
}]},
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :rec_impl_for},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]}]}]}
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp record_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, value }]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({ protocol, code }) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
|
||||
{ :ok, binary }
|
||||
end
|
||||
end
|
||||
@@ -17,20 +17,21 @@ defprotocol Range.Iterator do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(Range[first: first] = range, acc, fun) do
|
||||
def reduce(first .. _ = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
end
|
||||
def member?(Range[first: first, last: last], value) do
|
||||
value in first..last
|
||||
|
||||
def member?(first .. last, value) do
|
||||
first <= value and value <= last
|
||||
end
|
||||
|
||||
def count(Range[first: first] = range) do
|
||||
def count(first .. _ = range) do
|
||||
Range.Iterator.count(first, range)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
|
||||
defimpl Range.Iterator, for: Integer do
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
else
|
||||
@@ -55,11 +56,11 @@ defimpl Range.Iterator, for: Number do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
def count(first, Range[last: last]) when is_integer(last) and last >= first do
|
||||
last - first + 1
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
def count(first, Range[last: last]) when is_integer(last) do
|
||||
first - last + 1
|
||||
end
|
||||
end
|
||||
|
||||
+134
-107
@@ -1,19 +1,19 @@
|
||||
defmodule Record do
|
||||
@moduledoc %B"""
|
||||
Convenience functions for working with Records.
|
||||
@moduledoc %S"""
|
||||
Functions for working with Records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
where the first element is an atom. We can manually create a record
|
||||
by simply defining such a tuple:
|
||||
|
||||
iex> record = { User, "josé", 25 }
|
||||
iex> record = { User, "José", 25 }
|
||||
iex> is_record(record, User)
|
||||
true
|
||||
|
||||
However, manually constructing tuples can be quite error prone.
|
||||
In case we need to add a new field to our User, it would require
|
||||
If we need to add a new field to our User, it would require
|
||||
us to carefully change all places where the tuple is used.
|
||||
Furthermore, as more and more items are added to the tuple, they
|
||||
Furthermore, as more items are added to the tuple, they
|
||||
lose semantic value.
|
||||
|
||||
This module solves these problems by allowing us to name each
|
||||
@@ -38,7 +38,7 @@ defmodule Record do
|
||||
In the example above, `defrecordp` is going to generate a set of
|
||||
macros named `user` that allows us to create, update and match
|
||||
on a record. Our record is going to have two fields, a `name` with
|
||||
default value of "José" and `age` of 25.
|
||||
default value of "José" and `age` with default value 25.
|
||||
|
||||
Let's see some examples:
|
||||
|
||||
@@ -75,10 +75,7 @@ defmodule Record do
|
||||
As the name says, `defrecordp` is useful when you don't want to
|
||||
expose the record definition. The `user` macro used above, for
|
||||
example, is only available inside the `User` module and nowhere else.
|
||||
You can find more information in `Kernel.defrecordp/2` docs.
|
||||
|
||||
In general, though, records are used as part of a module's public
|
||||
interface. For such use cases, Elixir provides record modules.
|
||||
You can find more information in `Kernel.defrecordp/3` docs.
|
||||
|
||||
## defrecord
|
||||
|
||||
@@ -97,7 +94,7 @@ defmodule Record do
|
||||
user() #=> User[name: "José", age: 25]
|
||||
user(age: 26) #=> User[name: "José", age: 26]
|
||||
|
||||
Notice that now, since the record definition is accessible, Elixir
|
||||
Notice that now since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `raw: true` to `inspect`:
|
||||
|
||||
@@ -112,9 +109,9 @@ defmodule Record do
|
||||
User[] #=> User[name: "José", age: 25]
|
||||
User[age: 26] #=> User[name: "José", age: 26]
|
||||
|
||||
The macro name is replaced by the module name and the parenthesis
|
||||
are replaced by brackets. When the shortcut syntax is used, there
|
||||
is no need to import the record.
|
||||
The macro name is replaced by the module name and the parentheses
|
||||
are replaced by brackets. When this syntax is used, there is no
|
||||
need to import the record.
|
||||
|
||||
Before we sum up the differences between `defrecord` and
|
||||
`defrecordp`, there is one last functionality introduced by
|
||||
@@ -123,12 +120,12 @@ defmodule Record do
|
||||
## Runtime access
|
||||
|
||||
All the functionality discussed above happens at compilation time.
|
||||
This means that both `user(user, age: 26)` and `User[user, age: 26]`
|
||||
will be transformed into a simple tuple operation at compile time.
|
||||
This means that both `user(age: 26)` and `User[age: 26]` are expanded
|
||||
into a tuple at compile time.
|
||||
|
||||
However, there are some situations where we want to set or update
|
||||
fields dynamically. `defrecord` (and `defrecord` only) supports
|
||||
it out of the box:
|
||||
fields dynamically. `defrecord` (and not `defrecordp` ) supports
|
||||
this behaviour out of the box:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
@@ -138,16 +135,25 @@ defmodule Record do
|
||||
user.update(age: 26)
|
||||
#=> User[name: "Hello", age: 26]
|
||||
|
||||
All the calls above happen at runtime. It gives Elixir
|
||||
records flexibility at the cost of performance since
|
||||
there is more work happening at runtime.
|
||||
All the calls above happen at runtime. It gives Elixir records
|
||||
flexibility at the cost of performance since there is more work
|
||||
happening at runtime.
|
||||
|
||||
To sum up, `defrecordp` should be used when you don't want
|
||||
to expose the record information while `defrecord` should be used
|
||||
whenever you want to share a record within your code or with other
|
||||
libraries or whenever you need to dynamically set or update fields.
|
||||
The above calls (`new` and `update`) can accept both atom and string
|
||||
keys for field names, however not both at the same time. This feature
|
||||
allows to "sanitize" untrusted dictionaries and initialize/update
|
||||
records without using `Kernel.binary_to_existing_atom/1`.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/2` docs. Now
|
||||
To sum up, `defrecordp` should be used when you don't want to expose
|
||||
the record information while `defrecord` should be used whenever you
|
||||
want to share a record within your code or with other libraries or
|
||||
whenever you need to dynamically set or update fields.
|
||||
|
||||
The standard library contains excellent examples of both use cases,
|
||||
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
|
||||
and [`Range`](Range.html) with `defrecord`.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/3` docs. Now
|
||||
let's discuss the usefulness of combining records with protocols.
|
||||
|
||||
## Protocols
|
||||
@@ -161,29 +167,39 @@ defmodule Record do
|
||||
defrecord WeekDate, year: nil, week: nil, week_day: nil
|
||||
|
||||
Now we want this date to be represented as a string and this
|
||||
can be done by implementing the `Binary.Chars` protocol for
|
||||
can be done by implementing the `String.Chars` protocol for
|
||||
our record:
|
||||
|
||||
defimpl Binary.Chars, for: WeekDate do
|
||||
def to_binary(WeekDate[year: year, week: week, week_day: day]) do
|
||||
defimpl String.Chars, for: WeekDate do
|
||||
def to_string(WeekDate[year: year, week: week, week_day: day]) do
|
||||
"#{year}-W#{week}-#{day}"
|
||||
end
|
||||
end
|
||||
|
||||
Now we can explicitly convert our WeekDate:
|
||||
Now we can explicitly convert our `WeekDate`:
|
||||
|
||||
to_binary WeekDate[year: 2013, week: 26, week_day: 4]
|
||||
to_string WeekDate[year: 2013, week: 26, week_day: 4]
|
||||
"2013-W26-4"
|
||||
|
||||
A protocol can be implemented for any record defined via `defrecord`.
|
||||
A protocol can be implemented for any record, whether
|
||||
generated with `defrecordp` or `defrecord`.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
|
||||
@doc """
|
||||
Extract record information from an Erlang file and
|
||||
return the fields as a list of tuples.
|
||||
Extract record information from an Erlang file.
|
||||
|
||||
Returns the fields as a list of tuples.
|
||||
|
||||
## Examples
|
||||
|
||||
> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
|
||||
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
|
||||
major_device: :undefined, minor_device: :undefined, inode: :undefined,
|
||||
uid: :undefined, gid: :undefined]
|
||||
|
||||
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
|
||||
"""
|
||||
@@ -192,8 +208,9 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Imports a public record definition as a set of private macros,
|
||||
as defined by `Kernel.defrecordp/2`. This is useful when one
|
||||
Import a public record definition as a set of private macros.
|
||||
|
||||
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
@@ -229,14 +246,14 @@ defmodule Record do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Elixir.Record.DSL
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
|
||||
Record.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
@@ -278,8 +295,9 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the record
|
||||
Define record functions skipping the module definition.
|
||||
|
||||
This is called directly by `Kernel.defrecord/3`. It expects the record
|
||||
values, a set of options and the module environment.
|
||||
|
||||
## Examples
|
||||
@@ -297,7 +315,6 @@ defmodule Record do
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
indexes(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
@@ -307,7 +324,7 @@ defmodule Record do
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
@@ -316,7 +333,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines types and specs for the record.
|
||||
Define types and specs for the record.
|
||||
"""
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
@@ -338,8 +355,10 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines macros for manipulating records. This is called
|
||||
directly by `defrecordp`. It expects the macro name, the
|
||||
Define macros for manipulating records.
|
||||
|
||||
This is called
|
||||
directly by `Kernel.defrecordp/3`. It expects the macro name, the
|
||||
record values and the environment.
|
||||
|
||||
## Examples
|
||||
@@ -413,7 +432,7 @@ defmodule Record do
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -626,9 +645,8 @@ defmodule Record do
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
selective = lc { k, v } inlist values do
|
||||
quote do: Keyword.get(opts, unquote(k), unquote(v))
|
||||
end
|
||||
atom_selective = lc { k, v } inlist values, do: initialize_lookup(k, v)
|
||||
string_selective = lc { k, v } inlist values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -636,47 +654,17 @@ defmodule Record do
|
||||
|
||||
@doc false
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
|
||||
def new(tuple) when is_tuple(tuple), do: :erlang.setelement(1, tuple, __MODULE__)
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
|
||||
end
|
||||
end
|
||||
|
||||
# Define method to get index of a given key.
|
||||
#
|
||||
# Useful if you need to know position of the key for such applications as:
|
||||
# - ets
|
||||
# - mnesia
|
||||
#
|
||||
# For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define following method:
|
||||
#
|
||||
# def __index__(:atime), do: 2
|
||||
# def __index__(:mtime), do: 3
|
||||
# def __index__(_), do: nil
|
||||
#
|
||||
defp indexes(values) do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
@doc false
|
||||
def __index__(unquote(k)) do
|
||||
IO.write "[WARNING] #{__MODULE__}.__index__/1 is deprecated, " <>
|
||||
"please use #{__MODULE__}.__record__(:index, key) instead\n#{Exception.format_stacktrace}"
|
||||
unquote(index + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __index__(_), do: nil
|
||||
|
||||
@doc false
|
||||
def __index__(key, _), do: __index__(key)
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -767,15 +755,14 @@ defmodule Record do
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
fields =
|
||||
lc {key, _default} inlist values do
|
||||
index = find_index(values, key, 1)
|
||||
quote do
|
||||
Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
|
||||
end
|
||||
end
|
||||
atom_fields =
|
||||
lc {key, _default} inlist values, do: updater_lookup(key, key, values)
|
||||
|
||||
contents = quote do: { __MODULE__, unquote_splicing(fields) }
|
||||
string_fields =
|
||||
lc {key, _default} inlist values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
|
||||
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -783,8 +770,22 @@ defmodule Record do
|
||||
record
|
||||
end
|
||||
|
||||
def update(keywords, record) do
|
||||
unquote(contents)
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
v = find_index(values, key, 1)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> elem(record, unquote(v))
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -807,6 +808,7 @@ defmodule Record do
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = if values == [], do: [], else: [options_specs(values)]
|
||||
values_specs = if values == [], do: [], else: values_specs(values)
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@@ -814,11 +816,11 @@ defmodule Record do
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options)
|
||||
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options | tuple) :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@@ -832,6 +834,11 @@ defmodule Record do
|
||||
{ :|, [], [{ k, v }, acc] }
|
||||
end, { k, v }, t
|
||||
end
|
||||
defp values_specs([{ _, _, v }|t]) do
|
||||
:lists.foldl fn { _, _, v }, acc ->
|
||||
{ :|, [], [v, acc] }
|
||||
end, v, t
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
@@ -853,7 +860,7 @@ defmodule Record do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
@@ -861,17 +868,36 @@ defmodule Record do
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a function")
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port")
|
||||
|
||||
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
|
||||
defp convert_value({ atom, other }) when is_atom(atom), do:
|
||||
{ atom, check_value(atom, other) }
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
lc(i inlist other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
lc(i inlist tuple_to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == { :env, [] } and
|
||||
:erlang.fun_info(other, :type) == { :type, :external } do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
@@ -890,6 +916,7 @@ defmodule Record.DSL do
|
||||
|
||||
@doc """
|
||||
Defines the type for each field in the record.
|
||||
|
||||
Expects a keyword list.
|
||||
"""
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
|
||||
@@ -3,12 +3,12 @@ defmodule Record.Extractor do
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def retrieve(name, from: string) do
|
||||
file = to_char_list(string)
|
||||
def retrieve(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> nil
|
||||
realfile -> :ok
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
@@ -16,12 +16,12 @@ defmodule Record.Extractor do
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) do
|
||||
[app|path] = :filename.split(to_char_list(file))
|
||||
def retrieve(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(String.to_char_list!(file))
|
||||
|
||||
case :code.lib_dir(to_char_list(app)) do
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, "lib file #{to_binary(file)} could not be found"
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
end
|
||||
@@ -34,7 +34,7 @@ defmodule Record.Extractor do
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
|
||||
raise ArgumentError, message: "no record #{name} found at #{file}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,7 +51,7 @@ defmodule Record.Extractor do
|
||||
{ :ok, form } ->
|
||||
form
|
||||
other ->
|
||||
raise "error parsing file #{to_binary(file)}, got: #{inspect(other)}"
|
||||
raise "error parsing file #{file}, got: #{inspect(other)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,4 +88,4 @@ defmodule Record.Extractor do
|
||||
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+106
-63
@@ -1,22 +1,21 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %B"""
|
||||
Regular expressions for Elixir built on top of the re module
|
||||
@moduledoc %S"""
|
||||
Regular expressions for Elixir built on top of the `re` module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the re documentation: http://www.erlang.org/doc/man/re.html
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using Regex.compile!
|
||||
Regular expressions in Elixir can be created using `Regex.compile!`
|
||||
or using the special form with `%r`:
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
|
||||
# A regular expression with case insensitive options and handling for unicode chars
|
||||
%r/foo/iu
|
||||
# A regular expression with case insensitive options
|
||||
%r/foo/i
|
||||
|
||||
The re module provides several options, the ones available in Elixir, followed by
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
|
||||
* `unicode` (u) - enable unicode specific patterns like \p
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
@@ -27,8 +26,8 @@ defmodule Regex do
|
||||
allow `#` to delimit comments
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - invert the "greediness" of the regexp
|
||||
* `groups` (g) - compile with info about groups available
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `groups` (g) - compiles with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
@@ -44,25 +43,54 @@ defmodule Regex do
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, term, term, term }
|
||||
@type t :: { Regex, term, binary, binary, [atom] | nil }
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
|
||||
@doc """
|
||||
Compiles the regular expression according to the given options.
|
||||
Compiles the regular expression.
|
||||
|
||||
The given options can either be a binary with the characters
|
||||
representing the same regex options given to the `%r` sigil,
|
||||
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, %r"foo"}
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
"""
|
||||
def compile(source, options // "") when is_binary(source) do
|
||||
options = to_binary(options)
|
||||
opts = translate_options(options)
|
||||
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
|
||||
def compile(source, options // "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
{ :error, rest } ->
|
||||
{ :error, { :invalid_option, rest } }
|
||||
|
||||
translated_options ->
|
||||
# Always use the unicode option, we don't have a latin1 legacy like
|
||||
# Erlang.
|
||||
compile(source, [:unicode|translated_options], options)
|
||||
end
|
||||
end
|
||||
|
||||
def compile(source, options) when is_list(options) do
|
||||
compile(source, options, "")
|
||||
end
|
||||
|
||||
defp compile(source, opts, doc_opts) when is_binary(source) do
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
|
||||
case :re.compile(source, re_opts) do
|
||||
{ :ok, re_pattern } ->
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: options, groups: groups) }
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -95,9 +123,11 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string.
|
||||
It returns a list with all matches, `nil` if no match occurred, or `[]`
|
||||
if it matched, `/g` was specified, but nothing was captured.
|
||||
Runs the regular expression against the given string until the first match.
|
||||
It returns a list with all captures or `nil` if no match occurred.
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -128,29 +158,23 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a keyword list or `nil` if no captures are found.
|
||||
Requires the regex to be compiled with the groups option.
|
||||
Returns the given captures as a keyword list or `nil` if no captures
|
||||
are found. Requires the regex to be compiled with the groups option.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
|
||||
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[bar: "d", foo: "b"]
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
unless captures = Keyword.get(options, :capture) do
|
||||
captures = if groups do
|
||||
Enum.sort(groups)
|
||||
else
|
||||
raise ArgumentError, message: "regex was not compiled with g"
|
||||
end
|
||||
options = Keyword.put(options, :capture, captures)
|
||||
end
|
||||
def named_captures(regex(groups: groups) = regex, string, options // []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(captures, results)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -200,34 +224,48 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as run, but scans the target several times collecting all matches of
|
||||
the regular expression. A list is returned with each match. If the item in
|
||||
the list is a binary, it means there were no captures. If the item is another
|
||||
list, each element in this secondary list is a capture.
|
||||
Same as `run/3`, but scans the target several times collecting all
|
||||
matches of the regular expression. A list of lists is returned,
|
||||
where each entry in the primary list represents a match and each
|
||||
entry in the secondary list represents the captured contents.
|
||||
|
||||
The captured contents defaults to `:all`, which includes the whole
|
||||
regex match and each capture.
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
[["d"], ["e"]]
|
||||
[["cd", "d"], ["ce", "e"]]
|
||||
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
|
||||
["cd", "ce"]
|
||||
[["cd"], ["ce"]]
|
||||
iex> Regex.scan(%r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options // [])
|
||||
|
||||
def scan(regex(re_pattern: compiled), string, options) do
|
||||
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
options = [{ :capture, :all, return }, :global]
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
options = [{ :capture, captures, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:match -> []
|
||||
:nomatch -> []
|
||||
{ :match, results } -> flatten_result(results)
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Split the given target into the number of parts specified.
|
||||
Splits the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Examples
|
||||
@@ -235,8 +273,12 @@ defmodule Regex do
|
||||
["a","b","c"]
|
||||
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
["abc"]
|
||||
iex> Regex.split(%r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
iex> Regex.split(%r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
"""
|
||||
|
||||
def split(regex, string, options // [])
|
||||
@@ -251,16 +293,22 @@ defmodule Regex do
|
||||
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [return: return, parts: parts]
|
||||
:re.split(string, compiled, opts)
|
||||
splits = :re.split(string, compiled, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
Inside the replacement, you can either give `&` to access the
|
||||
whole regular expression or `\N`, where `N` is in integer to access
|
||||
a specific matching parens. You can also set global to false
|
||||
a specific matching parens. You can also set `:global` to `false`
|
||||
if you want to replace just the first occurrence.
|
||||
|
||||
## Examples
|
||||
@@ -284,10 +332,10 @@ defmodule Regex do
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Escapes a string to be literally matched in a regex.
|
||||
|
||||
## Examples
|
||||
@@ -306,7 +354,7 @@ defmodule Regex do
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
# Unescape map function used by Macro.unescape_binary.
|
||||
# Unescape map function used by Macro.unescape_string.
|
||||
def unescape_map(?f), do: ?\f
|
||||
def unescape_map(?n), do: ?\n
|
||||
def unescape_map(?r), do: ?\r
|
||||
@@ -320,7 +368,10 @@ defmodule Regex do
|
||||
defp return_for(element) when is_binary(element), do: :binary
|
||||
defp return_for(element) when is_list(element), do: :list
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
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)]
|
||||
@@ -329,17 +380,9 @@ defmodule Regex do
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
|
||||
defp flatten_result(results) do
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[_|t] -> t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
|
||||
+16
-1
@@ -1,5 +1,5 @@
|
||||
defmodule Set do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
@@ -15,6 +15,21 @@ defmodule Set do
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
|
||||
## Protocols
|
||||
|
||||
Sets are required to implement the `Enumerable` protocol,
|
||||
allowing one to write:
|
||||
|
||||
Enum.each(set, fn k ->
|
||||
IO.inspect k
|
||||
end)
|
||||
|
||||
## Match
|
||||
|
||||
Sets are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
+156
-49
@@ -8,7 +8,7 @@ defmodule Stream do
|
||||
|
||||
iex> range = 1..5
|
||||
1..5
|
||||
iex> Enum.map range, &1 * 2
|
||||
iex> Enum.map range, &(&1 * 2)
|
||||
[2,4,6,8,10]
|
||||
|
||||
In the example above, as we mapped over the range, the elements being
|
||||
@@ -16,8 +16,8 @@ defmodule Stream do
|
||||
module allows us to map the range, without triggering its enumeration:
|
||||
|
||||
iex> range = 1..3
|
||||
iex> stream = Stream.map(range, &1 * 2)
|
||||
iex> Enum.map(stream, &1 + 1)
|
||||
iex> stream = Stream.map(range, &(&1 * 2))
|
||||
iex> Enum.map(stream, &(&1 + 1))
|
||||
[3,5,7]
|
||||
|
||||
Notice we started with a range and then we created a stream that is
|
||||
@@ -27,16 +27,16 @@ defmodule Stream do
|
||||
We say the functions in `Stream` are *lazy* and the functions in `Enum`
|
||||
are *eager*.
|
||||
|
||||
Due to their laziness, streams are useful when working with large
|
||||
Due to their laziness, streams are useful when working with large
|
||||
(or even infinite) collections. When chaining many operations with `Enum`,
|
||||
intermediate lists are created, while `Stream` creates a recipe of
|
||||
computations that are executed at a later moment. Let's see another
|
||||
example:
|
||||
|
||||
1..3 |>
|
||||
Enum.map(IO.inspect(&1)) |>
|
||||
Enum.map(&1 * 2) |>
|
||||
Enum.map(IO.inspect(&1))
|
||||
Enum.map(&IO.inspect(&1)) |>
|
||||
Enum.map(&(&1 * 2)) |>
|
||||
Enum.map(&IO.inspect(&1))
|
||||
1
|
||||
2
|
||||
3
|
||||
@@ -50,9 +50,9 @@ defmodule Stream do
|
||||
was iterated three times. Let's see an example with streams:
|
||||
|
||||
stream = 1..3 |>
|
||||
Stream.map(IO.inspect(&1)) |>
|
||||
Stream.map(&1 * 2) |>
|
||||
Stream.map(IO.inspect(&1))
|
||||
Stream.map(&IO.inspect(&1)) |>
|
||||
Stream.map(&(&1 * 2)) |>
|
||||
Stream.map(&IO.inspect(&1))
|
||||
Enum.to_list(stream)
|
||||
1
|
||||
2
|
||||
@@ -81,8 +81,8 @@ defmodule Stream do
|
||||
|
||||
This module also allows us to create streams from any enumerable:
|
||||
|
||||
iex> stream = Stream.map([1,2,3], &1 * 2)
|
||||
iex> Enum.map(stream, &1 + 1)
|
||||
iex> stream = Stream.map([1,2,3], &(&1 * 2))
|
||||
iex> Enum.map(stream, &(&1 + 1))
|
||||
[3,5,7]
|
||||
|
||||
By simply passing a list (which is an enumerable) as the first argument
|
||||
@@ -97,32 +97,35 @@ defmodule Stream do
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
def reduce(Lazy[] = lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fun)
|
||||
do_reduce(lazy, acc, fun, 0)
|
||||
end
|
||||
|
||||
def count(Lazy[] = lazy) do
|
||||
do_reduce(lazy, 0, fn _, acc -> acc + 1 end)
|
||||
do_reduce(lazy, 0, fn _, acc -> acc + 1 end, 0)
|
||||
end
|
||||
|
||||
def member?(Lazy[] = lazy, value) do
|
||||
do_reduce(lazy, false, fn(entry, _) ->
|
||||
if entry === value, do: throw({ :stream_lazy, true }), else: false
|
||||
end)
|
||||
if entry === value, do: throw({ :stream_lazy, 0, true }), else: false
|
||||
end, 0)
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun) do
|
||||
do_reduce(enumerable, acc, f1.(fun))
|
||||
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun, nesting) do
|
||||
do_reduce(enumerable, acc, f1.(fun), nesting)
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun) do
|
||||
do_reduce(enumerable, { acc, side }, f1.(fun)) |> elem(0)
|
||||
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun, nesting) do
|
||||
do_reduce(enumerable, { acc, side }, f1.(fun, nesting), nesting + 1)
|
||||
end
|
||||
|
||||
defp do_reduce(enumerable, acc, fun) do
|
||||
Enumerable.reduce(enumerable, acc, fun)
|
||||
defp do_reduce(enumerable, acc, fun, nesting) do
|
||||
Enumerable.reduce(enumerable, acc, fun) |> remove_nesting(nesting)
|
||||
catch
|
||||
{ :stream_lazy, acc } -> acc
|
||||
{ :stream_lazy, nesting, res } -> remove_nesting(res, nesting)
|
||||
end
|
||||
|
||||
defp remove_nesting(acc, 0), do: acc
|
||||
defp remove_nesting(acc, nesting), do: remove_nesting(elem(acc, 0), nesting - 1)
|
||||
end
|
||||
|
||||
@type t :: Lazy.t | (acc, (element, acc -> acc) -> acc)
|
||||
@@ -131,6 +134,46 @@ defmodule Stream do
|
||||
@type index :: non_neg_integer
|
||||
@type default :: any
|
||||
|
||||
@doc """
|
||||
Creates a stream that enumerates each enumerable in an enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.concat([1..3, 4..6, 7..9])
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5,6,7,8,9]
|
||||
|
||||
"""
|
||||
@spec concat(Enumerable.t) :: t
|
||||
def concat(enumerables) do
|
||||
&do_concat(enumerables, &1, &2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that enumerates the first argument, followed by the second.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.concat(1..3, 4..6)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
iex> stream1 = Stream.cycle([1, 2, 3])
|
||||
iex> stream2 = Stream.cycle([4, 5, 6])
|
||||
iex> stream = Stream.concat(stream1, stream2)
|
||||
iex> Enum.take(stream, 6)
|
||||
[1,2,3,1,2,3]
|
||||
|
||||
"""
|
||||
@spec concat(Enumerable.t, Enumerable.t) :: t
|
||||
def concat(first, second) do
|
||||
&do_concat([first, second], &1, &2)
|
||||
end
|
||||
|
||||
defp do_concat(enumerables, acc, fun) do
|
||||
Enumerable.reduce(enumerables, acc, &Enumerable.reduce(&1, &2, fun))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that cycles through the given enumerable,
|
||||
infinitely.
|
||||
@@ -144,7 +187,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: t
|
||||
def cycle(enumerable) do
|
||||
do_cycle(enumerable, &1, &2)
|
||||
&do_cycle(enumerable, &1, &2)
|
||||
end
|
||||
|
||||
defp do_cycle(enumerable, acc, fun) do
|
||||
@@ -165,7 +208,7 @@ defmodule Stream do
|
||||
@spec drop(Enumerable.t, non_neg_integer) :: t
|
||||
def drop(enumerable, n) when n >= 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fun: fn(f1, _) ->
|
||||
fn
|
||||
_entry, { acc, n } when n > 0 ->
|
||||
{ acc, n - 1 }
|
||||
@@ -182,7 +225,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.drop_while(1..10, &1 <= 5)
|
||||
iex> stream = Stream.drop_while(1..10, &(&1 <= 5))
|
||||
iex> Enum.to_list(stream)
|
||||
[6,7,8,9,10]
|
||||
|
||||
@@ -190,7 +233,7 @@ defmodule Stream do
|
||||
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def drop_while(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fun: fn(f1, _) ->
|
||||
fn
|
||||
entry, { acc, true } ->
|
||||
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
|
||||
@@ -228,7 +271,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
|
||||
iex> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
|
||||
[0,1,2,3,4]
|
||||
|
||||
"""
|
||||
@@ -266,6 +309,46 @@ defmodule Stream do
|
||||
end]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on enumeration and
|
||||
flatten the result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.flat_map([1, 2, 3], fn(x) -> [x, x * 2] end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1, 2, 2, 4, 3, 6]
|
||||
|
||||
"""
|
||||
|
||||
@spec flat_map(Enumerable.t, (element -> any)) :: t
|
||||
def flat_map(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) -> do_flat_map(f.(entry), acc, f1) end
|
||||
end]
|
||||
end
|
||||
|
||||
defp do_flat_map(Lazy[] = lazy, acc, f1) do
|
||||
try do
|
||||
Enumerable.reduce(lazy, acc, fn x, y ->
|
||||
try do
|
||||
f1.(x, y)
|
||||
catch
|
||||
{ :stream_lazy, nesting, rest } ->
|
||||
throw({ :stream_flat_map, nesting, rest })
|
||||
end
|
||||
end)
|
||||
catch
|
||||
{ :stream_flat_map, nesting, rest } ->
|
||||
throw({ :stream_lazy, nesting, rest })
|
||||
end
|
||||
end
|
||||
|
||||
defp do_flat_map(enum, acc, f1) do
|
||||
Enumerable.reduce(enum, acc, f1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will reject elements according to
|
||||
the given function on enumeration.
|
||||
@@ -287,27 +370,25 @@ defmodule Stream do
|
||||
end]
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns a stream generated by calling `generator_fun` repeatedly.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.repeatedly(function(:random.uniform/0)) |> Enum.take(3)
|
||||
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
|
||||
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
|
||||
|
||||
"""
|
||||
@spec repeatedly((() -> element)) :: t
|
||||
def repeatedly(generator_fun)
|
||||
def repeatedly(generator_fun)
|
||||
when is_function(generator_fun, 0) do
|
||||
do_repeatedly(generator_fun, &1, &2)
|
||||
&do_repeatedly(generator_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, acc, fun) do
|
||||
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Lazily takes the next `n` items from the enumerable and stops
|
||||
enumeration.
|
||||
@@ -324,14 +405,14 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: t
|
||||
def take(enumerable, n) when n >= 0 do
|
||||
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: &(&1)]
|
||||
|
||||
def take(enumerable, n) when n > 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn
|
||||
(entry, { acc, n }) when n > 0 ->
|
||||
{ f1.(entry, acc), n-1 }
|
||||
(_entry, acc) ->
|
||||
throw { :stream_lazy, acc }
|
||||
fun: fn(f1, nesting) ->
|
||||
fn(entry, { acc, n }) ->
|
||||
res = f1.(entry, acc)
|
||||
if n > 1, do: { res, n-1 }, else: throw { :stream_lazy, nesting, res }
|
||||
end
|
||||
end,
|
||||
acc: n]
|
||||
@@ -343,7 +424,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.take_while(1..100, &1 <= 5)
|
||||
iex> stream = Stream.take_while(1..100, &(&1 <= 5))
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5]
|
||||
|
||||
@@ -351,17 +432,43 @@ defmodule Stream do
|
||||
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def take_while(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn
|
||||
entry, { acc, true } ->
|
||||
if f.(entry), do: { f1.(entry, acc), true }, else: { acc, false }
|
||||
_entry, acc ->
|
||||
acc
|
||||
fun: fn(f1, nesting) ->
|
||||
fn(entry, { acc, true }) ->
|
||||
if f.(entry) do
|
||||
{ f1.(entry, acc), true }
|
||||
else
|
||||
throw { :stream_lazy, nesting, acc }
|
||||
end
|
||||
end
|
||||
end,
|
||||
acc: true]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Emit a sequence of values and accumulators. Successive values are generated by
|
||||
calling `next_fun` with the previous accumulator.
|
||||
|
||||
If the return value is nil iteration ends.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
|
||||
[5, 4, 3, 2, 1]
|
||||
"""
|
||||
@spec unfold(acc, (acc -> { element, acc } | nil)) :: t
|
||||
def unfold(acc, f) do
|
||||
fn acc1, f1 ->
|
||||
do_unfold(acc, f, acc1, f1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_unfold(gen_acc, gen_fun, acc, fun) do
|
||||
case gen_fun.(gen_acc) do
|
||||
nil -> acc
|
||||
{ v, new_gen_acc } -> do_unfold(new_gen_acc, gen_fun, fun.(v, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream where each item in the enumerable will
|
||||
be accompanied by its index.
|
||||
@@ -376,10 +483,10 @@ defmodule Stream do
|
||||
@spec with_index(Enumerable.t) :: t
|
||||
def with_index(enumerable) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fun: fn(f1, _) ->
|
||||
fn(entry, { acc, counter }) ->
|
||||
acc = f1.({ entry, counter }, acc)
|
||||
{ acc, counter + 1}
|
||||
{ acc, counter + 1 }
|
||||
end
|
||||
end,
|
||||
acc: 0]
|
||||
|
||||
+420
-202
@@ -1,27 +1,29 @@
|
||||
import Kernel, except: [length: 1]
|
||||
|
||||
defmodule String do
|
||||
@moduledoc %B"""
|
||||
@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.2.0. For example,
|
||||
`titlecase`, `downcase`, `strip` are provided by this
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
Besides this module, Elixir provides more low-level
|
||||
operations that works directly with binaries. Some
|
||||
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:
|
||||
|
||||
* `binary_part/2` and `binary_part/3` - retrieves part of the binary
|
||||
* `bit_size/1` and `byte_size/1` - size related functions
|
||||
* `is_bitstring/1` and `is_binary/1` - type checking function
|
||||
* Plus a bunch of conversion functions, like `binary_to_atom/2`,
|
||||
`binary_to_integer/2`, `binary_to_term/1` and their opposite
|
||||
like `integer_to_binary/2`
|
||||
* `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 conversion functions, like `Kernel.binary_to_atom/1`,
|
||||
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
|
||||
like `Kernel.integer_to_binary/2`
|
||||
|
||||
Finally, [the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
provides a couple other functions that works on the byte level.
|
||||
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
provides a few other functions that work on the byte level.
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
@@ -30,10 +32,14 @@ defmodule String do
|
||||
For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> string = "é"
|
||||
...> byte_size(string)
|
||||
iex> byte_size("é")
|
||||
2
|
||||
|
||||
However, this module returns the proper length:
|
||||
|
||||
iex> String.length("é")
|
||||
1
|
||||
|
||||
Furthermore, this module also presents the concept of
|
||||
graphemes, which are multiple characters that may be
|
||||
"perceived as a single character" by readers. For example,
|
||||
@@ -43,6 +49,8 @@ defmodule String do
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
3
|
||||
iex> String.length(string)
|
||||
1
|
||||
|
||||
Although the example above is made of two characters, it is
|
||||
perceived by users as one.
|
||||
@@ -57,6 +65,11 @@ defmodule String do
|
||||
Standard, but does not contain any of the locale specific
|
||||
behaviour.
|
||||
|
||||
More information about graphemes can be found in the [Unicode
|
||||
Standard Annex #29](http://www.unicode.org/reports/tr29/).
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
@@ -93,7 +106,7 @@ defmodule String do
|
||||
codepoint needs to be rejected.
|
||||
|
||||
This module relies on this behaviour to ignore such invalid
|
||||
characters. For example, `String.length` is going to return
|
||||
characters. For example, `length/1` is going to return
|
||||
a correct result even if an invalid codepoint is fed into it.
|
||||
|
||||
In other words, this module expects invalid data to be detected
|
||||
@@ -108,7 +121,7 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Checks if a string is printable considering it is encoded
|
||||
as UTF-8. Returns true if so, false otherwise.
|
||||
as UTF-8. Returns `true` if so, `false` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -138,69 +151,8 @@ defmodule String do
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Escape the given string. It expects one extra paremeter
|
||||
representing the string surrounds which should also be escaped.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.escape("abc", ?")
|
||||
"abc"
|
||||
|
||||
iex> String.escape("a\nb", ?")
|
||||
"a\\nb"
|
||||
|
||||
"""
|
||||
def escape(other, char) do
|
||||
escape(other, char, <<>>)
|
||||
end
|
||||
|
||||
@compile { :inline, escape: 3 }
|
||||
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
defp escape(<< char, t :: binary >>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
defp escape(<<?\a, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
defp escape(<<?\b, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
end
|
||||
defp escape(<<?\d, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
end
|
||||
defp escape(<<?\e, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
end
|
||||
defp escape(<<?\f, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
end
|
||||
defp escape(<<?\n, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
end
|
||||
defp escape(<<?\r, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
end
|
||||
defp escape(<<?\\, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
end
|
||||
defp escape(<<?\t, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
end
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, h >>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a string on sub strings at each Unicode whitespace
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
occurrence with leading and trailing whitespace ignored.
|
||||
|
||||
## Examples
|
||||
@@ -216,30 +168,52 @@ defmodule String do
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Divides a string into sub strings based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
@doc %S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
default, unless the `global` option is set to false.
|
||||
default, unless the `global` option is set to `false`.
|
||||
|
||||
Empty strings are only removed from the result if the
|
||||
`trim` option is set to `true`.
|
||||
|
||||
## Examples
|
||||
|
||||
Splitting with a string pattern:
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split(" a b c ", " ", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
A list of patterns:
|
||||
|
||||
iex> String.split("1,2 3,4", [" ", ","])
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
A regular expression:
|
||||
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split("a,b", %r{\\.})
|
||||
["a,b"]
|
||||
iex> String.split(" a b c ", %r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty patterns returns codepoints:
|
||||
|
||||
iex> String.split("abc", %r{})
|
||||
["a", "b", "c", ""]
|
||||
iex> String.split("abc", "")
|
||||
["a", "b", "c", ""]
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
iex> String.split("abc", "", global: false)
|
||||
["a", "bc"]
|
||||
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@@ -248,17 +222,25 @@ defmodule String do
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, %r"", options)
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, global: options[:global])
|
||||
Regex.split(pattern, binary, options)
|
||||
end
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
:binary.split(binary, pattern, opts)
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to upcase.
|
||||
Convert all characters on the given string to uppercase.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -274,7 +256,7 @@ defmodule String do
|
||||
defdelegate upcase(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to downcase.
|
||||
Convert all characters on the given string to lowercase.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -291,11 +273,11 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Converts the first character in the given string to
|
||||
titlecase and the remaining to downcase.
|
||||
uppercase and the remaining to lowercase.
|
||||
|
||||
This relies on the titlecase information provided
|
||||
by the Unicode Standard. Note this function makes
|
||||
no attempt in capitalizing all words in the string
|
||||
no attempt to capitalize all words in the string
|
||||
(usually known as titlecase).
|
||||
|
||||
## Examples
|
||||
@@ -343,7 +325,7 @@ defmodule String do
|
||||
# Do a quick check before we traverse the whole
|
||||
# binary. :binary.last is a fast operation (it
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) do
|
||||
def rstrip(string, char) when char in 0..127 do
|
||||
if :binary.last(string) == char do
|
||||
do_rstrip(string, "", char)
|
||||
else
|
||||
@@ -351,12 +333,16 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<<char, string :: binary>>, buffer, char) do
|
||||
do_rstrip(string, <<char, buffer :: binary>>, char)
|
||||
def rstrip(string, char) do
|
||||
do_rstrip(string, "", char)
|
||||
end
|
||||
|
||||
defp do_rstrip(<<char, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char, do_rstrip(string, "", another_char) :: binary>>
|
||||
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, char) do
|
||||
<<do_rstrip(string, <<char :: utf8, buffer :: binary>>, char) :: binary>>
|
||||
end
|
||||
|
||||
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char :: utf8, do_rstrip(string, "", another_char) :: binary>>
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _, _) do
|
||||
@@ -387,7 +373,7 @@ defmodule String do
|
||||
|
||||
@spec lstrip(t, char) :: t
|
||||
|
||||
def lstrip(<<char, rest :: binary>>, char) do
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
end
|
||||
|
||||
@@ -427,14 +413,81 @@ defmodule String do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` for `replacement`. By default, it replaces
|
||||
all entries, except if the `global` option is set to false.
|
||||
@doc %S"""
|
||||
Returns a new string of length `len` with `subject` right justified and
|
||||
padded with `padding`. If `padding` is not present, it defaults to
|
||||
whitespace. When `len` is less than the length of `subject`, `subject` is
|
||||
returned.
|
||||
|
||||
If the replaced part must be used in `replacement`, then the
|
||||
position or the positions where it is to be inserted must be
|
||||
specified by using the option `insert_replaced`.
|
||||
## Examples
|
||||
|
||||
iex> String.rjust("abc", 5)
|
||||
" abc"
|
||||
iex> String.rjust("abc", 5, ?-)
|
||||
"--abc"
|
||||
|
||||
"""
|
||||
@spec rjust(t, pos_integer) :: t
|
||||
@spec rjust(t, pos_integer, char) :: t
|
||||
|
||||
def rjust(subject, len) do
|
||||
rjust(subject, len, ?\s)
|
||||
end
|
||||
|
||||
def rjust(subject, len, padding) when is_integer(padding) do
|
||||
do_justify(subject, len, padding, :right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Returns a new string of length `len` with `subject` left justified and padded
|
||||
with `padding`. If `padding` is not present, it defaults to whitespace. When
|
||||
`len` is less than the length of `subject`, `subject` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.ljust("abc", 5)
|
||||
"abc "
|
||||
iex> String.ljust("abc", 5, ?-)
|
||||
"abc--"
|
||||
|
||||
"""
|
||||
@spec ljust(t, pos_integer) :: t
|
||||
@spec ljust(t, pos_integer, char) :: t
|
||||
|
||||
def ljust(subject, len) do
|
||||
ljust(subject, len, ?\s)
|
||||
end
|
||||
|
||||
def ljust(subject, len, padding) when is_integer(padding) do
|
||||
do_justify(subject, len, padding, :left)
|
||||
end
|
||||
|
||||
defp do_justify(subject, 0, _padding, _type) do
|
||||
subject
|
||||
end
|
||||
|
||||
defp do_justify(subject, len, padding, type) when is_integer(padding) do
|
||||
subject_len = length(subject)
|
||||
|
||||
cond do
|
||||
subject_len >= len ->
|
||||
subject
|
||||
subject_len < len ->
|
||||
fill = duplicate(<<padding :: utf8>>, len - subject_len)
|
||||
|
||||
case type do
|
||||
:left -> subject <> fill
|
||||
:right -> fill <> subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` by `replacement`. By default, it replaces
|
||||
all entries, except if the `global` option is set to `false`.
|
||||
|
||||
A `pattern` may be a string or a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -442,6 +495,19 @@ defmodule String do
|
||||
"a-b-c"
|
||||
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 catpure 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 `&`,
|
||||
one can inject the whole matched pattern in the replacement string.
|
||||
|
||||
When strings are used as a pattern, a developer can also use the
|
||||
replaced part inside the `replacement` via the `:insert_replaced` option:
|
||||
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
@@ -453,7 +519,13 @@ defmodule String do
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options // []) do
|
||||
def replace(subject, pattern, replacement, options // [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
end
|
||||
|
||||
def replace(subject, pattern, replacement, options) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
end
|
||||
@@ -468,6 +540,30 @@ defmodule String do
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reverses the given string. Works on graphemes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.reverse("abcd")
|
||||
"dcba"
|
||||
iex> String.reverse("hello world")
|
||||
"dlrow olleh"
|
||||
iex> String.reverse("hello ∂og")
|
||||
"go∂ olleh"
|
||||
|
||||
"""
|
||||
@spec reverse(t) :: t
|
||||
def reverse(string) do
|
||||
do_reverse(next_grapheme(string), [])
|
||||
end
|
||||
|
||||
defp do_reverse({grapheme, rest}, acc) do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
|
||||
@@ -509,7 +605,7 @@ defmodule String do
|
||||
remaining of the string or `:no_codepoint` in case
|
||||
the string reached its end.
|
||||
|
||||
As the other functions in the String module, this
|
||||
As with other functions in the String module, this
|
||||
function does not check for the validity of the codepoint.
|
||||
That said, if an invalid codepoint is found, it will
|
||||
be returned by this function.
|
||||
@@ -520,10 +616,11 @@ defmodule String do
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Checks whether `str` contains only valid characters.
|
||||
|
||||
## Examples
|
||||
@@ -554,7 +651,7 @@ defmodule String do
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Checks whether `str` is a valid character.
|
||||
|
||||
All characters are codepoints, but some codepoints
|
||||
@@ -578,30 +675,10 @@ defmodule String do
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` is a valid codepoint.
|
||||
|
||||
Note that the empty string is considered invalid, as are
|
||||
strings containing multiple codepoints.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid_codepoint?("a")
|
||||
true
|
||||
iex> String.valid_codepoint?("ø")
|
||||
true
|
||||
iex> String.valid_codepoint?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid_codepoint?("asdf")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid_codepoint?(codepoint) :: boolean
|
||||
def valid_codepoint?(<<_ :: utf8>>), do: true
|
||||
def valid_codepoint?(_), do: false
|
||||
|
||||
@doc """
|
||||
Returns unicode graphemes in the string.
|
||||
Returns unicode graphemes in the string as per Extended Grapheme
|
||||
Cluster algorithm outlined in the [Unicode Standard Annex #29,
|
||||
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -610,7 +687,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec graphemes(t) :: [grapheme]
|
||||
defdelegate graphemes(string), to: String.Unicode
|
||||
defdelegate graphemes(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
Returns the next grapheme in a String.
|
||||
@@ -625,8 +702,9 @@ defmodule String do
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
defdelegate next_grapheme(string), to: String.Unicode
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
Returns the first grapheme from an utf8 string,
|
||||
@@ -650,7 +728,7 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Returns the last grapheme from an utf8 string,
|
||||
nil if the string is empty.
|
||||
`nil` if the string is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -718,7 +796,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
def at(string, position) when position < 0 do
|
||||
real_pos = do_length(next_grapheme(string)) - abs(position)
|
||||
real_pos = length(string) - abs(position)
|
||||
case real_pos >= 0 do
|
||||
true -> do_at(next_grapheme(string), real_pos, 0)
|
||||
false -> nil
|
||||
@@ -738,7 +816,7 @@ 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, than it returns nil.
|
||||
If the offset is greater than string length, than it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -762,18 +840,84 @@ defmodule String do
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
|
||||
def slice(string, start, len) when start >= 0 do
|
||||
def slice(string, start, 0) do
|
||||
case abs(start) <= length(string) do
|
||||
true -> ""
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
do_slice(next_grapheme(string), start, start + len - 1, 0, "")
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start < 0 do
|
||||
real_start_pos = do_length(next_grapheme(string)) - abs(start)
|
||||
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, "")
|
||||
false -> nil
|
||||
end
|
||||
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.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
string or if the range is in reverse order, returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.slice("elixir", 1..3)
|
||||
"lix"
|
||||
iex> String.slice("elixir", 1..10)
|
||||
"lixir"
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", 2..-1)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -4..6)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t | nil
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
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, "")
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
nil
|
||||
end
|
||||
@@ -801,68 +945,21 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an integer. If successful, returns a
|
||||
tuple of form {integer, remainder of string}. If unsuccessful,
|
||||
returns :error.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("34")
|
||||
{34,""}
|
||||
iex> String.to_integer("34.5")
|
||||
{34,".5"}
|
||||
iex> String.to_integer("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
@doc false
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(binary_to_list(string))
|
||||
case result do
|
||||
:error -> :error
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
end
|
||||
IO.write "String.to_integer/1 is deprecated, please use Integer.parse/1 instead\n#{Exception.format_stacktrace}"
|
||||
Integer.parse(string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to a float. If successful, returns a
|
||||
tuple of form {float, remainder of string}. If unsuccessful,
|
||||
returns :error. If given an integer value, will return
|
||||
same as to_integer/1.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_float("34")
|
||||
{34.0,""}
|
||||
iex> String.to_float("34.25")
|
||||
{34.25,""}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_float(t) :: {integer, t} | :error
|
||||
|
||||
@doc false
|
||||
def to_float(string) do
|
||||
charlist = binary_to_list(string)
|
||||
{result, remainder} = :string.to_float(charlist)
|
||||
case result do
|
||||
:error ->
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
_ -> {:erlang.float(int_result), list_to_binary(int_remainder)}
|
||||
end
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
end
|
||||
IO.write "String.to_float/1 is deprecated, please use Float.parse/1 instead\n#{Exception.format_stacktrace}"
|
||||
Float.parse(string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` starts with any of the prefixes given, otherwise
|
||||
false. `prefixes` can be either a single prefix or a list of prefixes.
|
||||
Returns `true` if `string` starts with any of the prefixes given, otherwise
|
||||
`false`. `prefixes` can be either a single prefix or a list of prefixes.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -877,14 +974,14 @@ defmodule String do
|
||||
@spec starts_with?(t, t | [t]) :: boolean
|
||||
|
||||
def starts_with?(string, prefixes) when is_list(prefixes) do
|
||||
Enum.any?(prefixes, do_starts_with(string, &1))
|
||||
Enum.any?(prefixes, &do_starts_with(string, &1))
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) do
|
||||
do_starts_with(string, prefix)
|
||||
end
|
||||
|
||||
defp do_starts_with(_, "") do
|
||||
defp do_starts_with(string, "") when is_binary(string) do
|
||||
true
|
||||
end
|
||||
|
||||
@@ -897,8 +994,8 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` ends with any of the suffixes given, otherwise
|
||||
false. `suffixes` can be either a single suffix or a list of suffixes.
|
||||
Returns `true` if `string` ends with any of the suffixes given, otherwise
|
||||
`false`. `suffixes` can be either a single suffix or a list of suffixes.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -913,14 +1010,14 @@ defmodule String do
|
||||
@spec ends_with?(t, t | [t]) :: boolean
|
||||
|
||||
def ends_with?(string, suffixes) when is_list(suffixes) do
|
||||
Enum.any?(suffixes, do_ends_with(string, &1))
|
||||
Enum.any?(suffixes, &do_ends_with(string, &1))
|
||||
end
|
||||
|
||||
def ends_with?(string, suffix) do
|
||||
do_ends_with(string, suffix)
|
||||
end
|
||||
|
||||
defp do_ends_with(_, "") do
|
||||
defp do_ends_with(string, "") when is_binary(string) do
|
||||
true
|
||||
end
|
||||
|
||||
@@ -936,7 +1033,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` contains match, otherwise false.
|
||||
Returns `true` if `string` contains match, otherwise `false`.
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
@@ -952,14 +1049,14 @@ defmodule String do
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, do_contains(string, &1))
|
||||
Enum.any?(matches, &do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, "") do
|
||||
defp do_contains(string, "") when is_binary(string) do
|
||||
true
|
||||
end
|
||||
|
||||
@@ -970,4 +1067,125 @@ defmodule String do
|
||||
defp do_contains(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, encoded: nil, rest: nil, kind: nil do
|
||||
def message(exception) do
|
||||
"#{exception.kind} #{detail(exception.rest)}"
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string into a char list converting each codepoint to its
|
||||
respective integer value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_char_list("æß")
|
||||
{ :ok, 'æß' }
|
||||
iex> String.to_char_list("abc")
|
||||
{ :ok, 'abc' }
|
||||
|
||||
"""
|
||||
@spec to_char_list(String.t) :: { :ok, char_list } | { :error, list, binary } | { :incomplete, list, binary }
|
||||
def to_char_list(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
{ :ok, result }
|
||||
|
||||
{ :error, _, _ } = error ->
|
||||
error
|
||||
|
||||
{ :incomplete, _, _ } = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string into a char list converting each codepoint to its
|
||||
respective integer value.
|
||||
|
||||
In case the conversion fails or is incomplete,
|
||||
it raises a `String.UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_char_list!("æß")
|
||||
'æß'
|
||||
iex> String.to_char_list!("abc")
|
||||
'abc'
|
||||
|
||||
"""
|
||||
@spec to_char_list!(String.t) :: char_list | no_return
|
||||
def to_char_list!(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{ :error, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{ :incomplete, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integer codepoints to a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_list([0x00E6, 0x00DF])
|
||||
{ :ok, "æß" }
|
||||
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
|
||||
{ :ok, "abc" }
|
||||
|
||||
"""
|
||||
@spec from_char_list(char_list) :: { :ok, String.t } | { :error, binary, binary } | { :incomplete, binary, binary }
|
||||
def from_char_list(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
{ :ok, result }
|
||||
|
||||
{ :error, _, _ } = error ->
|
||||
error
|
||||
|
||||
{ :incomplete, _, _ } = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integer codepoints to a string.
|
||||
|
||||
In case the conversion fails, it raises a `String.UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_list!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec from_char_list!(char_list) :: String.t | no_return
|
||||
def from_char_list!(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{ :error, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{ :incomplete, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defprotocol String.Chars do
|
||||
@moduledoc %S"""
|
||||
The String.Chars protocol is responsible for
|
||||
converting a structure to a Binary (only if applicable).
|
||||
The only function required to be implemented is
|
||||
`to_string` which does the conversion.
|
||||
|
||||
The `to_string` function automatically imported
|
||||
by Kernel invokes this protocol. String
|
||||
interpolation also invokes to_string in its
|
||||
arguments. For example, `"foo#{bar}"` is the same
|
||||
as `"foo" <> to_string(bar)`.
|
||||
"""
|
||||
|
||||
def to_string(thing)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Atom do
|
||||
@doc """
|
||||
Convert the atom literally to a binary, except
|
||||
`nil` which is converted to an empty string.
|
||||
"""
|
||||
def to_string(nil) do
|
||||
""
|
||||
end
|
||||
|
||||
def to_string(atom) do
|
||||
atom_to_binary(atom)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: BitString do
|
||||
@doc """
|
||||
Returns the given binary or raises an error for bitstrings.
|
||||
"""
|
||||
def to_string(thing) when is_binary(thing) do
|
||||
thing
|
||||
end
|
||||
|
||||
def to_string(thing) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: thing,
|
||||
description: "cannot convert a bitstring to a string"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: List do
|
||||
@doc """
|
||||
Consider the list is an iolist and converts it
|
||||
to a binary. This allows a list of binaries, or
|
||||
a charlist, or a mix of both, to be converted
|
||||
successfully.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> to_string('foo')
|
||||
"foo"
|
||||
iex> to_string(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_string(char_list), do: String.from_char_list!(char_list)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Integer do
|
||||
@doc """
|
||||
Simply converts the integer to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Float do
|
||||
@doc """
|
||||
Simply converts the float to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
@@ -103,11 +103,11 @@ defmodule Supervisor.Behaviour do
|
||||
Defaults to `:permanent`;
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated.
|
||||
Defaults to `5000`;
|
||||
Defaults to `5000` for a worker and `:infinity` for a supervisor;
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a supervisor, gen_server or gen_fsm. If the
|
||||
child process is a supervisor, `gen_server` or `gen_fsm`. If the
|
||||
child process is a gen_event, modules should be `:dynamic`.
|
||||
Defaults to a list with the given module;
|
||||
|
||||
@@ -167,6 +167,7 @@ defmodule Supervisor.Behaviour do
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options // []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
@@ -180,4 +181,4 @@ defmodule Supervisor.Behaviour do
|
||||
{ id, { module, function, args },
|
||||
restart, shutdown, type, modules }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+129
-78
@@ -9,8 +9,8 @@ defmodule System do
|
||||
message: "could not get a current working directory, the current location is not accessible"
|
||||
|
||||
@moduledoc """
|
||||
The System module provides access to some variables used or
|
||||
maintained by the VM and to functions that interact strongly
|
||||
The System module provides access to variables used or
|
||||
maintained by the VM and to functions that interact directly
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
@@ -53,17 +53,21 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
list_to_binary :httpd_util.rfc1123_date
|
||||
iolist_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
Elixir version information.
|
||||
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
@spec version() :: String.t
|
||||
def version, do: get_version
|
||||
|
||||
@doc """
|
||||
Returns a keywords list with version, git tag info and date.
|
||||
Elixir build information.
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
def build_info do
|
||||
@@ -71,7 +75,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of command-line arguments passed to the program.
|
||||
List command line arguments.
|
||||
|
||||
Returns the list of command line arguments passed to the program.
|
||||
"""
|
||||
@spec argv() :: [String.t]
|
||||
def argv do
|
||||
@@ -79,17 +85,32 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current working directory or nil if one
|
||||
Modify command line arguments.
|
||||
|
||||
Changes the list of command line arguments. Use it with caution,
|
||||
as it destroys any previous argv information.
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(args) do
|
||||
:elixir_code_server.cast({ :argv, args })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Current working directory.
|
||||
|
||||
Returns the current working directory or `nil` if one
|
||||
is not available.
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{ :ok, list } -> :unicode.characters_to_binary(list)
|
||||
{ :ok, base } -> String.from_char_list!(base)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
Returns the current working directory or raises `System.NoAccessCwdError`.
|
||||
"""
|
||||
def cwd! do
|
||||
@@ -97,16 +118,23 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the user home (platform independent).
|
||||
It returns nil if no user home is set.
|
||||
User home directory.
|
||||
|
||||
Returns the user home directory (platform independent).
|
||||
Returns `nil` if no user home is set.
|
||||
"""
|
||||
def user_home do
|
||||
get_unix_home || get_windows_home
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> get_windows_home
|
||||
_ -> get_unix_home
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `user_home` but raises `System.NoHomeError`
|
||||
instead of returning nil if no user home is set.
|
||||
User home directory, exception on error.
|
||||
|
||||
Same as `user_home/0` but raises `System.NoHomeError`
|
||||
instead of returning `nil` if no user home is set.
|
||||
"""
|
||||
def user_home! do
|
||||
user_home || raise NoHomeError
|
||||
@@ -117,16 +145,20 @@ defmodule System do
|
||||
end
|
||||
|
||||
defp get_windows_home do
|
||||
get_env("USERPROFILE") || (
|
||||
hd = get_env("HOMEDRIVE")
|
||||
hp = get_env("HOMEPATH")
|
||||
hd && hp && hd <> hp
|
||||
:filename.absname(
|
||||
get_env("USERPROFILE") || (
|
||||
hd = get_env("HOMEDRIVE")
|
||||
hp = get_env("HOMEPATH")
|
||||
hd && hp && hd <> hp
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Writable temporary directory.
|
||||
|
||||
Returns a writable temporary directory.
|
||||
It searches for directories in the following order:
|
||||
Searches for directories in the following order:
|
||||
|
||||
1. The directory named by the TMPDIR environment variable
|
||||
2. The directory named by the TEMP environment variable
|
||||
@@ -134,28 +166,30 @@ defmodule System do
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. As a last resort, the current working directory
|
||||
|
||||
Returns nil if none of the above are writable.
|
||||
Returns `nil` if none of the above are writable.
|
||||
"""
|
||||
def tmp_dir do
|
||||
write_env_tmp_dir('TMPDIR') ||
|
||||
write_env_tmp_dir('TEMP') ||
|
||||
write_env_tmp_dir('TMP') ||
|
||||
write_tmp_dir("/tmp") ||
|
||||
write_env_tmp_dir('TEMP') ||
|
||||
write_env_tmp_dir('TMP') ||
|
||||
write_tmp_dir('/tmp') ||
|
||||
((cwd = cwd()) && write_tmp_dir(cwd))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `tmp_dir` but raises `System.NoTmpDirError`
|
||||
instead of returning nil if no temp dir is set.
|
||||
Writable temporary directory, exception on error.
|
||||
|
||||
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
|
||||
instead of returning `nil` if no temp dir is set.
|
||||
"""
|
||||
def tmp_dir! do
|
||||
tmp_dir || raise NoTmpDirError
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
case get_env(env) do
|
||||
nil -> nil
|
||||
tmp -> write_tmp_dir tmp
|
||||
case :os.getenv(env) do
|
||||
false -> nil
|
||||
tmp -> write_tmp_dir(tmp)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -166,26 +200,31 @@ defmodule System do
|
||||
access_index = File.Stat.__record__(:index, :access)
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
dir
|
||||
_ -> nil
|
||||
String.from_char_list!(dir)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
{ :error, _ } -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register a program exit handler function.
|
||||
|
||||
Registers a function that will be invoked
|
||||
at the end of program execution. Useful for
|
||||
invoking a hook in a "script" mode.
|
||||
invoking a hook in "script" mode.
|
||||
|
||||
The function must expect the exit status code
|
||||
as argument.
|
||||
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 }
|
||||
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.
|
||||
@@ -193,17 +232,22 @@ defmodule System do
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec cmd(binary) :: binary
|
||||
@spec cmd(char_list) :: char_list
|
||||
@spec cmd(String.t) :: String.t
|
||||
|
||||
def cmd(command) when is_list(command) do
|
||||
:os.cmd(command)
|
||||
end
|
||||
|
||||
def cmd(command) do
|
||||
list_to_binary :os.cmd(to_char_list(command))
|
||||
def cmd(command) when is_binary(command) do
|
||||
# Notice we don't use unicode for conversion
|
||||
# because the OS is expecting and returning raw bytes
|
||||
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locate 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
|
||||
@@ -213,61 +257,77 @@ defmodule System do
|
||||
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
|
||||
@spec find_executable(String.t) :: String.t | nil
|
||||
|
||||
def find_executable(program) when is_list(program) do
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(program) do
|
||||
case :os.find_executable(to_char_list(program)) do
|
||||
def find_executable(program) when is_binary(program) do
|
||||
# Notice we don't use unicode for conversion
|
||||
# because the OS is expecting and returning raw bytes
|
||||
case :os.find_executable(:binary.bin_to_list(program)) do
|
||||
false -> nil
|
||||
other -> list_to_binary(other)
|
||||
other -> :binary.list_to_bin(other)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all environment variables. Each environment variable is
|
||||
given as a single string of the format "VarName=Value", where VarName is the
|
||||
name of the variable and Value its value.
|
||||
System environment variables.
|
||||
|
||||
Returns a list of all environment variables. Each variable is given as a
|
||||
`{name, value}` tuple where both `name` and `value` are strings.
|
||||
"""
|
||||
@spec get_env() :: [{String.t, String.t}]
|
||||
def get_env do
|
||||
Enum.map :os.getenv, :unicode.characters_to_binary &1
|
||||
Enum.map(:os.getenv, fn var ->
|
||||
var = String.from_char_list! var
|
||||
[k, v] = String.split var, "=", global: false
|
||||
{k, v}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Environment variable value.
|
||||
|
||||
Returns the value of the environment variable
|
||||
`varname` as a binary, or nil if the environment
|
||||
`varname` as a binary, or `nil` if the environment
|
||||
variable is undefined.
|
||||
"""
|
||||
@spec get_env(String.t) :: String.t | nil
|
||||
def get_env(varname) do
|
||||
case :os.getenv(to_char_list(varname)) do
|
||||
@spec get_env(binary) :: binary | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(String.to_char_list!(varname)) do
|
||||
false -> nil
|
||||
other -> :unicode.characters_to_binary(other)
|
||||
other -> String.from_char_list!(other)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Erlang VM process identifier.
|
||||
|
||||
Returns the process identifier of the current Erlang emulator
|
||||
in the format most commonly used by the operating system environment.
|
||||
|
||||
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
|
||||
"""
|
||||
@spec get_pid() :: String.t
|
||||
def get_pid, do: list_to_binary(:os.getpid)
|
||||
@spec get_pid() :: binary
|
||||
def get_pid, do: iolist_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Set an environment variable value.
|
||||
|
||||
Sets a new `value` for the environment variable `varname`.
|
||||
"""
|
||||
@spec put_env(String.t, String.t | char_list) :: :ok
|
||||
def put_env(varname, value) when is_binary(value) or is_list(value) do
|
||||
:os.putenv to_char_list(varname), :unicode.characters_to_list(value)
|
||||
@spec put_env(binary, binary) :: :ok
|
||||
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
|
||||
:os.putenv :binary.bin_to_list(varname), String.to_char_list!(value)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set multiple environment variables.
|
||||
|
||||
Sets a new value for each environment variable corresponding
|
||||
to each key in `dict`.
|
||||
"""
|
||||
@@ -277,23 +337,24 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets Elixir's stacktrace.
|
||||
Last exception stacktrace.
|
||||
|
||||
Notice the Erlang VM (and therefore this function) does not
|
||||
Note that the Erlang VM (and therefore this function) does not
|
||||
return the current stacktrace but rather the stacktrace of the
|
||||
latest exception.
|
||||
"""
|
||||
def stacktrace do
|
||||
filter_stacktrace :erlang.get_stacktrace
|
||||
:erlang.get_stacktrace
|
||||
end
|
||||
|
||||
@doc """
|
||||
Halts the Erlang runtime system where the first argument status must be a
|
||||
non-negative integer, the atom `:abort` or any type that can be converted
|
||||
to a char list.
|
||||
Halt the Erlang runtime system.
|
||||
|
||||
Halts the Erlang runtime system where the argument `status` must be a
|
||||
non-negative integer, the atom `:abort` or a binary.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the Operating System;
|
||||
is returned to the operating system;
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
@@ -304,34 +365,24 @@ defmodule System do
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
For integer status, Erlang runtime system closes all ports and allows async
|
||||
threads to finish their operations before exiting. To exit without such
|
||||
flushing, pass options [flush: false] instead.
|
||||
|
||||
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-2
|
||||
For more information, check: http://www.erlang.org/doc/man/erlang.html#halt-1
|
||||
|
||||
## Examples
|
||||
|
||||
System.halt(0)
|
||||
System.halt(1, flush: false)
|
||||
System.halt(1)
|
||||
System.halt(:abort)
|
||||
|
||||
"""
|
||||
@spec halt() :: no_return
|
||||
@spec halt(non_neg_integer | List.Chars.t | :abort) :: no_return
|
||||
@spec halt(non_neg_integer | List.Chars.t | :abort, [] | [flush: false]) :: no_return
|
||||
def halt(status // 0, options // [])
|
||||
@spec halt(non_neg_integer | binary | :abort) :: no_return
|
||||
def halt(status // 0)
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
:erlang.halt(status, options)
|
||||
def halt(status) when is_integer(status) or status == :abort do
|
||||
:erlang.halt(status)
|
||||
end
|
||||
|
||||
def halt(status, options) do
|
||||
:erlang.halt(to_char_list(status), options)
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(:binary.bin_to_list(status))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: t
|
||||
defp filter_stacktrace(t), do: t
|
||||
end
|
||||
|
||||
+59
-2
@@ -1,3 +1,60 @@
|
||||
defmodule Tuple do
|
||||
@moduledoc false
|
||||
end
|
||||
@moduledoc """
|
||||
Functions for working with tuples.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Create a new tuple.
|
||||
|
||||
Creates a tuple of size `size` containing the
|
||||
given `data` at every position.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Tuple.duplicate(:hello, 3)
|
||||
{ :hello, :hello, :hello }
|
||||
|
||||
"""
|
||||
@spec duplicate(term, non_neg_integer) :: tuple
|
||||
def duplicate(data, size) do
|
||||
:erlang.make_tuple(size, data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Insert an element into a tuple.
|
||||
|
||||
Inserts `value` into `tuple` at the given zero-based `index`.
|
||||
Raises an `ArgumentError` if `index` is greater than the
|
||||
length of `tuple`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
...> Tuple.insert_at(tuple, 0, :foo)
|
||||
{ :foo, :bar, :baz }
|
||||
|
||||
"""
|
||||
@spec insert_at(tuple, non_neg_integer, term) :: tuple
|
||||
def insert_at(tuple, index, term) do
|
||||
:erlang.insert_element(index + 1, tuple, term)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
or equal to the length of `tuple`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :foo, :bar, :baz }
|
||||
...> Tuple.delete_at(tuple, 0)
|
||||
{ :bar, :baz }
|
||||
|
||||
"""
|
||||
@spec delete_at(tuple, non_neg_integer) :: tuple
|
||||
def delete_at(tuple, index) do
|
||||
:erlang.delete_element(index + 1, tuple)
|
||||
end
|
||||
end
|
||||
|
||||
+21
-18
@@ -31,8 +31,8 @@ defmodule URI do
|
||||
|
||||
@doc """
|
||||
Returns the default port for a given scheme.
|
||||
If the scheme is unknown to URI, returns nil.
|
||||
Any scheme may be registered via `URI.default_port/2`.
|
||||
If the scheme is unknown to URI, returns `nil`.
|
||||
Any scheme may be registered via `default_port/2`.
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
@@ -40,7 +40,7 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a scheme with a default port into Elixir.
|
||||
Registers a scheme with a default port.
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
@@ -49,19 +49,19 @@ defmodule URI do
|
||||
|
||||
@doc """
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of k=v&k2=v2... where keys and values are
|
||||
URL encoded as per encode. Keys and values can be any term
|
||||
that implements the Binary.Chars protocol (i.e. can be converted
|
||||
to binary).
|
||||
and returns a string of the form "k=v&k2=v2..." where keys and values are
|
||||
URL encoded as per `encode/1`. Keys and values can be any term
|
||||
that implements the `String.Chars` protocol (i.e. can be converted
|
||||
to a binary).
|
||||
"""
|
||||
def encode_query(l), do: Enum.map_join(l, "&", pair(&1))
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc """
|
||||
Given a query string of the form "key1=value1&key=value2...", produces an
|
||||
orddict with one entry for each key-value pair. Each key and value will be a
|
||||
binary. It also does percent-unescaping of both keys and values.
|
||||
|
||||
Use decoder/1 if you want to customize or iterate each value manually.
|
||||
Use `query_decoder/1` if you want to iterate over each value manually.
|
||||
"""
|
||||
def decode_query(q, dict // HashDict.new) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
@@ -98,7 +98,7 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
encode(to_binary(k)) <> "=" <> encode(to_binary(v))
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -150,14 +150,14 @@ defmodule URI do
|
||||
have different default ports. Sometimes the parsing
|
||||
of portions themselves are different. This parser
|
||||
is extensible via behavior modules. If you have a
|
||||
module named URI.MYSCHEME with a function called
|
||||
'parse' that takes a single argument, the generically
|
||||
module named `URI.MYSCHEME` with a function called
|
||||
`parse` that takes a single argument, the generically
|
||||
parsed URI, that function will be called when this
|
||||
parse function is passed a URI of that scheme. This
|
||||
allows you to build on top of what the URI library
|
||||
currently offers. You also need to define default_port
|
||||
which takes 0 arguments and returns the default port
|
||||
for that particular scheme. Take a look at URI.HTTPS for an
|
||||
currently offers. You also need to define `default_port`
|
||||
which takes no arguments and returns the default port
|
||||
for that particular scheme. Take a look at `URI.HTTPS` for an
|
||||
example of one of these extension modules.
|
||||
"""
|
||||
def parse(s) when is_binary(s) do
|
||||
@@ -192,9 +192,12 @@ defmodule URI do
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(s) do
|
||||
s = s || ""
|
||||
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
|
||||
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
|
||||
|
||||
{ userinfo, host, port }
|
||||
end
|
||||
|
||||
@@ -207,8 +210,8 @@ defmodule URI do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: URI.Info do
|
||||
def to_binary(URI.Info[] = uri) do
|
||||
defimpl String.Chars, for: URI.Info do
|
||||
def to_string(URI.Info[] = uri) do
|
||||
scheme = uri.scheme
|
||||
|
||||
if scheme && (port = URI.default_port(scheme)) do
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
defmodule Mix.Version do
|
||||
@moduledoc %B"""
|
||||
This module provides functions for parsing and matching
|
||||
versions with requirements.
|
||||
defmodule Version do
|
||||
@moduledoc %S"""
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string or a `Mix.Version.Schema` generated
|
||||
after parsing via `Mix.Version.parse/1`. A requirement is
|
||||
a string that follows a specific format.
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Mix.Version` parsing and requirements follows
|
||||
`Version` parsing and requirements follow
|
||||
[SemVer 2.0 schema](http://semver.org/) and you will get
|
||||
the most of Mix' version system by following it. In order
|
||||
the most of Mix's version system by following it. In order
|
||||
to support integration with projects that may
|
||||
follow different versioning schemas, Elixir won't choke
|
||||
on unknown versions, however you won't be able to use
|
||||
@@ -33,7 +31,7 @@ defmodule Mix.Version do
|
||||
|
||||
Requirements allow you to specify which versions of a given
|
||||
dependency you are willing to work against. It supports common
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that would
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that
|
||||
work as one would expect:
|
||||
|
||||
# Only version 2.0.0
|
||||
@@ -54,13 +52,13 @@ defmodule Mix.Version do
|
||||
|
||||
"""
|
||||
|
||||
@type t :: String.t | Mix.Version.Schema.t
|
||||
@type requirement :: String.t | Mix.Version.Requirement.t
|
||||
@type t :: String.t | Version.Schema.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, non_neg_integer } | nil }
|
||||
pre :: [String.t] }
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
|
||||
@@ -79,11 +77,22 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given version matches the specification.
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.match?("2.0", ">1.0")
|
||||
true
|
||||
iex> Version.match?("2.0", "==1.0")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, requirement) :: boolean
|
||||
def match?(version, requirement) when is_binary(requirement) do
|
||||
case Mix.Version.Parser.parse_requirement(requirement) do
|
||||
case Version.Parser.parse_requirement(requirement) do
|
||||
{ :ok, req } ->
|
||||
match?(version, req)
|
||||
{ :error, reason } ->
|
||||
@@ -105,24 +114,38 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a version string is compatible with [semver](http://semver.org/).
|
||||
Check if a version string is compatible with [semver](http://semver.org/).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.valid?("2.0")
|
||||
true
|
||||
iex> Version.valid?("invalid")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid?(String.t | Schema.t) :: boolean
|
||||
def valid?(string) when is_binary(string) do
|
||||
Mix.Version.Parser.valid_version?(string)
|
||||
Version.Parser.valid_version?(string)
|
||||
end
|
||||
|
||||
def valid?(Mix.Version.Schema[major: nil]), do: false
|
||||
def valid?(Mix.Version.Schema[]), do: true
|
||||
def valid?(Version.Schema[major: nil]), do: false
|
||||
def valid?(Version.Schema[]), do: true
|
||||
|
||||
@doc """
|
||||
Parse a version into a matchable value.
|
||||
Parse a version string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.parse("2.0.1-alpha1")
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: { :ok, Schema.t } | { :error, term }
|
||||
@spec parse(String.t) :: Schema.t
|
||||
def parse(string) when is_binary(string) do
|
||||
case Mix.Version.Parser.parse_version(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
|
||||
{ :error, _ } -> Mix.Version.Schema[source: string]
|
||||
{ :error, _ } -> Version.Schema[source: string]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -137,19 +160,25 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the matchable representation.
|
||||
Convert a version to a `Version.matchable`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.to_matchable("2.0.1-alpha.1")
|
||||
{2, 0, 1, ["alpha", 1]}
|
||||
|
||||
"""
|
||||
@spec to_matchable(String.t | Schema.t) :: Mix.Version.matchable
|
||||
@spec to_matchable(String.t | Schema.t) :: Version.matchable
|
||||
def to_matchable(Schema[major: nil, source: source]) do
|
||||
{ source, nil, nil, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
{ major, minor, patch, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Mix.Version.Parser.parse_pre(pre) }
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Version.Parser.parse_pre(pre) }
|
||||
end
|
||||
|
||||
def to_matchable(string) do
|
||||
@@ -157,11 +186,17 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a matchable to a `Mix.Version`.
|
||||
Convert a matchable to a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
@spec from_matchable(Mix.Version.matchable) :: Schema.t
|
||||
@spec from_matchable(Version.matchable) :: Schema.t
|
||||
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
|
||||
Mix.Version.Schema[source: source]
|
||||
Version.Schema[source: source]
|
||||
end
|
||||
|
||||
def from_matchable({ major, minor, patch, pre }) do
|
||||
@@ -184,7 +219,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
end
|
||||
|
||||
Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
end
|
||||
|
||||
defmodule Parser.DSL do
|
||||
@@ -251,12 +286,12 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
deflexer acc do
|
||||
Enum.filter(Enum.reverse(acc), &1 != :' ')
|
||||
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
|
||||
end
|
||||
|
||||
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
|
||||
|
||||
@spec parse_requirement(String.t) :: { :ok, Mix.Version.Requirement.t } | { :error, binary | atom }
|
||||
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
|
||||
def parse_requirement(source) do
|
||||
lexed = lexer(source, [])
|
||||
|
||||
@@ -269,7 +304,7 @@ defmodule Mix.Version do
|
||||
|
||||
{ :error, errors } ->
|
||||
{ :error, Enum.map(errors, fn { :error, reason } ->
|
||||
to_binary(reason)
|
||||
to_string(reason)
|
||||
end) }
|
||||
end
|
||||
else
|
||||
@@ -277,14 +312,17 @@ defmodule Mix.Version do
|
||||
end
|
||||
end
|
||||
|
||||
@spec parse_version(String.t) :: { :ok, Mix.Version.matchable } | { :error, :invalid_version }
|
||||
defp nillify(""), do: nil
|
||||
defp nillify(o), do: o
|
||||
|
||||
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
|
||||
def parse_version(string) when is_binary(string) do
|
||||
if valid_version?(string) do
|
||||
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
|
||||
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor || "0")
|
||||
patch = binary_to_integer(patch || "0")
|
||||
minor = binary_to_integer(minor |> nillify || "0")
|
||||
patch = binary_to_integer(patch |> nillify || "0")
|
||||
pre = pre && parse_pre(pre) || []
|
||||
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
@@ -296,7 +334,7 @@ defmodule Mix.Version do
|
||||
@doc false
|
||||
def parse_pre(pre) do
|
||||
String.split(pre, ".") |> Enum.map fn piece ->
|
||||
if piece =~ %r/^[1-9][0-9]*$/ do
|
||||
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
|
||||
binary_to_integer(piece)
|
||||
else
|
||||
piece
|
||||
@@ -361,7 +399,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp approximate(version) do
|
||||
Mix.Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
[_, major] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [] }
|
||||
|
||||
@@ -384,39 +422,71 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'==', version | _]) do
|
||||
version = Mix.Version.to_matchable(version)
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = Mix.Version.to_matchable(version)
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
{ :'/=', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'~>', version | _]) do
|
||||
from = Mix.Version.parse(version)
|
||||
from = Version.parse(version)
|
||||
to = approximate(version)
|
||||
|
||||
{ :andalso, to_condition([:'>=', to_binary(from)]),
|
||||
to_condition([:'<', to_binary(to)]) }
|
||||
{ :andalso, to_condition([:'>=', to_string(from)]),
|
||||
to_condition([:'<', to_string(to)]) }
|
||||
end
|
||||
|
||||
defp to_condition([:'>', version | _]) do
|
||||
matchspec(version, :'>', :<)
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { :andalso, { :'==', { :length, :'$4' }, 0 },
|
||||
{ :'/=', length(pre), 0 } },
|
||||
{ :andalso, { :'/=', length(pre), 0 },
|
||||
{ :orelse, { :'>', { :length, :'$4' }, length(pre) },
|
||||
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
|
||||
{ :'>', :'$4', { :const, pre } } } } } } } } }
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchspec(version, :'>=', :<)
|
||||
matchable = Version.to_matchable(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
to_condition([:'>', version]) }
|
||||
end
|
||||
|
||||
defp to_condition([:'<', version | _]) do
|
||||
matchspec(version, :'<', :>)
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { :andalso, { :'/=', { :length, :'$4' }, 0 },
|
||||
{ :'==', length(pre), 0 } },
|
||||
{ :andalso, { :'/=', { :length, :'$4' }, 0 },
|
||||
{ :orelse, { :'<', { :length, :'$4' }, length(pre) },
|
||||
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
|
||||
{ :'<', :'$4', { :const, pre } } } } } } } } }
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchspec(version, :'=<', :>)
|
||||
matchable = Version.to_matchable(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
to_condition([:'<', version]) }
|
||||
end
|
||||
|
||||
defp to_condition(current, []) do
|
||||
@@ -430,41 +500,29 @@ defmodule Mix.Version do
|
||||
defp to_condition(current, [:'||', operator, version | rest]) do
|
||||
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
|
||||
end
|
||||
|
||||
defp matchspec(version, comp_op, length_op) do
|
||||
{ major, minor, patch, pre } = Mix.Version.to_matchable(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { comp_op, {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { length_op, { :length, :'$4' }, { :const, length(pre) } },
|
||||
{ comp_op, :'$4', { :const, pre } } } } } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: Mix.Version.Schema do
|
||||
def to_binary(Mix.Version.Schema[source: source]) do
|
||||
defimpl String.Chars, for: Version.Schema do
|
||||
def to_string(Version.Schema[source: source]) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Mix.Version.Schema do
|
||||
defimpl Inspect, for: Version.Schema do
|
||||
def inspect(self, _opts) do
|
||||
"#Mix.Version.Schema<" <> to_binary(self) <> ">"
|
||||
"#Version.Schema<" <> to_string(self) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: Mix.Version.Requirement do
|
||||
def to_binary({ _, source, _ }) do
|
||||
defimpl String.Chars, for: Version.Requirement do
|
||||
def to_string({ _, source, _ }) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Mix.Version.Requirement do
|
||||
defimpl Inspect, for: Version.Requirement do
|
||||
def inspect({ _, source, _ }, _opts) do
|
||||
"#Mix.Version.Requirement<" <> source <> ">"
|
||||
"#Version.Requirement<" <> source <> ">"
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,420 +0,0 @@
|
||||
LATIN CAPITAL LETTER A WITH MACRON AND GRAVE;0100 0300
|
||||
LATIN SMALL LETTER A WITH MACRON AND GRAVE;0101 0300
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW;0045 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW;0065 0329
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00C8 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00E8 0329
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00C9 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00E9 0329
|
||||
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND MACRON;00CA 0304
|
||||
LATIN SMALL LETTER E WITH CIRCUMFLEX AND MACRON;00EA 0304
|
||||
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND CARON;00CA 030C
|
||||
LATIN SMALL LETTER E WITH CIRCUMFLEX AND CARON;00EA 030C
|
||||
LATIN CAPITAL LETTER I WITH MACRON AND GRAVE;012A 0300
|
||||
LATIN SMALL LETTER I WITH MACRON AND GRAVE;012B 0300
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND ACUTE;0069 0307 0301
|
||||
LATIN SMALL LETTER NG WITH TILDE ABOVE;006E 0360 0067
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW;004F 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW;006F 0329
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00D2 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00F2 0329
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00D3 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00F3 0329
|
||||
LATIN CAPITAL LETTER S WITH VERTICAL LINE BELOW;0053 0329
|
||||
LATIN SMALL LETTER S WITH VERTICAL LINE BELOW;0073 0329
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND GRAVE;016A 0300
|
||||
LATIN SMALL LETTER U WITH MACRON AND GRAVE;016B 0300
|
||||
LATIN CAPITAL LETTER A WITH OGONEK AND ACUTE;0104 0301
|
||||
LATIN SMALL LETTER A WITH OGONEK AND ACUTE;0105 0301
|
||||
LATIN CAPITAL LETTER A WITH OGONEK AND TILDE;0104 0303
|
||||
LATIN SMALL LETTER A WITH OGONEK AND TILDE;0105 0303
|
||||
LATIN CAPITAL LETTER E WITH OGONEK AND ACUTE;0118 0301
|
||||
LATIN SMALL LETTER E WITH OGONEK AND ACUTE;0119 0301
|
||||
LATIN CAPITAL LETTER E WITH OGONEK AND TILDE;0118 0303
|
||||
LATIN SMALL LETTER E WITH OGONEK AND TILDE;0119 0303
|
||||
LATIN CAPITAL LETTER E WITH DOT ABOVE AND ACUTE;0116 0301
|
||||
LATIN SMALL LETTER E WITH DOT ABOVE AND ACUTE;0117 0301
|
||||
LATIN CAPITAL LETTER E WITH DOT ABOVE AND TILDE;0116 0303
|
||||
LATIN SMALL LETTER E WITH DOT ABOVE AND TILDE;0117 0303
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND GRAVE;0069 0307 0300
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND TILDE;0069 0307 0303
|
||||
LATIN CAPITAL LETTER I WITH OGONEK AND ACUTE;012E 0301
|
||||
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND ACUTE;012F 0307 0301
|
||||
LATIN CAPITAL LETTER I WITH OGONEK AND TILDE;012E 0303
|
||||
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND TILDE;012F 0307 0303
|
||||
LATIN CAPITAL LETTER J WITH TILDE;004A 0303
|
||||
LATIN SMALL LETTER J WITH DOT ABOVE AND TILDE;006A 0307 0303
|
||||
LATIN CAPITAL LETTER L WITH TILDE;004C 0303
|
||||
LATIN SMALL LETTER L WITH TILDE;006C 0303
|
||||
LATIN CAPITAL LETTER M WITH TILDE;004D 0303
|
||||
LATIN SMALL LETTER M WITH TILDE;006D 0303
|
||||
LATIN CAPITAL LETTER R WITH TILDE;0052 0303
|
||||
LATIN SMALL LETTER R WITH TILDE;0072 0303
|
||||
LATIN CAPITAL LETTER U WITH OGONEK AND ACUTE;0172 0301
|
||||
LATIN SMALL LETTER U WITH OGONEK AND ACUTE;0173 0301
|
||||
LATIN CAPITAL LETTER U WITH OGONEK AND TILDE;0172 0303
|
||||
LATIN SMALL LETTER U WITH OGONEK AND TILDE;0173 0303
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND ACUTE;016A 0301
|
||||
LATIN SMALL LETTER U WITH MACRON AND ACUTE;016B 0301
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND TILDE;016A 0303
|
||||
LATIN SMALL LETTER U WITH MACRON AND TILDE;016B 0303
|
||||
LATIN SMALL LETTER AE WITH GRAVE;00E6 0300
|
||||
LATIN SMALL LETTER OPEN O WITH GRAVE;0254 0300
|
||||
LATIN SMALL LETTER OPEN O WITH ACUTE;0254 0301
|
||||
LATIN SMALL LETTER TURNED V WITH GRAVE;028C 0300
|
||||
LATIN SMALL LETTER TURNED V WITH ACUTE;028C 0301
|
||||
LATIN SMALL LETTER SCHWA WITH GRAVE;0259 0300
|
||||
LATIN SMALL LETTER SCHWA WITH ACUTE;0259 0301
|
||||
LATIN SMALL LETTER HOOKED SCHWA WITH GRAVE;025A 0300
|
||||
LATIN SMALL LETTER HOOKED SCHWA WITH ACUTE;025A 0301
|
||||
BENGALI LETTER KHINYA;0995 09CD 09B7
|
||||
TAMIL CONSONANT K; 0B95 0BCD
|
||||
TAMIL CONSONANT NG; 0B99 0BCD
|
||||
TAMIL CONSONANT C; 0B9A 0BCD
|
||||
TAMIL CONSONANT NY; 0B9E 0BCD
|
||||
TAMIL CONSONANT TT; 0B9F 0BCD
|
||||
TAMIL CONSONANT NN; 0BA3 0BCD
|
||||
TAMIL CONSONANT T; 0BA4 0BCD
|
||||
TAMIL CONSONANT N; 0BA8 0BCD
|
||||
TAMIL CONSONANT P; 0BAA 0BCD
|
||||
TAMIL CONSONANT M; 0BAE 0BCD
|
||||
TAMIL CONSONANT Y; 0BAF 0BCD
|
||||
TAMIL CONSONANT R; 0BB0 0BCD
|
||||
TAMIL CONSONANT L; 0BB2 0BCD
|
||||
TAMIL CONSONANT V; 0BB5 0BCD
|
||||
TAMIL CONSONANT LLL;0BB4 0BCD
|
||||
TAMIL CONSONANT LL; 0BB3 0BCD
|
||||
TAMIL CONSONANT RR; 0BB1 0BCD
|
||||
TAMIL CONSONANT NNN;0BA9 0BCD
|
||||
TAMIL CONSONANT J; 0B9C 0BCD
|
||||
TAMIL CONSONANT SH; 0BB6 0BCD
|
||||
TAMIL CONSONANT SS; 0BB7 0BCD
|
||||
TAMIL CONSONANT S; 0BB8 0BCD
|
||||
TAMIL CONSONANT H; 0BB9 0BCD
|
||||
TAMIL CONSONANT KSS;0B95 0BCD 0BB7 0BCD
|
||||
TAMIL SYLLABLE KAA; 0B95 0BBE
|
||||
TAMIL SYLLABLE KI; 0B95 0BBF
|
||||
TAMIL SYLLABLE KII; 0B95 0BC0
|
||||
TAMIL SYLLABLE KU; 0B95 0BC1
|
||||
TAMIL SYLLABLE KUU; 0B95 0BC2
|
||||
TAMIL SYLLABLE KE; 0B95 0BC6
|
||||
TAMIL SYLLABLE KEE; 0B95 0BC7
|
||||
TAMIL SYLLABLE KAI; 0B95 0BC8
|
||||
TAMIL SYLLABLE KO; 0B95 0BCA
|
||||
TAMIL SYLLABLE KOO; 0B95 0BCB
|
||||
TAMIL SYLLABLE KAU; 0B95 0BCC
|
||||
TAMIL SYLLABLE NGAA; 0B99 0BBE
|
||||
TAMIL SYLLABLE NGI; 0B99 0BBF
|
||||
TAMIL SYLLABLE NGII; 0B99 0BC0
|
||||
TAMIL SYLLABLE NGU; 0B99 0BC1
|
||||
TAMIL SYLLABLE NGUU; 0B99 0BC2
|
||||
TAMIL SYLLABLE NGE; 0B99 0BC6
|
||||
TAMIL SYLLABLE NGEE; 0B99 0BC7
|
||||
TAMIL SYLLABLE NGAI; 0B99 0BC8
|
||||
TAMIL SYLLABLE NGO; 0B99 0BCA
|
||||
TAMIL SYLLABLE NGOO; 0B99 0BCB
|
||||
TAMIL SYLLABLE NGAU; 0B99 0BCC
|
||||
TAMIL SYLLABLE CI; 0B9A 0BBF
|
||||
TAMIL SYLLABLE CII; 0B9A 0BC0
|
||||
TAMIL SYLLABLE CU; 0B9A 0BC1
|
||||
TAMIL SYLLABLE CUU; 0B9A 0BC2
|
||||
TAMIL SYLLABLE CE; 0B9A 0BC6
|
||||
TAMIL SYLLABLE CEE; 0B9A 0BC7
|
||||
TAMIL SYLLABLE CAI; 0B9A 0BC8
|
||||
TAMIL SYLLABLE CO; 0B9A 0BCA
|
||||
TAMIL SYLLABLE COO; 0B9A 0BCB
|
||||
TAMIL SYLLABLE CAU; 0B9A 0BCC
|
||||
TAMIL SYLLABLE NYAA; 0B9E 0BBE
|
||||
TAMIL SYLLABLE NYI; 0B9E 0BBF
|
||||
TAMIL SYLLABLE NYII; 0B9E 0BC0
|
||||
TAMIL SYLLABLE NYU; 0B9E 0BC1
|
||||
TAMIL SYLLABLE NYUU; 0B9E 0BC2
|
||||
TAMIL SYLLABLE NYE; 0B9E 0BC6
|
||||
TAMIL SYLLABLE NYEE; 0B9E 0BC7
|
||||
TAMIL SYLLABLE NYAI; 0B9E 0BC8
|
||||
TAMIL SYLLABLE NYO; 0B9E 0BCA
|
||||
TAMIL SYLLABLE NYOO; 0B9E 0BCB
|
||||
TAMIL SYLLABLE NYAU; 0B9E 0BCC
|
||||
TAMIL SYLLABLE TTAA; 0B9F 0BBE
|
||||
TAMIL SYLLABLE TTI; 0B9F 0BBF
|
||||
TAMIL SYLLABLE TTII; 0B9F 0BC0
|
||||
TAMIL SYLLABLE TTU; 0B9F 0BC1
|
||||
TAMIL SYLLABLE TTUU; 0B9F 0BC2
|
||||
TAMIL SYLLABLE TTE; 0B9F 0BC6
|
||||
TAMIL SYLLABLE TTEE; 0B9F 0BC7
|
||||
TAMIL SYLLABLE TTAI; 0B9F 0BC8
|
||||
TAMIL SYLLABLE TTO; 0B9F 0BCA
|
||||
TAMIL SYLLABLE TTOO; 0B9F 0BCB
|
||||
TAMIL SYLLABLE TTAU; 0B9F 0BCC
|
||||
TAMIL SYLLABLE NNAA; 0BA3 0BBE
|
||||
TAMIL SYLLABLE NNI; 0BA3 0BBF
|
||||
TAMIL SYLLABLE NNII; 0BA3 0BC0
|
||||
TAMIL SYLLABLE NNU; 0BA3 0BC1
|
||||
TAMIL SYLLABLE NNUU; 0BA3 0BC2
|
||||
TAMIL SYLLABLE NNE; 0BA3 0BC6
|
||||
TAMIL SYLLABLE NNEE; 0BA3 0BC7
|
||||
TAMIL SYLLABLE NNAI; 0BA3 0BC8
|
||||
TAMIL SYLLABLE NNO; 0BA3 0BCA
|
||||
TAMIL SYLLABLE NNOO; 0BA3 0BCB
|
||||
TAMIL SYLLABLE NNAU; 0BA3 0BCC
|
||||
TAMIL SYLLABLE TAA; 0BA4 0BBE
|
||||
TAMIL SYLLABLE TI; 0BA4 0BBF
|
||||
TAMIL SYLLABLE TII; 0BA4 0BC0
|
||||
TAMIL SYLLABLE TU; 0BA4 0BC1
|
||||
TAMIL SYLLABLE TUU; 0BA4 0BC2
|
||||
TAMIL SYLLABLE TE; 0BA4 0BC6
|
||||
TAMIL SYLLABLE TEE; 0BA4 0BC7
|
||||
TAMIL SYLLABLE TAI; 0BA4 0BC8
|
||||
TAMIL SYLLABLE TO; 0BA4 0BCA
|
||||
TAMIL SYLLABLE TOO; 0BA4 0BCB
|
||||
TAMIL SYLLABLE TAU; 0BA4 0BCC
|
||||
TAMIL SYLLABLE NAA; 0BA8 0BBE
|
||||
TAMIL SYLLABLE NI; 0BA8 0BBF
|
||||
TAMIL SYLLABLE NII; 0BA8 0BC0
|
||||
TAMIL SYLLABLE NU; 0BA8 0BC1
|
||||
TAMIL SYLLABLE NUU; 0BA8 0BC2
|
||||
TAMIL SYLLABLE NE; 0BA8 0BC6
|
||||
TAMIL SYLLABLE NEE; 0BA8 0BC7
|
||||
TAMIL SYLLABLE NAI; 0BA8 0BC8
|
||||
TAMIL SYLLABLE NO; 0BA8 0BCA
|
||||
TAMIL SYLLABLE NOO; 0BA8 0BCB
|
||||
TAMIL SYLLABLE NAU; 0BA8 0BCC
|
||||
TAMIL SYLLABLE PAA; 0BAA 0BBE
|
||||
TAMIL SYLLABLE PI; 0BAA 0BBF
|
||||
TAMIL SYLLABLE PII; 0BAA 0BC0
|
||||
TAMIL SYLLABLE PU; 0BAA 0BC1
|
||||
TAMIL SYLLABLE PUU; 0BAA 0BC2
|
||||
TAMIL SYLLABLE PE; 0BAA 0BC6
|
||||
TAMIL SYLLABLE PEE; 0BAA 0BC7
|
||||
TAMIL SYLLABLE PAI; 0BAA 0BC8
|
||||
TAMIL SYLLABLE PO; 0BAA 0BCA
|
||||
TAMIL SYLLABLE POO; 0BAA 0BCB
|
||||
TAMIL SYLLABLE PAU; 0BAA 0BCC
|
||||
TAMIL SYLLABLE MAA; 0BAE 0BBE
|
||||
TAMIL SYLLABLE MI; 0BAE 0BBF
|
||||
TAMIL SYLLABLE MII; 0BAE 0BC0
|
||||
TAMIL SYLLABLE MU; 0BAE 0BC1
|
||||
TAMIL SYLLABLE MUU; 0BAE 0BC2
|
||||
TAMIL SYLLABLE ME; 0BAE 0BC6
|
||||
TAMIL SYLLABLE MEE; 0BAE 0BC7
|
||||
TAMIL SYLLABLE MAI; 0BAE 0BC8
|
||||
TAMIL SYLLABLE MO; 0BAE 0BCA
|
||||
TAMIL SYLLABLE MOO; 0BAE 0BCB
|
||||
TAMIL SYLLABLE MAU; 0BAE 0BCC
|
||||
TAMIL SYLLABLE YAA; 0BAF 0BBE
|
||||
TAMIL SYLLABLE YI; 0BAF 0BBF
|
||||
TAMIL SYLLABLE YII; 0BAF 0BC0
|
||||
TAMIL SYLLABLE YU; 0BAF 0BC1
|
||||
TAMIL SYLLABLE YUU; 0BAF 0BC2
|
||||
TAMIL SYLLABLE YE; 0BAF 0BC6
|
||||
TAMIL SYLLABLE YEE; 0BAF 0BC7
|
||||
TAMIL SYLLABLE YAI; 0BAF 0BC8
|
||||
TAMIL SYLLABLE YO; 0BAF 0BCA
|
||||
TAMIL SYLLABLE YOO; 0BAF 0BCB
|
||||
TAMIL SYLLABLE YAU; 0BAF 0BCC
|
||||
TAMIL SYLLABLE RAA; 0BB0 0BBE
|
||||
TAMIL SYLLABLE RI; 0BB0 0BBF
|
||||
TAMIL SYLLABLE RII; 0BB0 0BC0
|
||||
TAMIL SYLLABLE RU; 0BB0 0BC1
|
||||
TAMIL SYLLABLE RUU; 0BB0 0BC2
|
||||
TAMIL SYLLABLE RE; 0BB0 0BC6
|
||||
TAMIL SYLLABLE REE; 0BB0 0BC7
|
||||
TAMIL SYLLABLE RAI; 0BB0 0BC8
|
||||
TAMIL SYLLABLE RO; 0BB0 0BCA
|
||||
TAMIL SYLLABLE ROO; 0BB0 0BCB
|
||||
TAMIL SYLLABLE RAU; 0BB0 0BCC
|
||||
TAMIL SYLLABLE LAA; 0BB2 0BBE
|
||||
TAMIL SYLLABLE LI; 0BB2 0BBF
|
||||
TAMIL SYLLABLE LII; 0BB2 0BC0
|
||||
TAMIL SYLLABLE LU; 0BB2 0BC1
|
||||
TAMIL SYLLABLE LUU; 0BB2 0BC2
|
||||
TAMIL SYLLABLE LE; 0BB2 0BC6
|
||||
TAMIL SYLLABLE LEE; 0BB2 0BC7
|
||||
TAMIL SYLLABLE LAI; 0BB2 0BC8
|
||||
TAMIL SYLLABLE LO; 0BB2 0BCA
|
||||
TAMIL SYLLABLE LOO; 0BB2 0BCB
|
||||
TAMIL SYLLABLE LAU; 0BB2 0BCC
|
||||
TAMIL SYLLABLE VAA; 0BB5 0BBE
|
||||
TAMIL SYLLABLE VI; 0BB5 0BBF
|
||||
TAMIL SYLLABLE VII; 0BB5 0BC0
|
||||
TAMIL SYLLABLE VU; 0BB5 0BC1
|
||||
TAMIL SYLLABLE VUU; 0BB5 0BC2
|
||||
TAMIL SYLLABLE VE; 0BB5 0BC6
|
||||
TAMIL SYLLABLE VEE; 0BB5 0BC7
|
||||
TAMIL SYLLABLE VAI; 0BB5 0BC8
|
||||
TAMIL SYLLABLE VO; 0BB5 0BCA
|
||||
TAMIL SYLLABLE VOO; 0BB5 0BCB
|
||||
TAMIL SYLLABLE VAU; 0BB5 0BCC
|
||||
TAMIL SYLLABLE LLLAA; 0BB4 0BBE
|
||||
TAMIL SYLLABLE LLLI; 0BB4 0BBF
|
||||
TAMIL SYLLABLE LLLII; 0BB4 0BC0
|
||||
TAMIL SYLLABLE LLLU; 0BB4 0BC1
|
||||
TAMIL SYLLABLE LLLUU; 0BB4 0BC2
|
||||
TAMIL SYLLABLE LLLE; 0BB4 0BC6
|
||||
TAMIL SYLLABLE LLLEE; 0BB4 0BC7
|
||||
TAMIL SYLLABLE LLLAI; 0BB4 0BC8
|
||||
TAMIL SYLLABLE LLLO; 0BB4 0BCA
|
||||
TAMIL SYLLABLE LLLOO; 0BB4 0BCB
|
||||
TAMIL SYLLABLE LLLAU; 0BB4 0BCC
|
||||
TAMIL SYLLABLE LLAA; 0BB3 0BBE
|
||||
TAMIL SYLLABLE LLI; 0BB3 0BBF
|
||||
TAMIL SYLLABLE LLII; 0BB3 0BC0
|
||||
TAMIL SYLLABLE LLU; 0BB3 0BC1
|
||||
TAMIL SYLLABLE LLUU; 0BB3 0BC2
|
||||
TAMIL SYLLABLE LLE; 0BB3 0BC6
|
||||
TAMIL SYLLABLE LLEE; 0BB3 0BC7
|
||||
TAMIL SYLLABLE LLAI; 0BB3 0BC8
|
||||
TAMIL SYLLABLE LLO; 0BB3 0BCA
|
||||
TAMIL SYLLABLE LLOO; 0BB3 0BCB
|
||||
TAMIL SYLLABLE LLAU; 0BB3 0BCC
|
||||
TAMIL SYLLABLE RRAA; 0BB1 0BBE
|
||||
TAMIL SYLLABLE RRI; 0BB1 0BBF
|
||||
TAMIL SYLLABLE RRII; 0BB1 0BC0
|
||||
TAMIL SYLLABLE RRU; 0BB1 0BC1
|
||||
TAMIL SYLLABLE RRUU; 0BB1 0BC2
|
||||
TAMIL SYLLABLE RRE; 0BB1 0BC6
|
||||
TAMIL SYLLABLE RREE; 0BB1 0BC7
|
||||
TAMIL SYLLABLE RRAI; 0BB1 0BC8
|
||||
TAMIL SYLLABLE RRO; 0BB1 0BCA
|
||||
TAMIL SYLLABLE RROO; 0BB1 0BCB
|
||||
TAMIL SYLLABLE RRAU; 0BB1 0BCC
|
||||
TAMIL SYLLABLE NNNAA; 0BA9 0BBE
|
||||
TAMIL SYLLABLE NNNI; 0BA9 0BBF
|
||||
TAMIL SYLLABLE NNNII; 0BA9 0BC0
|
||||
TAMIL SYLLABLE NNNU; 0BA9 0BC1
|
||||
TAMIL SYLLABLE NNNUU; 0BA9 0BC2
|
||||
TAMIL SYLLABLE NNNE; 0BA9 0BC6
|
||||
TAMIL SYLLABLE NNNEE; 0BA9 0BC7
|
||||
TAMIL SYLLABLE NNNAI; 0BA9 0BC8
|
||||
TAMIL SYLLABLE NNNO; 0BA9 0BCA
|
||||
TAMIL SYLLABLE NNNOO; 0BA9 0BCB
|
||||
TAMIL SYLLABLE NNNAU; 0BA9 0BCC
|
||||
TAMIL SYLLABLE JAA; 0B9C 0BBE
|
||||
TAMIL SYLLABLE JI; 0B9C 0BBF
|
||||
TAMIL SYLLABLE JII; 0B9C 0BC0
|
||||
TAMIL SYLLABLE JU; 0B9C 0BC1
|
||||
TAMIL SYLLABLE JUU; 0B9C 0BC2
|
||||
TAMIL SYLLABLE JE; 0B9C 0BC6
|
||||
TAMIL SYLLABLE JEE; 0B9C 0BC7
|
||||
TAMIL SYLLABLE JAI; 0B9C 0BC8
|
||||
TAMIL SYLLABLE JO; 0B9C 0BCA
|
||||
TAMIL SYLLABLE JOO; 0B9C 0BCB
|
||||
TAMIL SYLLABLE JAU; 0B9C 0BCC
|
||||
TAMIL SYLLABLE SHAA; 0BB6 0BBE
|
||||
TAMIL SYLLABLE SHI; 0BB6 0BBF
|
||||
TAMIL SYLLABLE SHII; 0BB6 0BC0
|
||||
TAMIL SYLLABLE SHU; 0BB6 0BC1
|
||||
TAMIL SYLLABLE SHUU; 0BB6 0BC2
|
||||
TAMIL SYLLABLE SHE; 0BB6 0BC6
|
||||
TAMIL SYLLABLE SHEE; 0BB6 0BC7
|
||||
TAMIL SYLLABLE SHAI; 0BB6 0BC8
|
||||
TAMIL SYLLABLE SHO; 0BB6 0BCA
|
||||
TAMIL SYLLABLE SHOO; 0BB6 0BCB
|
||||
TAMIL SYLLABLE SHAU; 0BB6 0BCC
|
||||
TAMIL SYLLABLE SSAA; 0BB7 0BBE
|
||||
TAMIL SYLLABLE SSI; 0BB7 0BBF
|
||||
TAMIL SYLLABLE SSII; 0BB7 0BC0
|
||||
TAMIL SYLLABLE SSU; 0BB7 0BC1
|
||||
TAMIL SYLLABLE SSUU; 0BB7 0BC2
|
||||
TAMIL SYLLABLE SSE; 0BB7 0BC6
|
||||
TAMIL SYLLABLE SSEE; 0BB7 0BC7
|
||||
TAMIL SYLLABLE SSAI; 0BB7 0BC8
|
||||
TAMIL SYLLABLE SSO; 0BB7 0BCA
|
||||
TAMIL SYLLABLE SSOO; 0BB7 0BCB
|
||||
TAMIL SYLLABLE SSAU; 0BB7 0BCC
|
||||
TAMIL SYLLABLE SAA; 0BB8 0BBE
|
||||
TAMIL SYLLABLE SI; 0BB8 0BBF
|
||||
TAMIL SYLLABLE SII; 0BB8 0BC0
|
||||
TAMIL SYLLABLE SU; 0BB8 0BC1
|
||||
TAMIL SYLLABLE SUU; 0BB8 0BC2
|
||||
TAMIL SYLLABLE SE; 0BB8 0BC6
|
||||
TAMIL SYLLABLE SEE; 0BB8 0BC7
|
||||
TAMIL SYLLABLE SAI; 0BB8 0BC8
|
||||
TAMIL SYLLABLE SO; 0BB8 0BCA
|
||||
TAMIL SYLLABLE SOO; 0BB8 0BCB
|
||||
TAMIL SYLLABLE SAU; 0BB8 0BCC
|
||||
TAMIL SYLLABLE HAA; 0BB9 0BBE
|
||||
TAMIL SYLLABLE HI; 0BB9 0BBF
|
||||
TAMIL SYLLABLE HII; 0BB9 0BC0
|
||||
TAMIL SYLLABLE HU; 0BB9 0BC1
|
||||
TAMIL SYLLABLE HUU; 0BB9 0BC2
|
||||
TAMIL SYLLABLE HE; 0BB9 0BC6
|
||||
TAMIL SYLLABLE HEE; 0BB9 0BC7
|
||||
TAMIL SYLLABLE HAI; 0BB9 0BC8
|
||||
TAMIL SYLLABLE HO; 0BB9 0BCA
|
||||
TAMIL SYLLABLE HOO; 0BB9 0BCB
|
||||
TAMIL SYLLABLE HAU; 0BB9 0BCC
|
||||
TAMIL SYLLABLE KSSA; 0B95 0BCD 0BB7
|
||||
TAMIL SYLLABLE KSSAA; 0B95 0BCD 0BB7 0BBE
|
||||
TAMIL SYLLABLE KSSI; 0B95 0BCD 0BB7 0BBF
|
||||
TAMIL SYLLABLE KSSII; 0B95 0BCD 0BB7 0BC0
|
||||
TAMIL SYLLABLE KSSU; 0B95 0BCD 0BB7 0BC1
|
||||
TAMIL SYLLABLE KSSUU; 0B95 0BCD 0BB7 0BC2
|
||||
TAMIL SYLLABLE KSSE; 0B95 0BCD 0BB7 0BC6
|
||||
TAMIL SYLLABLE KSSEE; 0B95 0BCD 0BB7 0BC7
|
||||
TAMIL SYLLABLE KSSAI; 0B95 0BCD 0BB7 0BC8
|
||||
TAMIL SYLLABLE KSSO; 0B95 0BCD 0BB7 0BCA
|
||||
TAMIL SYLLABLE KSSOO; 0B95 0BCD 0BB7 0BCB
|
||||
TAMIL SYLLABLE KSSAU; 0B95 0BCD 0BB7 0BCC
|
||||
TAMIL SYLLABLE SHRII; 0BB6 0BCD 0BB0 0BC0
|
||||
SINHALA CONSONANT SIGN YANSAYA;0DCA 200D 0DBA
|
||||
SINHALA CONSONANT SIGN RAKAARAANSAYA;0DCA 200D 0DBB
|
||||
SINHALA CONSONANT SIGN REPAYA;0DBB 0DCA 200D
|
||||
GEORGIAN LETTER U-BRJGU;10E3 0302
|
||||
KHMER CONSONANT SIGN COENG KA;17D2 1780
|
||||
KHMER CONSONANT SIGN COENG KHA;17D2 1781
|
||||
KHMER CONSONANT SIGN COENG KO;17D2 1782
|
||||
KHMER CONSONANT SIGN COENG KHO;17D2 1783
|
||||
KHMER CONSONANT SIGN COENG NGO;17D2 1784
|
||||
KHMER CONSONANT SIGN COENG CA;17D2 1785
|
||||
KHMER CONSONANT SIGN COENG CHA;17D2 1786
|
||||
KHMER CONSONANT SIGN COENG CO;17D2 1787
|
||||
KHMER CONSONANT SIGN COENG CHO;17D2 1788
|
||||
KHMER CONSONANT SIGN COENG NYO;17D2 1789
|
||||
KHMER CONSONANT SIGN COENG DA;17D2 178A
|
||||
KHMER CONSONANT SIGN COENG TTHA;17D2 178B
|
||||
KHMER CONSONANT SIGN COENG DO;17D2 178C
|
||||
KHMER CONSONANT SIGN COENG TTHO;17D2 178D
|
||||
KHMER CONSONANT SIGN COENG NA;17D2 178E
|
||||
KHMER CONSONANT SIGN COENG TA;17D2 178F
|
||||
KHMER CONSONANT SIGN COENG THA;17D2 1790
|
||||
KHMER CONSONANT SIGN COENG TO;17D2 1791
|
||||
KHMER CONSONANT SIGN COENG THO;17D2 1792
|
||||
KHMER CONSONANT SIGN COENG NO;17D2 1793
|
||||
KHMER CONSONANT SIGN COENG BA;17D2 1794
|
||||
KHMER CONSONANT SIGN COENG PHA;17D2 1795
|
||||
KHMER CONSONANT SIGN COENG PO;17D2 1796
|
||||
KHMER CONSONANT SIGN COENG PHO;17D2 1797
|
||||
KHMER CONSONANT SIGN COENG MO;17D2 1798
|
||||
KHMER CONSONANT SIGN COENG YO;17D2 1799
|
||||
KHMER CONSONANT SIGN COENG RO;17D2 179A
|
||||
KHMER CONSONANT SIGN COENG LO;17D2 179B
|
||||
KHMER CONSONANT SIGN COENG VO;17D2 179C
|
||||
KHMER CONSONANT SIGN COENG SHA;17D2 179D
|
||||
KHMER CONSONANT SIGN COENG SSA;17D2 179E
|
||||
KHMER CONSONANT SIGN COENG SA;17D2 179F
|
||||
KHMER CONSONANT SIGN COENG HA;17D2 17A0
|
||||
KHMER CONSONANT SIGN COENG LA;17D2 17A1
|
||||
KHMER VOWEL SIGN COENG QA;17D2 17A2
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG QU;17D2 17A7
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG RY;17D2 17AB
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG RYY;17D2 17AC
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG QE;17D2 17AF
|
||||
KHMER VOWEL SIGN OM;17BB 17C6
|
||||
KHMER VOWEL SIGN AAM;17B6 17C6
|
||||
HIRAGANA LETTER BIDAKUON NGA;304B 309A
|
||||
HIRAGANA LETTER BIDAKUON NGI;304D 309A
|
||||
HIRAGANA LETTER BIDAKUON NGU;304F 309A
|
||||
HIRAGANA LETTER BIDAKUON NGE;3051 309A
|
||||
HIRAGANA LETTER BIDAKUON NGO;3053 309A
|
||||
KATAKANA LETTER BIDAKUON NGA;30AB 309A
|
||||
KATAKANA LETTER BIDAKUON NGI;30AD 309A
|
||||
KATAKANA LETTER BIDAKUON NGU;30AF 309A
|
||||
KATAKANA LETTER BIDAKUON NGE;30B1 309A
|
||||
KATAKANA LETTER BIDAKUON NGO;30B3 309A
|
||||
KATAKANA LETTER AINU CE;30BB 309A
|
||||
KATAKANA LETTER AINU TU;30C4 309A
|
||||
KATAKANA LETTER AINU TO;30C8 309A
|
||||
KATAKANA LETTER AINU P;31F7 309A
|
||||
MODIFIER LETTER EXTRA-HIGH EXTRA-LOW CONTOUR TONE BAR;02E5 02E9
|
||||
MODIFIER LETTER EXTRA-LOW EXTRA-HIGH CONTOUR TONE BAR;02E9 02E5
|
||||
@@ -100,4 +100,4 @@ FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
|
||||
1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
|
||||
1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
|
||||
@@ -1509,6 +1509,7 @@
|
||||
0619;ARABIC SMALL DAMMA;Mn;31;NSM;;;;;N;;;;;
|
||||
061A;ARABIC SMALL KASRA;Mn;32;NSM;;;;;N;;;;;
|
||||
061B;ARABIC SEMICOLON;Po;0;AL;;;;;N;;;;;
|
||||
061C;ARABIC LETTER MARK;Cf;0;AL;;;;;N;;;;;
|
||||
061E;ARABIC TRIPLE DOT PUNCTUATION MARK;Po;0;AL;;;;;N;;;;;
|
||||
061F;ARABIC QUESTION MARK;Po;0;AL;;;;;N;;;;;
|
||||
0620;ARABIC LETTER KASHMIRI YEH;Lo;0;AL;;;;;N;;;;;
|
||||
@@ -5296,7 +5297,7 @@
|
||||
180B;MONGOLIAN FREE VARIATION SELECTOR ONE;Mn;0;NSM;;;;;N;;;;;
|
||||
180C;MONGOLIAN FREE VARIATION SELECTOR TWO;Mn;0;NSM;;;;;N;;;;;
|
||||
180D;MONGOLIAN FREE VARIATION SELECTOR THREE;Mn;0;NSM;;;;;N;;;;;
|
||||
180E;MONGOLIAN VOWEL SEPARATOR;Zs;0;WS;;;;;N;;;;;
|
||||
180E;MONGOLIAN VOWEL SEPARATOR;Cf;0;BN;;;;;N;;;;;
|
||||
1810;MONGOLIAN DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
|
||||
1811;MONGOLIAN DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
|
||||
1812;MONGOLIAN DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
|
||||
@@ -5751,7 +5752,7 @@
|
||||
1A18;BUGINESE VOWEL SIGN U;Mn;220;NSM;;;;;N;;;;;
|
||||
1A19;BUGINESE VOWEL SIGN E;Mc;0;L;;;;;N;;;;;
|
||||
1A1A;BUGINESE VOWEL SIGN O;Mc;0;L;;;;;N;;;;;
|
||||
1A1B;BUGINESE VOWEL SIGN AE;Mc;0;L;;;;;N;;;;;
|
||||
1A1B;BUGINESE VOWEL SIGN AE;Mn;0;NSM;;;;;N;;;;;
|
||||
1A1E;BUGINESE PALLAWA;Po;0;L;;;;;N;;;;;
|
||||
1A1F;BUGINESE END OF SECTION;Po;0;L;;;;;N;;;;;
|
||||
1A20;TAI THAM LETTER HIGH KA;Lo;0;L;;;;;N;;;;;
|
||||
@@ -7116,6 +7117,10 @@
|
||||
2062;INVISIBLE TIMES;Cf;0;BN;;;;;N;;;;;
|
||||
2063;INVISIBLE SEPARATOR;Cf;0;BN;;;;;N;;;;;
|
||||
2064;INVISIBLE PLUS;Cf;0;BN;;;;;N;;;;;
|
||||
2066;LEFT-TO-RIGHT ISOLATE;Cf;0;LRI;;;;;N;;;;;
|
||||
2067;RIGHT-TO-LEFT ISOLATE;Cf;0;RLI;;;;;N;;;;;
|
||||
2068;FIRST STRONG ISOLATE;Cf;0;FSI;;;;;N;;;;;
|
||||
2069;POP DIRECTIONAL ISOLATE;Cf;0;PDI;;;;;N;;;;;
|
||||
206A;INHIBIT SYMMETRIC SWAPPING;Cf;0;BN;;;;;N;;;;;
|
||||
206B;ACTIVATE SYMMETRIC SWAPPING;Cf;0;BN;;;;;N;;;;;
|
||||
206C;INHIBIT ARABIC FORM SHAPING;Cf;0;BN;;;;;N;;;;;
|
||||
@@ -7738,10 +7743,10 @@
|
||||
2305;PROJECTIVE;So;0;ON;;;;;N;;;;;
|
||||
2306;PERSPECTIVE;So;0;ON;;;;;N;;;;;
|
||||
2307;WAVY LINE;So;0;ON;;;;;N;;;;;
|
||||
2308;LEFT CEILING;Sm;0;ON;;;;;Y;;;;;
|
||||
2309;RIGHT CEILING;Sm;0;ON;;;;;Y;;;;;
|
||||
230A;LEFT FLOOR;Sm;0;ON;;;;;Y;;;;;
|
||||
230B;RIGHT FLOOR;Sm;0;ON;;;;;Y;;;;;
|
||||
2308;LEFT CEILING;Ps;0;ON;;;;;Y;;;;;
|
||||
2309;RIGHT CEILING;Pe;0;ON;;;;;Y;;;;;
|
||||
230A;LEFT FLOOR;Ps;0;ON;;;;;Y;;;;;
|
||||
230B;RIGHT FLOOR;Pe;0;ON;;;;;Y;;;;;
|
||||
230C;BOTTOM RIGHT CROP;So;0;ON;;;;;N;;;;;
|
||||
230D;BOTTOM LEFT CROP;So;0;ON;;;;;N;;;;;
|
||||
230E;TOP RIGHT CROP;So;0;ON;;;;;N;;;;;
|
||||
@@ -18740,8 +18745,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
|
||||
12453;CUNEIFORM NUMERIC SIGN FOUR BAN2 VARIANT FORM;Nl;0;L;;;;4;N;;;;;
|
||||
12454;CUNEIFORM NUMERIC SIGN FIVE BAN2;Nl;0;L;;;;5;N;;;;;
|
||||
12455;CUNEIFORM NUMERIC SIGN FIVE BAN2 VARIANT FORM;Nl;0;L;;;;5;N;;;;;
|
||||
12456;CUNEIFORM NUMERIC SIGN NIGIDAMIN;Nl;0;L;;;;-1;N;;;;;
|
||||
12457;CUNEIFORM NUMERIC SIGN NIGIDAESH;Nl;0;L;;;;-1;N;;;;;
|
||||
12456;CUNEIFORM NUMERIC SIGN NIGIDAMIN;Nl;0;L;;;;2;N;;;;;
|
||||
12457;CUNEIFORM NUMERIC SIGN NIGIDAESH;Nl;0;L;;;;3;N;;;;;
|
||||
12458;CUNEIFORM NUMERIC SIGN ONE ESHE3;Nl;0;L;;;;1;N;;;;;
|
||||
12459;CUNEIFORM NUMERIC SIGN TWO ESHE3;Nl;0;L;;;;2;N;;;;;
|
||||
1245A;CUNEIFORM NUMERIC SIGN ONE THIRD DISH;Nl;0;L;;;;1/3;N;;;;;
|
||||
|
||||
+191
-60
@@ -1,10 +1,9 @@
|
||||
# This file has its own compilation step because
|
||||
# it needs to parse String.Unicode data and
|
||||
# compile a digested module.
|
||||
# This file has its own compilation step because it needs to parse
|
||||
# String.Unicode and String.Graphemes data and compile digested modules.
|
||||
|
||||
defmodule String.Unicode do
|
||||
@moduledoc false
|
||||
|
||||
def version, do: {6,2,0}
|
||||
def version, do: {6,3,0}
|
||||
|
||||
to_binary = fn
|
||||
"" ->
|
||||
@@ -45,23 +44,13 @@ defmodule String.Unicode do
|
||||
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
|
||||
end
|
||||
|
||||
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
|
||||
|
||||
seqs = Enum.map File.stream!(seqs_path), fn(line) ->
|
||||
[ _name, codepoints ] = :binary.split(line, ";", [:global])
|
||||
codepoints = :binary.split(codepoints, " ", [:global])
|
||||
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
|
||||
codepoints = Enum.filter codepoints, fn(x) -> size(x) > 0 end
|
||||
Enum.map codepoints, to_binary.(&1)
|
||||
end
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(string), do: do_downcase(string) |> list_to_binary
|
||||
def downcase(string), do: do_downcase(string) |> iolist_to_binary
|
||||
|
||||
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
|
||||
defp do_downcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(lower)) ++ downcase(rest)
|
||||
unquote(:binary.bin_to_list(lower)) ++ downcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,11 +62,11 @@ defmodule String.Unicode do
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(string), do: do_upcase(string) |> list_to_binary
|
||||
def upcase(string), do: do_upcase(string) |> iolist_to_binary
|
||||
|
||||
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
|
||||
defp do_upcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(upper)) ++ do_upcase(rest)
|
||||
unquote(:binary.bin_to_list(upper)) ++ do_upcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,7 +107,7 @@ defmodule String.Unicode do
|
||||
end
|
||||
|
||||
lc codepoint inlist whitespace do
|
||||
c = binary_to_list(codepoint) |> :lists.reverse
|
||||
c = :binary.bin_to_list(codepoint) |> :lists.reverse
|
||||
|
||||
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
|
||||
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
|
||||
@@ -133,7 +122,7 @@ defmodule String.Unicode do
|
||||
do_rstrip(rest, nil, [char|acc1])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
|
||||
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> iolist_to_binary
|
||||
|
||||
# Split
|
||||
|
||||
@@ -145,11 +134,7 @@ defmodule String.Unicode do
|
||||
|
||||
lc codepoint inlist whitespace do
|
||||
defp do_split(unquote(codepoint) <> rest, buffer, acc) do
|
||||
if buffer != "" do
|
||||
do_split(rest, "", [buffer | acc])
|
||||
else
|
||||
do_split(rest, buffer, acc)
|
||||
end
|
||||
do_split(rest, "", add_buffer_to_acc(buffer, acc))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -158,43 +143,13 @@ defmodule String.Unicode do
|
||||
end
|
||||
|
||||
defp do_split(<<>>, buffer, acc) do
|
||||
if buffer != "" do
|
||||
[buffer | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
add_buffer_to_acc(buffer, acc)
|
||||
end
|
||||
|
||||
# Graphemes
|
||||
@compile { :inline, add_buffer_to_acc: 2 }
|
||||
|
||||
lc codepoints inlist seqs do
|
||||
def next_grapheme(<< unquote_splicing(codepoints), t :: binary >>) do
|
||||
{ << unquote_splicing(codepoints) >>, t }
|
||||
end
|
||||
end
|
||||
|
||||
def next_grapheme(<<>>) do
|
||||
:no_grapheme
|
||||
end
|
||||
|
||||
def next_grapheme(binary) when is_binary(binary) do
|
||||
case next_codepoint(binary) do
|
||||
:no_codepoint -> :no_grapheme
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def graphemes(binary) when is_binary(binary) do
|
||||
do_graphemes(next_grapheme(binary))
|
||||
end
|
||||
|
||||
defp do_graphemes({ c, rest }) do
|
||||
[c|do_graphemes(next_grapheme(rest))]
|
||||
end
|
||||
|
||||
defp do_graphemes(:no_grapheme) do
|
||||
[]
|
||||
end
|
||||
defp add_buffer_to_acc("", acc), do: acc
|
||||
defp add_buffer_to_acc(buffer, acc), do: [buffer|acc]
|
||||
|
||||
# Codepoints
|
||||
|
||||
@@ -222,3 +177,179 @@ defmodule String.Unicode do
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule String.Graphemes do
|
||||
@moduledoc false
|
||||
|
||||
cluster_path = Path.join(__DIR__, "GraphemeBreakProperty.txt")
|
||||
regex = %r/(?:^([0-9A-F]+)(?:\.\.([0-9A-F]+))?)\s+;\s(\w+)/m
|
||||
|
||||
to_range = fn
|
||||
first, "" ->
|
||||
[<< binary_to_integer(first, 16) :: utf8 >>]
|
||||
first, last ->
|
||||
range = binary_to_integer(first, 16)..binary_to_integer(last, 16)
|
||||
Enum.map(range, fn(int) -> << int :: utf8 >> end)
|
||||
end
|
||||
|
||||
cluster = Enum.reduce File.stream!(cluster_path), HashDict.new, fn(line, dict) ->
|
||||
[ _full, first, last, class ] = Regex.run(regex, line)
|
||||
|
||||
# Skip surrogates
|
||||
if first == "D800" and last == "DFFF" do
|
||||
dict
|
||||
else
|
||||
list = to_range.(first, last)
|
||||
Dict.update(dict, class, list, &(&1 ++ list))
|
||||
end
|
||||
end
|
||||
|
||||
# There is no codepoint marked as Prepend by Unicode 6.3.0
|
||||
if cluster["Prepend"] do
|
||||
raise "It seems this new unicode version has added Prepend items. " <>
|
||||
"Please remove this error and uncomment the code below."
|
||||
end
|
||||
|
||||
# Don't break CRLF
|
||||
def next_grapheme(<< ?\n, ?\r, rest :: binary >>) do
|
||||
{ "\n\r", rest }
|
||||
end
|
||||
|
||||
# Break on control
|
||||
lc codepoint inlist cluster["CR"] ++ cluster["LF"] ++ cluster["Control"] do
|
||||
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
|
||||
{ << unquote(codepoint) >>, rest }
|
||||
end
|
||||
end
|
||||
|
||||
# Break on Prepend*
|
||||
# lc codepoint inlist cluster["Prepend"] do
|
||||
# def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
|
||||
# next_prepend(<< unquote(codepoint) >>, rest)
|
||||
# end
|
||||
# end
|
||||
|
||||
# Handle Hangul L
|
||||
lc codepoint inlist cluster["L"] do
|
||||
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_l(<< unquote(codepoint) >>, rest)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Hangul T
|
||||
lc codepoint inlist cluster["T"] do
|
||||
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_t(<< unquote(codepoint) >>, rest)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle Regional
|
||||
lc codepoint inlist cluster["Regional_Indicator"] do
|
||||
def next_grapheme(<< unquote(codepoint), rest :: binary >>) do
|
||||
next_regional(<< unquote(codepoint) >>, rest)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle extended entries
|
||||
def next_grapheme(<< cp :: utf8, rest :: binary >>) do
|
||||
next_extend(<< cp :: utf8 >>, rest)
|
||||
end
|
||||
|
||||
def next_grapheme(<< cp, rest :: binary >>) do
|
||||
{ <<cp>>, rest }
|
||||
end
|
||||
|
||||
def next_grapheme(<<>>) do
|
||||
:no_grapheme
|
||||
end
|
||||
|
||||
# Handle Hangul L
|
||||
lc codepoint inlist cluster["L"] do
|
||||
defp next_hangul_l(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_l(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
lc codepoint inlist cluster["LV"] do
|
||||
defp next_hangul_l(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_v(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
lc codepoint inlist cluster["LVT"] do
|
||||
defp next_hangul_l(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_t(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_hangul_l(head, tail) do
|
||||
next_hangul_v(head, tail)
|
||||
end
|
||||
|
||||
# Handle Hangul V
|
||||
lc codepoint inlist cluster["V"] do
|
||||
defp next_hangul_v(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_v(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_hangul_v(head, tail) do
|
||||
next_hangul_t(head, tail)
|
||||
end
|
||||
|
||||
# Handle Hangul T
|
||||
lc codepoint inlist cluster["T"] do
|
||||
defp next_hangul_t(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_hangul_t(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_hangul_t(head, tail) do
|
||||
next_extend(head, tail)
|
||||
end
|
||||
|
||||
# Handle regional
|
||||
lc codepoint inlist cluster["Regional_Indicator"] do
|
||||
defp next_regional(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_regional(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_regional(head, tail) do
|
||||
next_extend(head, tail)
|
||||
end
|
||||
|
||||
# Handle Extend+SpacingMark
|
||||
lc codepoint inlist cluster["Extend"] ++ cluster["SpacingMark"] do
|
||||
defp next_extend(head, << unquote(codepoint), rest :: binary >>) do
|
||||
next_extend(head <> unquote(codepoint), rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp next_extend(head, tail) do
|
||||
{ head, tail }
|
||||
end
|
||||
|
||||
# Handle Prepend
|
||||
# lc codepoint inlist cluster["Prepend"] do
|
||||
# defp next_prepend(<< unquote(codepoint), rest :: binary >>) do
|
||||
# next_prepend(head <> unquote(codepoint), rest)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# defp next_prepend(head, tail) do
|
||||
# { head, tail }
|
||||
# end
|
||||
|
||||
def graphemes(binary) when is_binary(binary) do
|
||||
do_graphemes(next_grapheme(binary))
|
||||
end
|
||||
|
||||
defp do_graphemes({ c, rest }) do
|
||||
[c|do_graphemes(next_grapheme(rest))]
|
||||
end
|
||||
|
||||
defp do_graphemes(:no_grapheme) do
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
+12
-28
@@ -2,11 +2,9 @@
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4, file_type/1,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
eval/2, eval/3, eval/4, eval_forms/3,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -44,14 +42,6 @@ start_cli() ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% Helpers
|
||||
|
||||
file_type(File) ->
|
||||
case file:read_file_info(File) of
|
||||
{ ok, #file_info{type=Type} } -> { ok, Type };
|
||||
{ error, _ } = Error -> Error
|
||||
end.
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
@@ -66,7 +56,7 @@ scope_for_eval(Opts) ->
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> to_binary(RawFile);
|
||||
{ file, RawFile } when is_binary(RawFile) -> RawFile;
|
||||
false -> Scope#elixir_scope.file
|
||||
end,
|
||||
|
||||
@@ -133,22 +123,16 @@ eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
|
||||
%% Handle forms evaluation internally, it is an
|
||||
%% internal API not meant for external usage.
|
||||
|
||||
translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
translate_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
|
||||
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
|
||||
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
|
||||
case ParseTree of
|
||||
[] -> { nil, Binding, NewScope };
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope, nil),
|
||||
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
|
||||
case Exprs of
|
||||
[] ->
|
||||
{ nil, Binding, NewScope };
|
||||
_ ->
|
||||
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
|
||||
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
|
||||
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewScope }
|
||||
end.
|
||||
|
||||
%% INTERNAL HELPERS
|
||||
|
||||
to_binary(Bin) when is_binary(Bin) -> Bin;
|
||||
to_binary(List) when is_list(List) -> list_to_binary(List).
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/4, store/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, S) -> S;
|
||||
store(Meta, New, Old, S) ->
|
||||
store(_Meta, New, New, _TKV, S) -> S;
|
||||
store(Meta, New, Old, TKV, S) ->
|
||||
record_warn(Meta, New, TKV, S),
|
||||
|
||||
SA = S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
},
|
||||
@@ -19,47 +21,58 @@ store(Meta, New, Old, S) ->
|
||||
SA
|
||||
end.
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Warn =
|
||||
case lists:keyfind(warn, 1, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
expand(Alias, MacroAliases);
|
||||
expand(Alias, MacroAliases, LexicalTracker);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
case expand(Alias, MacroAliases) of
|
||||
case expand(Alias, MacroAliases, LexicalTracker) of
|
||||
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
|
||||
OtherAliases when is_list(OtherAliases) -> Atom
|
||||
end;
|
||||
false ->
|
||||
expand(Alias, Aliases)
|
||||
expand(Alias, Aliases, LexicalTracker)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases) of
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H];
|
||||
Atom -> Atom
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
case H of
|
||||
'Elixir' ->
|
||||
concat(T);
|
||||
_ ->
|
||||
case expand_one(H, Aliases) of
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H|T];
|
||||
Atom -> concat([Atom|T])
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases) ->
|
||||
expand_one(H, Aliases, LexicalTracker) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
Else ->
|
||||
elixir_lexical:record_alias(Lookup, LexicalTracker),
|
||||
Else
|
||||
end.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
@@ -83,7 +83,7 @@ extract_args(_) -> error.
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Left, Right } = elixir_utils:split_last(Args),
|
||||
{ Left, extract_or_clauses(Right, []) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
@@ -199,6 +199,9 @@ has_match_tuple({'case', _, _, _}) ->
|
||||
has_match_tuple({match, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
||||
false;
|
||||
|
||||
has_match_tuple(H) when is_tuple(H) ->
|
||||
has_match_tuple(tuple_to_list(H));
|
||||
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
-record(elixir_code_server, {
|
||||
argv=[],
|
||||
compilation_status=ok,
|
||||
argv=[],
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
pool={[],0},
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
waiting=[]
|
||||
erl_compiler_options=nil
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
@@ -28,7 +27,9 @@ get_timeout() ->
|
||||
start_link() ->
|
||||
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
init(_args) ->
|
||||
init(_Args) ->
|
||||
code:ensure_loaded('Elixir.Module.DispatchTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
@@ -64,11 +65,19 @@ handle_call(compilation_status, _From, Config) ->
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
[H|T] ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool=T} };
|
||||
[] ->
|
||||
Counter = Config#elixir_code_server.counter,
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{counter=Counter+1} }
|
||||
{ [H|T], Counter } ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
|
||||
{ [], Counter } ->
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{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(_Request, _From, Config) ->
|
||||
@@ -112,8 +121,8 @@ 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, I }, #elixir_code_server{pool=Pool} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool=[I|Pool]} };
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
@@ -127,4 +136,24 @@ terminate(_Reason, _Config) ->
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
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
|
||||
end.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-export([core/0, module/4, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
@@ -18,7 +18,7 @@ get_opt(Key, Dict) ->
|
||||
get_opts() ->
|
||||
elixir_code_server:call(compiler_options).
|
||||
|
||||
%% Compiles the given string.
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
@@ -29,34 +29,34 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Scope = elixir:scope_for_eval([{file,File}]),
|
||||
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
put(elixir_compiled, Previous)
|
||||
end.
|
||||
|
||||
%% Compile a file, return a tuple of module names and binaries.
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
File = filename:absname(Relative),
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
string(unicode:characters_to_list(Bin), File).
|
||||
|
||||
%% Compiles a file to the given path (directory).
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Lists = file(File),
|
||||
[binary_to_path(X, Path) || X <- Lists],
|
||||
Lists.
|
||||
|
||||
%% Evaluates the contents/forms by compiling them to an Erlang module.
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Line, Vars, S) ->
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
|
||||
Fun = module_form_fun(S#elixir_scope.module),
|
||||
Form = module_form(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Fun = eval_fun(S#elixir_scope.module),
|
||||
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Args = [X || { _, _, _, X } <- Vars],
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
@@ -79,22 +79,53 @@ eval_forms(Forms, Line, Vars, S) ->
|
||||
Res
|
||||
end), FS }.
|
||||
|
||||
%% Internal API
|
||||
eval_fun(nil) -> '__FILE__';
|
||||
eval_fun(_) -> '__MODULE__'.
|
||||
|
||||
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
|
||||
Args = [{ var, Line, '_@MODULE'}, Cons],
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, length(Args), [
|
||||
{ clause, Line, Args, [], Exprs }
|
||||
] },
|
||||
{ function, Line, '__RELATIVE__', 0, [
|
||||
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
|
||||
] }
|
||||
].
|
||||
|
||||
retrieve_module_name() ->
|
||||
elixir_code_server:call(retrieve_module_name).
|
||||
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
|
||||
%% INTERNAL API
|
||||
|
||||
%% 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, Callback) ->
|
||||
Options = case get_opt(debug_info) of
|
||||
true -> [debug_info];
|
||||
_ -> []
|
||||
end,
|
||||
module(Forms, File, Options, false, Callback).
|
||||
module(Forms, File, Opts, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info];
|
||||
false -> []
|
||||
end,
|
||||
module(Forms, File, Final, false, Callback).
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
|
||||
Listname = binary_to_list(File),
|
||||
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
@@ -102,52 +133,22 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
format_warnings(Bootstrap, File, Warnings),
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, File, Warnings),
|
||||
format_errors(File, Errors)
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
%% Compile core files for bootstrap.
|
||||
%% Invoked from the Makefile.
|
||||
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
%% HELPERS
|
||||
|
||||
compile_opts(Forms, Options) ->
|
||||
EnvOptions = env_default_opts(),
|
||||
compiler_options(Forms, Options) ->
|
||||
EnvOptions = elixir_code_server:call(erl_compiler_options),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
env_default_opts() ->
|
||||
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.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
@@ -156,46 +157,17 @@ no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
module_form_fun(nil) -> '__FILE__';
|
||||
module_form_fun(_) -> '__MODULE__'.
|
||||
|
||||
module_form(Fun, Exprs, Line, File, Module, Vars) when
|
||||
is_binary(File), is_integer(Line), is_list(Exprs), is_atom(Module) ->
|
||||
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
%% CORE HANDLING
|
||||
|
||||
Args = [{ var, Line, '_@MODULE'}, Cons],
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { binary_to_list(File), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 2 }] },
|
||||
{ function, Line, Fun, length(Args), [
|
||||
{ clause, Line, Args, [], Exprs }
|
||||
] }
|
||||
].
|
||||
|
||||
%% Generate module names from code server.
|
||||
|
||||
retrieve_module_name() ->
|
||||
elixir_code_server:call(retrieve_module_name).
|
||||
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
|
||||
%% Receives a module Binary and outputs it in the given path.
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
ok = file:write_file(Path, Binary),
|
||||
Path.
|
||||
|
||||
%% CORE FILES COMPILATION
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
core_file(File) ->
|
||||
try
|
||||
Lists = file(list_to_binary(File)),
|
||||
Lists = file(File),
|
||||
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||
io:format("Compiled ~ts~n", [File])
|
||||
catch
|
||||
@@ -206,46 +178,55 @@ core_file(File) ->
|
||||
|
||||
core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/exception.ex",
|
||||
"lib/elixir/lib/code.ex",
|
||||
"lib/elixir/lib/protocol.ex",
|
||||
"lib/elixir/lib/enum.ex",
|
||||
"lib/elixir/lib/inspect/algebra.ex",
|
||||
"lib/elixir/lib/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/io.ex",
|
||||
"lib/elixir/lib/path.ex",
|
||||
"lib/elixir/lib/system.ex",
|
||||
"lib/elixir/lib/kernel/cli.ex",
|
||||
"lib/elixir/lib/kernel/error_handler.ex",
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex",
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex",
|
||||
"lib/elixir/lib/module/dispatch_tracker.ex"
|
||||
<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
<<"lib/elixir/lib/range.ex">>,
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>,
|
||||
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
|
||||
].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
ok = file:write_file(Path, Binary),
|
||||
Path.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
|
||||
format_errors(_File, []) ->
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
format_errors(File, Errors) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
|
||||
end, Errors).
|
||||
|
||||
format_warnings(Bootstrap, File, Warnings) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
format_warnings(Bootstrap, Warnings) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
end, Warnings).
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
wrap_definition/6,
|
||||
wrap_definition/8,
|
||||
store_definition/7,
|
||||
store_definition/9,
|
||||
store_each/8,
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
@@ -72,25 +70,17 @@ delete_definition(Module, Tuple) ->
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
|
||||
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Call, Expr, elixir_scope:serialize(S)
|
||||
Line = ?line(Meta),
|
||||
?wrap_call(Line, ?MODULE, store_definition, [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'},
|
||||
Call,
|
||||
Expr,
|
||||
elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Name, Args, Guards, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
|
||||
Line = ?line(Meta),
|
||||
Invoke =
|
||||
[{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'}] ++ Extra,
|
||||
|
||||
?wrap_call(Line, ?MODULE, store_definition, Invoke).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
@@ -139,10 +129,7 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity };
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
{ Name, Arity }.
|
||||
|
||||
%% @on_definition
|
||||
|
||||
@@ -152,21 +139,21 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
elixir_module:eval_callbacks(Line, Module, on_definition,
|
||||
[Env, Kind, Name, Args, Guards, Expr], S)
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
%% Retrieve @file or fallback to default
|
||||
|
||||
retrieve_file(Line, Module, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> { binary_to_list(S#elixir_scope.file), Line };
|
||||
true -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
|
||||
_ ->
|
||||
case 'Elixir.Module':get_attribute(Module, file) of
|
||||
nil -> { binary_to_list(S#elixir_scope.file), Line };
|
||||
nil -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
|
||||
Else ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
Else
|
||||
{ Else, 0 }
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -239,9 +226,9 @@ unwrap_stored_definitions(File, Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
|
||||
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
|
||||
|
||||
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
@@ -252,15 +239,19 @@ unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro,
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
false -> { Functions, Tail };
|
||||
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
false ->
|
||||
NewAll = All,
|
||||
{ Functions, Tail };
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
end,
|
||||
|
||||
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
|
||||
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
|
||||
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ Exports, Private, ordsets:from_list(Def),
|
||||
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
|
||||
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
@@ -305,12 +296,13 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
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, _, StoredCheck, StoredLocation, StoredDefaults }] ->
|
||||
[{ Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults }] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
|
||||
(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);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
@@ -328,13 +320,14 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
_ ->
|
||||
Relative = elixir_utils:relative_to_cwd(StoredFile),
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ override_function, { Kind, Name, Arity } } })
|
||||
{ ungrouped_clause, { Kind, Name, Arity, StoredLine, Relative } } })
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
@@ -381,8 +374,9 @@ format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
|
||||
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({override_function,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
|
||||
io_lib:format("clauses for the same ~ts should be grouped together, ~ts ~ts/~B was previously defined (~ts:~B)",
|
||||
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
|
||||
elixir_tracker:record_local(Tuple, Module, Function),
|
||||
get_function(Line, Module, Clauses);
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
|
||||
All, Kind == defmacrop, Clauses /= [] ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
@@ -19,7 +19,7 @@ macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, name/2, store/3, format_error/1]).
|
||||
name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
overridable(Module) ->
|
||||
@@ -25,19 +25,6 @@ ensure_defined(Meta, Module, Tuple, S) ->
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Assign pseudo variables to the given vars.
|
||||
|
||||
assign_args(Line, Args, S) when is_integer(Line) ->
|
||||
{ FArgs, _ } = lists:mapfoldl(fun(X, Acc) -> assign_args(Line, X, Acc, S) end, 1, Args),
|
||||
FArgs.
|
||||
|
||||
assign_args(Line, X, Acc, _) ->
|
||||
Match = { match, Line, X, { var, Line, super_arg(Acc) } },
|
||||
{ Match, Acc + 1 }.
|
||||
|
||||
super_arg(Counter) ->
|
||||
?atom_concat(['_@S', Counter]).
|
||||
|
||||
%% Gets the name based on the function and stored overridables
|
||||
|
||||
name(Module, Function) ->
|
||||
@@ -85,5 +72,5 @@ format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
B = list_to_binary(integer_to_list(Arity)),
|
||||
B = integer_to_binary(Arity),
|
||||
<< A/binary, $/, B/binary >>.
|
||||
@@ -17,16 +17,18 @@ default_functions() ->
|
||||
default_macros() ->
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
@@ -39,15 +41,20 @@ import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
nomatch ->
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
false ->
|
||||
case elixir_import:special_form(Name, Arity) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
@@ -56,6 +63,7 @@ require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
@@ -69,6 +77,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
@@ -76,6 +85,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
@@ -83,10 +93,11 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -97,6 +108,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
@@ -105,10 +117,11 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -119,27 +132,45 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
Function = S#elixir_scope.function,
|
||||
|
||||
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
_ ->
|
||||
case Result of
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
{ _, Receiver } ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
|
||||
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?builtin } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
Fun = (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
|
||||
end
|
||||
{ error, noexpansion }
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
@@ -148,6 +179,7 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
@@ -156,19 +188,14 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, S),
|
||||
Function = S#elixir_scope.function,
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
@@ -190,7 +217,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
@@ -199,17 +226,18 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
translate_expansion(Meta, Receiver, Tree, S) ->
|
||||
Line = ?line(Meta),
|
||||
New = S#elixir_scope.macro_counter + 1,
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, Tree),
|
||||
S
|
||||
elixir_quote:linify(Line, { Receiver, New }, Tree),
|
||||
S#elixir_scope{macro_counter=New}
|
||||
)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, [mfa(Line, S)|erlang:get_stacktrace()])
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
@@ -246,7 +274,7 @@ find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
{ [Receiver], [] } -> { function, Receiver };
|
||||
{ [], [] } -> nomatch;
|
||||
{ [], [] } -> false;
|
||||
_ ->
|
||||
{ Name, Arity } = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
@@ -281,17 +309,19 @@ not_an_import(Tuple, Context, Dict) ->
|
||||
true
|
||||
end.
|
||||
|
||||
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, [{ elixir_dispatch, expand_macro_fun, _, _ }|_], _) ->
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, [H|T], S) ->
|
||||
[H|munge_stacktrace(Info, T, S)];
|
||||
prune_stacktrace(Info, [H|T], S) ->
|
||||
[H|prune_stacktrace(Info, T, S)];
|
||||
|
||||
munge_stacktrace(_, [], _) ->
|
||||
[].
|
||||
prune_stacktrace(Info, [], _) ->
|
||||
[Info].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
@@ -312,10 +342,14 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
||||
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], ", "),
|
||||
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], <<", ">>),
|
||||
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]);
|
||||
|
||||
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B",
|
||||
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
||||
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
|
||||
@@ -363,17 +397,12 @@ in_elixir_macros() ->
|
||||
in_erlang_functions() ->
|
||||
[
|
||||
{ abs, 1 },
|
||||
{ atom_to_binary, 2 },
|
||||
{ atom_to_list, 1 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_list, 1 },
|
||||
{ binary_to_list, 3 },
|
||||
{ binary_to_term, 1 },
|
||||
{ binary_to_term, 2 },
|
||||
{ bit_size, 1 },
|
||||
@@ -408,13 +437,11 @@ in_erlang_functions() ->
|
||||
{ is_tuple, 1 },
|
||||
{ length, 1 },
|
||||
{ list_to_atom, 1 },
|
||||
{ list_to_binary, 1 },
|
||||
{ list_to_bitstring, 1 },
|
||||
{ list_to_existing_atom, 1 },
|
||||
{ list_to_float, 1 },
|
||||
{ list_to_integer, 1 },
|
||||
{ list_to_integer, 2 },
|
||||
{ list_to_pid, 1 },
|
||||
{ list_to_tuple, 1 },
|
||||
{ make_ref, 0 },
|
||||
{ max, 2 },
|
||||
@@ -422,7 +449,6 @@ in_erlang_functions() ->
|
||||
{ node, 0 },
|
||||
{ node, 1 },
|
||||
% { now, 0 },
|
||||
{ pid_to_list, 1 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ size, 1 },
|
||||
@@ -480,8 +506,6 @@ in_erlang_macros() ->
|
||||
{defp,1},
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{function,1},
|
||||
{function,3},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
{'or',2},
|
||||
|
||||
@@ -8,22 +8,22 @@
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3, warn/1,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
warn(Warning) ->
|
||||
elixir_code_server:cast(register_warning),
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
%% Handle inspecting for exceptions modules
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
'Elixir.Inspect.Atom':inspect(Atom).
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string(), list()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string(), list()) -> no_return().
|
||||
|
||||
syntax_error(Meta, File, Message) when is_list(Message) ->
|
||||
syntax_error(Meta, File, iolist_to_binary(Message));
|
||||
@@ -33,7 +33,7 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
|
||||
|
||||
syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
|
||||
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
compile_error(Meta, File, iolist_to_binary(Message));
|
||||
@@ -43,11 +43,11 @@ compile_error(Meta, File, Message) when is_binary(Message) ->
|
||||
|
||||
compile_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.CompileError', unicode:characters_to_binary(Message)).
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
|
||||
parse_error(Meta, File, Error, []) ->
|
||||
Message = case Error of
|
||||
@@ -67,7 +67,7 @@ parse_error(Meta, File, Error, Token) ->
|
||||
|
||||
BinToken = if
|
||||
Token == [] -> <<>>;
|
||||
true -> unicode:characters_to_binary(Token)
|
||||
true -> elixir_utils:characters_to_binary(Token)
|
||||
end,
|
||||
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
@@ -75,7 +75,7 @@ parse_error(Meta, File, Error, Token) ->
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
-spec form_error(non_neg_integer(), file:filename(), module(), any()) -> no_return().
|
||||
-spec form_error(non_neg_integer() | list(), file:filename_all(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
@@ -103,11 +103,30 @@ handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
|
||||
%% We handle unused local warnings ourselves
|
||||
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
|
||||
|
||||
%% Rewrite
|
||||
%% Make no_effect clauses pretty
|
||||
handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A } } }) ->
|
||||
{ Fmt, Args } = case erl_internal:comp_op(F, A) of
|
||||
true -> { "use of operator ~ts has no effect", [translate_comp_op(F)] };
|
||||
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] }
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
%% Rewrite undefined behaviour to check for protocols
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
{ DefKind, Def, DefArity } =
|
||||
case atom_to_list(Fun) of
|
||||
"MACRO-" ++ Rest -> { macro, list_to_atom(Rest), Arity - 1 };
|
||||
_ -> { function, Fun, Arity }
|
||||
end,
|
||||
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
|
||||
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
@@ -118,17 +137,19 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
warn(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
%% Ignore unused vars at "weird" lines (<= 0)
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
[];
|
||||
|
||||
%% Ignore shadowed vars as we guarantee no conflicts ourselves
|
||||
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
[];
|
||||
|
||||
%% 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(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
|
||||
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
%% Default behavior
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
@@ -137,7 +158,20 @@ handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
|
||||
-spec handle_file_error(file:filename_all(), {non_neg_integer(), module(), any()}) -> no_return().
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
Translated = case In of
|
||||
'orelse' -> 'or';
|
||||
'andalso' -> 'and';
|
||||
_ -> 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));
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [inspect(M), F, A]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
@@ -148,18 +182,6 @@ assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Meta, Kind, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, Kind, #elixir_scope{context=match} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside match clause", [Kind]);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside guard", [Kind]);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
@@ -168,6 +190,18 @@ assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_scope{context=match} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in match clause", []);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_scope{context=guard} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in guard", []);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
@@ -177,15 +211,21 @@ raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
%% Populate the stacktrace so we can raise it
|
||||
try
|
||||
throw(ok)
|
||||
catch
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
erlang:raise(error, Exception, Stacktrace).
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) ->
|
||||
io_lib:format("~ts: ~ts~n", [File, Message]);
|
||||
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
|
||||
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
|
||||
|
||||
format_var(Var) ->
|
||||
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
|
||||
@@ -211,4 +251,10 @@ is_protocol(Module) ->
|
||||
end;
|
||||
{ error, _ } ->
|
||||
false
|
||||
end.
|
||||
end.
|
||||
|
||||
translate_comp_op('/=') -> '!=';
|
||||
translate_comp_op('=<') -> '<=';
|
||||
translate_comp_op('=:=') -> '===';
|
||||
translate_comp_op('=/=') -> '!==';
|
||||
translate_comp_op(Other) -> Other.
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
-module(elixir_fn).
|
||||
-export([fn/3, capture/3]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
fn(Meta, Clauses, S) ->
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
ImportMeta =
|
||||
case lists:keyfind(import_fa, 1, Meta) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
lists:keystore(context, 1,
|
||||
lists:keystore(import, 1, Meta, { import, Receiver }),
|
||||
{ context, Context }
|
||||
);
|
||||
false -> Meta
|
||||
end,
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_import(Meta, { F, ImportMeta, Args }, S, true);
|
||||
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, S, false);
|
||||
|
||||
capture(Meta, { '__block__', _, [Expr] }, S) ->
|
||||
capture(Meta, Expr, S);
|
||||
|
||||
capture(Meta, { '__block__', _, _ } = Expr, S) ->
|
||||
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { Left, Right }, S) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, S);
|
||||
|
||||
capture(Meta, List, S) when is_list(List) ->
|
||||
do_capture(Meta, List, S, is_sequential_and_not_empty(List));
|
||||
|
||||
capture(Meta, Arg, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
|
||||
capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, S).
|
||||
|
||||
%% Helpers
|
||||
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
|
||||
case Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
|
||||
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
|
||||
case Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
do_capture(Meta, Expr, S, Sequential) ->
|
||||
case do_escape(Expr, S, []) of
|
||||
{ _, _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, S);
|
||||
{ TExpr, TS, TDict } ->
|
||||
TVars = validate(Meta, TDict, 1, S),
|
||||
fn(Meta, [{ TVars, Meta, TExpr }], TS)
|
||||
end.
|
||||
|
||||
invalid_capture(Meta, Arg, S) ->
|
||||
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
|
||||
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
|
||||
[Var|validate(Meta, T, Pos + 1, S)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _Pos, _S) ->
|
||||
[].
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
case orddict:find(Pos, Dict) of
|
||||
{ ok, Var } ->
|
||||
{ Var, S, Dict };
|
||||
error ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Var, SC, orddict:store(Pos, Var, Dict) }
|
||||
end;
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
|
||||
|
||||
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
|
||||
Message = "nested captures via & are not allowed: ~ts",
|
||||
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
do_escape({ Left, Meta, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, Meta, TRight }, S2, Dict2 };
|
||||
|
||||
do_escape({ Left, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, TRight }, S2, Dict2 };
|
||||
|
||||
do_escape(List, S, Dict) when is_list(List) ->
|
||||
do_escape_list(List, S, Dict, []);
|
||||
|
||||
do_escape(Other, S, Dict) ->
|
||||
{ Other, S, Dict }.
|
||||
|
||||
do_escape_list([H|T], S, Dict, Acc) ->
|
||||
{ TH, TS, TDict } = do_escape(H, S, Dict),
|
||||
do_escape_list(T, TS, TDict, [TH|Acc]);
|
||||
|
||||
do_escape_list([], S, Dict, Acc) ->
|
||||
{ lists:reverse(Acc), S, Dict }.
|
||||
|
||||
is_sequential_and_not_empty([]) -> false;
|
||||
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
|
||||
|
||||
is_sequential([{ '&', _, [Int] }|T], Int) ->
|
||||
is_sequential(T, Int + 1);
|
||||
is_sequential([], _Int) -> true;
|
||||
is_sequential(_, _Int) -> false.
|
||||
@@ -2,76 +2,70 @@
|
||||
%% in between local functions and imports.
|
||||
%% For imports dispatch, please check elixir_dispatch.
|
||||
-module(elixir_import).
|
||||
-export([import/5, format_error/1,
|
||||
ensure_no_local_conflict/4]).
|
||||
-export([import/4, special_form/2, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% IMPORT HELPERS
|
||||
%% IMPORT
|
||||
|
||||
%% Update the scope to consider the imports for aliases
|
||||
%% based on the given options and selector.
|
||||
import(Meta, Ref, Opts, S) ->
|
||||
SI =
|
||||
case keyfind(only, Opts) of
|
||||
{ only, functions } ->
|
||||
import_functions(Meta, Ref, Opts, S);
|
||||
{ only, macros } ->
|
||||
import_macros(true, Meta, Ref, Opts, S);
|
||||
{ only, List } when is_list(List) ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
|
||||
false ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
|
||||
end,
|
||||
record_warn(Meta, Ref, Opts, SI),
|
||||
SI.
|
||||
|
||||
import(Meta, Ref, Opts, Selector, S) ->
|
||||
IncludeAll = (Selector == all) or (Selector == default),
|
||||
import_functions(Meta, Ref, Opts, S) ->
|
||||
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
|
||||
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
|
||||
S#elixir_scope{functions=Functions, macro_functions=Temp}.
|
||||
|
||||
SF = case IncludeAll or (Selector == functions) of
|
||||
false -> S;
|
||||
true ->
|
||||
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
|
||||
{ Functions, TempF } = calculate(Meta, Ref, Opts,
|
||||
S#elixir_scope.functions, S#elixir_scope.macro_functions, FunctionsFun, S),
|
||||
S#elixir_scope{functions=Functions, macro_functions=TempF}
|
||||
import_macros(Force, Meta, Ref, Opts, S) ->
|
||||
Existing = fun() ->
|
||||
case Force of
|
||||
true -> get_macros(Meta, Ref, S);
|
||||
false -> get_optional_macros(Ref)
|
||||
end
|
||||
end,
|
||||
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
|
||||
S#elixir_scope.macro_macros, Existing, S),
|
||||
S#elixir_scope{macros=Macros, macro_macros=Temp}.
|
||||
|
||||
SM = case IncludeAll or (Selector == macros) of
|
||||
false -> SF;
|
||||
true ->
|
||||
MacrosFun = fun(K) ->
|
||||
case IncludeAll of
|
||||
true -> remove_underscored(K andalso Selector, get_optional_macros(Ref));
|
||||
false -> get_macros(Meta, Ref, SF)
|
||||
end
|
||||
end,
|
||||
{ Macros, TempM } = calculate(Meta, Ref, Opts,
|
||||
SF#elixir_scope.macros, SF#elixir_scope.macro_macros, MacrosFun, SF),
|
||||
SF#elixir_scope{macros=Macros, macro_macros=TempM}
|
||||
end,
|
||||
|
||||
record_warn(Meta, Ref, Opts, S),
|
||||
SM.
|
||||
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
|
||||
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, Only } ->
|
||||
{ only, Only } when is_list(Only) ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
|
||||
_ ->
|
||||
intersection(Only, AvailableFun(false))
|
||||
intersection(Only, Existing())
|
||||
end;
|
||||
false ->
|
||||
_ ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> AvailableFun(true);
|
||||
{ except, [] } -> AvailableFun(true);
|
||||
{ except, Except } ->
|
||||
false -> remove_underscored(Existing());
|
||||
{ except, [] } -> remove_underscored(Existing());
|
||||
{ except, Except } when is_list(Except) ->
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun(true) -- Except;
|
||||
false -> remove_underscored(Existing()) -- Except;
|
||||
{Key,OldImports} -> OldImports -- Except
|
||||
end
|
||||
end
|
||||
@@ -82,9 +76,11 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
[] ->
|
||||
{ keydelete(Key, Old),
|
||||
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
_ ->
|
||||
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
|
||||
{ [{ Key, Final }|keydelete(Key, Old)],
|
||||
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
|
||||
end.
|
||||
@@ -139,28 +135,16 @@ get_optional_macros(Module) ->
|
||||
|
||||
%% VALIDATION HELPERS
|
||||
|
||||
%% Check if any of the locals defined conflicts with an invoked
|
||||
%% Elixir "implemented in Erlang" macro.
|
||||
|
||||
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
|
||||
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
|
||||
|
||||
%% Ensure the given functions don't clash with any
|
||||
%% of Elixir non overridable macros.
|
||||
|
||||
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
|
||||
Values = lists:filter(fun({X,Y}) ->
|
||||
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
|
||||
end, special_form()),
|
||||
|
||||
case Values /= [] of
|
||||
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T]) ->
|
||||
case special_form(Name, Arity) of
|
||||
true ->
|
||||
Tuple = { Reason, { Key, Name, Arity } },
|
||||
Tuple = { special_form_conflict, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
|
||||
false ->
|
||||
ensure_no_special_form_conflict(Meta, File, Key, T)
|
||||
end;
|
||||
|
||||
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
@@ -168,10 +152,7 @@ format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({local_conflict,{_, Name, Arity}}) ->
|
||||
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
|
||||
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
||||
format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
@@ -196,57 +177,52 @@ intersection([], _All) -> [].
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(default, List) ->
|
||||
remove_underscored(List) ->
|
||||
lists:filter(fun({ Name, _ }) ->
|
||||
case atom_to_list(Name) of
|
||||
"_" ++ _ -> false;
|
||||
_ -> true
|
||||
end
|
||||
end, List);
|
||||
|
||||
remove_underscored(_, List) -> List.
|
||||
end, List).
|
||||
|
||||
remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
|
||||
%% Macros implemented in Erlang that are not importable.
|
||||
%% Special forms
|
||||
|
||||
special_form() ->
|
||||
[
|
||||
{'^',1},
|
||||
{'=',2},
|
||||
{'__op__',2},
|
||||
{'__op__',3},
|
||||
{'__scope__',2},
|
||||
{'__block__','*'},
|
||||
{'->','*'},
|
||||
{'<<>>','*'},
|
||||
{'{}','*'},
|
||||
{'[]','*'},
|
||||
{'alias',1},
|
||||
{'alias',2},
|
||||
{'require',1},
|
||||
{'require',2},
|
||||
{'import',1},
|
||||
{'import',2},
|
||||
{'import',3},
|
||||
{'__ENV__',0},
|
||||
{'__CALLER__',0},
|
||||
{'__MODULE__',0},
|
||||
{'__FILE__',0},
|
||||
{'__DIR__',0},
|
||||
{'__aliases__','*'},
|
||||
{'quote',1},
|
||||
{'quote',2},
|
||||
{'unquote',1},
|
||||
{'unquote_splicing',1},
|
||||
{'fn','*'},
|
||||
{'super','*'},
|
||||
{'super?',0},
|
||||
{'bc','*'},
|
||||
{'lc','*'},
|
||||
{'var!',1},
|
||||
{'var!',2},
|
||||
{'alias!',1}
|
||||
].
|
||||
special_form('&',1) -> true;
|
||||
special_form('^',1) -> true;
|
||||
special_form('=',2) -> true;
|
||||
special_form('__op__',2) -> true;
|
||||
special_form('__op__',3) -> true;
|
||||
special_form('__scope__',2) -> true;
|
||||
special_form('__block__',_) -> true;
|
||||
special_form('->',_) -> true;
|
||||
special_form('<<>>',_) -> true;
|
||||
special_form('{}',_) -> true;
|
||||
special_form('alias',1) -> true;
|
||||
special_form('alias',2) -> true;
|
||||
special_form('require',1) -> true;
|
||||
special_form('require',2) -> true;
|
||||
special_form('import',1) -> true;
|
||||
special_form('import',2) -> true;
|
||||
special_form('__ENV__',0) -> true;
|
||||
special_form('__CALLER__',0) -> true;
|
||||
special_form('__MODULE__',0) -> true;
|
||||
special_form('__FILE__',0) -> true;
|
||||
special_form('__DIR__',0) -> true;
|
||||
special_form('__aliases__',_) -> true;
|
||||
special_form('quote',1) -> true;
|
||||
special_form('quote',2) -> true;
|
||||
special_form('unquote',1) -> true;
|
||||
special_form('unquote_splicing',1) -> true;
|
||||
special_form('fn',_) -> true;
|
||||
special_form('super',_) -> true;
|
||||
special_form('super?',0) -> true;
|
||||
special_form('bc',_) -> true;
|
||||
special_form('lc',_) -> true;
|
||||
special_form('var!',1) -> true;
|
||||
special_form('var!',2) -> true;
|
||||
special_form('alias!',1) -> true;
|
||||
special_form(_, _) -> false.
|
||||
|
||||
@@ -6,62 +6,70 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
extract(Line, File, Interpol, String, Last) ->
|
||||
extract(Line, File, Interpol, String, [], [], [], Last).
|
||||
extract(Line, Raw, Interpol, String, Last) ->
|
||||
%% Ignore whatever is in the scope and enable terminator checking.
|
||||
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
|
||||
extract(Line, Scope, Interpol, String, [], 0, [], Last).
|
||||
|
||||
extract(Line, File, _Interpol, [], Buffer, [], Output, []) ->
|
||||
finish_extraction(Line, File, Buffer, Output, []);
|
||||
%% Terminators
|
||||
|
||||
extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
|
||||
finish_extraction(Line, Buffer, Output, []);
|
||||
|
||||
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining);
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
|
||||
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
|
||||
finish_extraction(Line, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
|
||||
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, File, Interpol, [$\\, $#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${,$#|Buffer], [], Output, Last);
|
||||
%% Going through the string
|
||||
|
||||
extract(Line, File, Interpol, [$\\,Char|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char,$\\|Buffer], [], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, true, [$#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
NewOutput = build_interpol(s, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [$}], NewOutput, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
|
||||
NewOutput = build_interpol(i, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [], NewOutput, Last);
|
||||
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
|
||||
|
||||
%% Check for available separators inside interpolation
|
||||
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
Output1 = build_string(Line, Buffer, Output),
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $>; C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
|
||||
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
|
||||
{ error, { EndLine, _, "}" }, [$}|NewRest], Tokens } ->
|
||||
Output2 = build_interpol(Line, Tokens, Output1),
|
||||
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
|
||||
{ error, Reason, _, _ } ->
|
||||
{ error, Reason };
|
||||
{ ok, _EndLine, _ } ->
|
||||
{ error, { string, Line, "missing interpolation terminator: }", [] } }
|
||||
end;
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
|
||||
%% Matching () [] {} <> inside sigils
|
||||
|
||||
extract(Line, File, Interpol, [$<|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$<|Buffer], [$>|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
|
||||
|
||||
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
|
||||
|
||||
extract(Line, File, Interpol, [$[|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$[|Buffer], [$]|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
|
||||
|
||||
extract(Line, File, Interpol, [$(|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$(|Buffer], [$)|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
|
||||
|
||||
%% Else
|
||||
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
%% Catch all clause
|
||||
|
||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
|
||||
%% Unescape a series of tokens as returned by extract.
|
||||
|
||||
@@ -148,33 +156,16 @@ unescape_map(E) -> E.
|
||||
|
||||
% Extract Helpers
|
||||
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining) ->
|
||||
case build_interpol(s, Line, File, Buffer, Output) of
|
||||
finish_extraction(Line, Buffer, Output, Remaining) ->
|
||||
case build_string(Line, Buffer, Output) of
|
||||
[] -> Final = [<<>>];
|
||||
Final -> []
|
||||
end,
|
||||
{ Line, lists:reverse(Final), Remaining }.
|
||||
|
||||
build_interpol(_Kind, _Line, _File, [], Output) ->
|
||||
Output;
|
||||
build_string(_Line, [], Output) -> Output;
|
||||
build_string(_Line, Buffer, Output) ->
|
||||
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output].
|
||||
|
||||
build_interpol(s, _Line, _File, Buffer, Output) ->
|
||||
[unicode:characters_to_binary(lists:reverse(Buffer))|Output];
|
||||
|
||||
build_interpol(i, Line, File, Buffer, Output) ->
|
||||
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
|
||||
|
||||
wrap_interpol(_Line, Form) when is_binary(Form) ->
|
||||
Form;
|
||||
|
||||
wrap_interpol(Line, Form) ->
|
||||
Meta = [{line,Line}],
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Binary.Chars', to_binary] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case elixir_translator:forms(String, StartLine, File, []) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', [{line,StartLine}], Forms };
|
||||
{ error, Tuple } -> throw({ interpolation_error, Tuple })
|
||||
end.
|
||||
build_interpol(Line, Buffer, Output) ->
|
||||
[{ Line, lists:reverse(Buffer) }|Output].
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
%% Module responsible for tracking lexical information.
|
||||
-module(elixir_lexical).
|
||||
-export([run/2,
|
||||
record_alias/4, record_alias/2,
|
||||
record_import/4, record_import/2,
|
||||
record_remote/2, format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(tracker, 'Elixir.Kernel.LexicalTracker').
|
||||
|
||||
run(File, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{ file, _ } ->
|
||||
Pid = ?tracker:start_link(),
|
||||
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
warn_unused_aliases(File, Pid),
|
||||
warn_unused_imports(File, Pid),
|
||||
unlink(Pid), ?tracker:stop(Pid)
|
||||
end;
|
||||
false ->
|
||||
Callback(nil)
|
||||
end.
|
||||
|
||||
%% RECORD
|
||||
|
||||
record_alias(Module, Line, Warn, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:add_alias(Pid, Module, Line, Warn),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(Module, Line, Warn, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:add_import(Pid, Module, Line, Warn),
|
||||
true
|
||||
end).
|
||||
|
||||
record_alias(Module, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:alias_dispatch(Pid, Module),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(Module, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:import_dispatch(Pid, Module),
|
||||
true
|
||||
end).
|
||||
|
||||
record_remote(Module, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:remote_dispatch(Pid, Module),
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(nil, _Callback) -> false;
|
||||
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
warn_unused_imports(File, Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_imports(Pid)].
|
||||
|
||||
warn_unused_aliases(File, Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_alias, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
|
||||
|
||||
format_error({unused_alias, Module}) ->
|
||||
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
|
||||
@@ -5,71 +5,48 @@
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
Else -> Else
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
|
||||
translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ '[]', _Meta, [] }, S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{'|',_,[Left,Right]} ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
|
||||
end,
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
|
||||
Else -> Else
|
||||
end;
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', [], [Left, Right]}, S);
|
||||
|
||||
translate(Args, S) when is_list(Args) ->
|
||||
translate({ '[]', [], Args }, S);
|
||||
translate([], S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate(Number, S) when is_integer(Number) ->
|
||||
{ { integer, 0, Number }, S };
|
||||
translate([_|_] = Args, S) ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
translate(Number, S) when is_float(Number) ->
|
||||
{ { float, 0, Number }, S };
|
||||
case RTail of
|
||||
{ '|', _, [Left,Right] } ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, 0 }, ST } end
|
||||
end,
|
||||
|
||||
translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S };
|
||||
translate(Tuple, S) when is_tuple(Tuple) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file,
|
||||
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple)]);
|
||||
|
||||
translate(Other, S) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file, "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Other)]).
|
||||
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -102,9 +79,16 @@ build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
case (is_default_or_utf(Types) andalso Expr) of
|
||||
{ bin, _, BinElements } ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_bin_elements(BinElements, Size, Types, []) ++ Acc);
|
||||
|
||||
AllowString = types_allow_string(Types),
|
||||
AllowSplice = types_allow_splice(Types),
|
||||
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
|
||||
|
||||
case AllowAny andalso Expr of
|
||||
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
|
||||
{ bin, _, Elements } when AllowSplice ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
{ cons, _, _, _ } = Cons ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
|
||||
{ nil, _ } ->
|
||||
@@ -117,16 +101,17 @@ rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
|
||||
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
|
||||
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
|
||||
|
||||
rehash_bin_elements([{ bin_element, Line, Expr, _S, _T }|T], Size, Types, Acc) ->
|
||||
rehash_bin_elements(T, Size, Types, [{ bin_element, Line, Expr, Size, Types }|Acc]);
|
||||
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
|
||||
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
|
||||
types_allow_string([]) -> true;
|
||||
types_allow_string(_) -> false.
|
||||
|
||||
rehash_bin_elements([], _Size, _Types, Acc) ->
|
||||
Acc.
|
||||
|
||||
is_default_or_utf(default) -> true;
|
||||
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
|
||||
is_default_or_utf([_|T]) -> is_default_or_utf(T);
|
||||
is_default_or_utf([]) -> false.
|
||||
types_allow_splice(default) -> true;
|
||||
types_allow_splice([bytes]) -> true;
|
||||
types_allow_splice([binary]) -> true;
|
||||
types_allow_splice([bits]) -> true;
|
||||
types_allow_splice([bitstring]) -> true;
|
||||
types_allow_splice(_) -> false.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4,
|
||||
syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3,
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
@@ -35,58 +33,21 @@ translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
|
||||
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ '!', Meta, [Expr] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
elixir_tree_helpers:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
||||
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
|
||||
{ TExpr, TS };
|
||||
|
||||
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "in operator inside matches is deprecated, please move it to a guard"),
|
||||
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
|
||||
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
|
||||
|
||||
%% Functions
|
||||
|
||||
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
elixir_translator:translate_fn(Meta, Pairs, S);
|
||||
|
||||
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
|
||||
translate({ function, Meta, [M, F, A] }, S);
|
||||
|
||||
translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
WrappedMeta =
|
||||
case lists:keyfind(import_fa, 1, MetaFA) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
lists:keystore(context, 1,
|
||||
lists:keystore(import, 1, Meta, { import, Receiver }),
|
||||
{ context, Context }
|
||||
);
|
||||
false -> Meta
|
||||
end,
|
||||
|
||||
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
|
||||
false -> compile_error(WrappedMeta, S#elixir_scope.file,
|
||||
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ function, Meta, [Arg] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function/1: ~ts",
|
||||
['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
{ [A,B,C], SA } = translate_args(Args, S),
|
||||
{ { 'fun', ?line(Meta), { function, A, B, C } }, SA };
|
||||
|
||||
%% @
|
||||
|
||||
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
|
||||
@@ -127,7 +88,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_tree_helpers:elixir_to_erl(Contents), S }
|
||||
{ elixir_utils:elixir_to_erl(?line(Meta), Contents, S), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
||||
@@ -141,7 +102,7 @@ translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
|
||||
RClauses = case elixir_tree_helpers:returns_boolean(TExpr) of
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
@@ -182,7 +143,7 @@ translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
|
||||
{ FClauses, TAfter } = elixir_tree_helpers:split_last(TClauses),
|
||||
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
||||
end;
|
||||
@@ -202,7 +163,7 @@ translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, [{warn,false}], S);
|
||||
false -> S
|
||||
end,
|
||||
|
||||
@@ -229,15 +190,6 @@ translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
||||
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TName, SN } = translate_each(Name, S),
|
||||
{ TArgs, SA } = translate_each(Args, SN),
|
||||
{ TGuards, SG } = translate_each(Guards, SA),
|
||||
{ TExpr, SE } = translate_each(Expr, SG),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
|
||||
|
||||
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
||||
|
||||
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
@@ -274,20 +226,20 @@ translate_in(Meta, Left, Right, S) ->
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
{ InGuard, TExpr } = case TRight of
|
||||
{ TCache, TExpr } = case TRight of
|
||||
{ nil, _ } ->
|
||||
Expr = { atom, Line, false },
|
||||
{ Cache, Expr };
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
[H|T] = elixir_utils:cons_to_list(TRight),
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
||||
end, { op, Line, '==', Var, H }, T),
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, X }, Acc }
|
||||
end, { op, Line, '=:=', Var, H }, T),
|
||||
{ Cache, Expr };
|
||||
{ string, _, [H|T] } ->
|
||||
Expr = lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T),
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '=:=', Var, { integer, Line, H } }, T),
|
||||
{ Cache, Expr };
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
Expr = case { Start, End } of
|
||||
@@ -315,7 +267,7 @@ translate_in(Meta, Left, Right, S) ->
|
||||
end
|
||||
end,
|
||||
|
||||
case InGuard of
|
||||
case TCache of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { Var, TExpr, SV }
|
||||
end.
|
||||
@@ -330,8 +282,18 @@ decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
%% TODO: Once we have elixir_exp, we can move this
|
||||
%% clause to Elixir code and out of case.
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{ 'cond', true } ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
end;
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
@@ -346,7 +308,8 @@ expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
|
||||
%% defmodule Hello.World
|
||||
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.lexical_tracker) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
Aliases when is_list(Aliases) ->
|
||||
@@ -358,6 +321,7 @@ expand_module(_Raw, Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(typedoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(lexical_attr, '__lexical_tracker').
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
-define(overridable_attr, '__overridable').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
|
||||
Binding = orddict:store('_@MODULE', Module, RawBinding),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
@@ -23,7 +25,6 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
S#elixir_scope{module=Module};
|
||||
|
||||
scope_for_eval(Module, Opts) ->
|
||||
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
|
||||
|
||||
@@ -43,7 +44,7 @@ docs_table(Module) ->
|
||||
|
||||
translate(Meta, Ref, Block, S) ->
|
||||
Line = ?line(Meta),
|
||||
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
|
||||
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, S),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
@@ -59,18 +60,19 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
end,
|
||||
|
||||
File = S#elixir_scope.file,
|
||||
FileList = binary_to_list(File),
|
||||
FileList = elixir_utils:characters_to_list(File),
|
||||
|
||||
check_module_availability(Line, File, Module, C),
|
||||
build(Line, File, Module),
|
||||
build(Line, File, Module, S#elixir_scope.lexical_tracker),
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
Forms3 = typedocs_form(Module, Forms2),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
@@ -78,18 +80,18 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
|
||||
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
|
||||
elixir_tracker:ensure_all_imports_used(Line, File, Module),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
warn_invalid_clauses(Line, File, Module, All),
|
||||
warn_unused_docs(Line, File, Module),
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
{ attribute, Line, module, Module } | Forms2
|
||||
{ attribute, Line, module, Module } | Forms3
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, S),
|
||||
Binary = load_form(Line, Final, compile_opts(Module), S),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
elixir_tracker:cleanup(Module),
|
||||
@@ -108,7 +110,7 @@ compile(Line, Module, Block, Vars, RawS) ->
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
build(Line, File, Module) ->
|
||||
build(Line, File, Module, Lexical) ->
|
||||
%% Table with meta information about the module.
|
||||
DataTable = data_table(Module),
|
||||
|
||||
@@ -118,7 +120,6 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
ets:new(DataTable, [set, named_table, public]),
|
||||
ets:insert(DataTable, { '__overridable', [] }),
|
||||
ets:insert(DataTable, { before_compile, [] }),
|
||||
ets:insert(DataTable, { after_compile, [] }),
|
||||
|
||||
@@ -128,9 +129,11 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile, after_compile, on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
ets:insert(DataTable, { ?lexical_attr, Lexical }),
|
||||
ets:insert(DataTable, { ?overridable_attr, [] }),
|
||||
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
@@ -149,18 +152,20 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions, C) ->
|
||||
Functions = case elixir_compiler:get_opt(internal, C) of
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
|
||||
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
|
||||
true -> RawFunctions;
|
||||
false -> record_rewrite_functions(Module, RawFunctions)
|
||||
end,
|
||||
|
||||
{ FinalExport, FinalFunctions } =
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
|
||||
|
||||
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
|
||||
{ FinalExport ++ PrivateTuple, [
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
All = [{ '__info__', 1 }|BaseAll],
|
||||
Export = [{ '__info__', 1 }|BaseExport],
|
||||
Functions = [Info|BaseFunctions],
|
||||
|
||||
{ All, [
|
||||
{ attribute, Line, export, lists:sort(Export) } | Functions
|
||||
] }.
|
||||
|
||||
record_rewrite_functions(Module, Functions) ->
|
||||
@@ -196,9 +201,21 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
|
||||
ets:foldl(Transform, Current, Table).
|
||||
|
||||
%% Add typedocs to the form
|
||||
typedocs_form(Module, Current) ->
|
||||
Table = docs_table(Module),
|
||||
Transform = fun({ Tuple, Line, Kind, _Sig, Doc }, Acc) ->
|
||||
case Kind of
|
||||
type -> [{ attribute, Line, typedoc, { Tuple, Doc } } | Acc];
|
||||
opaque -> [{ attribute, Line, typedoc, { Tuple, Doc } } | Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
end,
|
||||
ets:foldl(Transform, Current, Table).
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
|
||||
specs_form(Module, Private, Defmacro, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
@@ -210,16 +227,17 @@ specs_form(Line, Module, Private, Defmacro, Forms, C) ->
|
||||
'Elixir.Module':delete_attribute(Module, spec),
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
|
||||
Temp = specs_attributes(Line, spec, Forms, Specs),
|
||||
specs_attributes(Line, callback, Temp, Callbacks)
|
||||
Temp = specs_attributes(spec, Forms, Specs),
|
||||
specs_attributes(callback, Temp, Callbacks)
|
||||
end.
|
||||
|
||||
specs_attributes(Line, Type, Forms, Specs) ->
|
||||
specs_attributes(Type, Forms, Specs) ->
|
||||
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
|
||||
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
|
||||
lists:keystore(Tuple, 1, Acc, { Tuple, Value })
|
||||
end, [], Specs),
|
||||
lists:foldl(fun({ Tuple, _ }, Acc) ->
|
||||
Values = [V || { K, V } <- Specs, K == Tuple],
|
||||
{ type, Line, _, _ } = hd(Values),
|
||||
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
|
||||
end, Forms, Keys).
|
||||
|
||||
@@ -244,8 +262,14 @@ spec_for_macro(Else) -> Else.
|
||||
|
||||
%% Loads the form into the code server.
|
||||
|
||||
load_form(Line, Forms, S) ->
|
||||
elixir_compiler:module(Forms, S#elixir_scope.file, fun(Module, Binary) ->
|
||||
compile_opts(Module) ->
|
||||
case ets:lookup(data_table(Module), compile) of
|
||||
[{compile,Opts}] when is_list(Opts) -> Opts;
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
@@ -253,11 +277,16 @@ load_form(Line, Forms, S) ->
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
put(elixir_compiled, [{Module,Binary}|Current]),
|
||||
|
||||
case get(elixir_compiler_pid) of
|
||||
undefined -> [];
|
||||
PID -> PID ! { module_available, self(), Module, Binary }
|
||||
PID ->
|
||||
Ref = make_ref(),
|
||||
PID ! { module_available, self(), Ref, File, Module, Binary },
|
||||
receive { Ref, ack } -> ok end
|
||||
end;
|
||||
_ -> []
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Binary
|
||||
@@ -276,31 +305,56 @@ check_module_availability(Line, File, Module, Compiler) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, File, Module, All) ->
|
||||
ets:foldl(fun
|
||||
({ _, _, Kind, _, _ }, _) when Kind == type; Kind == opaque ->
|
||||
ok;
|
||||
({ Tuple, Line, _, _, _ }, _) ->
|
||||
case lists:member(Tuple, All) of
|
||||
false ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_clause, Tuple } });
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, ok, docs_table(Module)).
|
||||
|
||||
warn_unused_docs(Line, File, Module) ->
|
||||
lists:foreach(fun(Attribute) ->
|
||||
case ets:member(data_table(Module), Attribute) of
|
||||
true ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_doc, Attribute } });
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, [typedoc]).
|
||||
|
||||
% EXTRA FUNCTIONS
|
||||
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
|
||||
Pair = { '__info__', 1 },
|
||||
case lists:member(Pair, Export) of
|
||||
true -> elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
true ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
false ->
|
||||
Docs = elixir_compiler:get_opt(docs, C),
|
||||
Contents = { function, 0, '__info__', 1, [
|
||||
{ function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Module, Def, Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Module),
|
||||
else_clause()
|
||||
] },
|
||||
{ [Pair|Export], [Contents|Functions] }
|
||||
] }
|
||||
end.
|
||||
|
||||
functions_clause(Def) ->
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
|
||||
|
||||
macros_clause(Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
@@ -311,15 +365,18 @@ module_clause(Module) ->
|
||||
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
|
||||
|
||||
docs_clause(Module, true) ->
|
||||
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:elixir_to_erl(Docs)] };
|
||||
Docs = ordsets:from_list(
|
||||
[{Tuple, Line, Kind, Sig, Doc} ||
|
||||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
|
||||
Kind =/= type, Kind =/= opaque]),
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_utils:elixir_to_erl(Docs)] };
|
||||
|
||||
docs_clause(_Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
|
||||
|
||||
moduledoc_clause(Line, Module, true) ->
|
||||
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_utils:elixir_to_erl({ Line, Docs })] };
|
||||
|
||||
moduledoc_clause(_Line, _Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
|
||||
@@ -331,8 +388,7 @@ else_clause() ->
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
Binding = elixir_scope:binding_for_eval([], Module),
|
||||
S = elixir_scope:vars_from_binding(RawS, Binding),
|
||||
{ Binding, S } = elixir_scope:load_binding([], RawS, Module),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
@@ -350,14 +406,36 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
|
||||
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
|
||||
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T]) ->
|
||||
[H|prune_stacktrace(Info, T)];
|
||||
|
||||
prune_stacktrace(Info, []) ->
|
||||
[Info].
|
||||
|
||||
% ERROR HANDLING
|
||||
|
||||
format_error({ invalid_clause, { Name, Arity } }) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({ unused_doc, typedoc }) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
|
||||
format_error({ unused_doc, doc }) ->
|
||||
"@doc provided but no definition follows it";
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
|
||||
@@ -19,16 +19,16 @@ Nonterminals
|
||||
kw_eol kw_expr kw_comma kw
|
||||
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
|
||||
var list bracket_access bit_string tuple
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
var list bit_string tuple
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
.
|
||||
|
||||
Terminals
|
||||
identifier kw_identifier punctuated_identifier
|
||||
bracket_identifier paren_identifier do_identifier block_identifier
|
||||
identifier kw_identifier bracket_identifier
|
||||
paren_identifier do_identifier block_identifier
|
||||
fn 'end' aliases
|
||||
number signed_number atom bin_string list_string sigil
|
||||
number signed_number atom atom_string bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
@@ -42,6 +42,7 @@ Rootsymbol grammar.
|
||||
%% There are two shift/reduce conflicts coming from call_args_parens.
|
||||
Expect 2.
|
||||
|
||||
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
@@ -62,7 +63,7 @@ Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Right 230 than_op_eol. %% < (op) > (e.g <>)
|
||||
Right 240 two_op_eol. %% ++, --, **
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @ (op)
|
||||
@@ -95,7 +96,6 @@ matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> identifier_expr : '$1'.
|
||||
|
||||
no_parens_expr -> dot_punctuated_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
no_parens_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
no_parens_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
|
||||
@@ -109,7 +109,6 @@ unmatched_expr -> block_expr : '$1'.
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
|
||||
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
|
||||
op_expr -> match_op_eol expr : { '$1', '$2' }.
|
||||
@@ -166,10 +165,8 @@ matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
|
||||
identifier_expr -> dot_punctuated_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_punctuated_identifier : build_identifier('$1', []).
|
||||
identifier_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
identifier_expr -> var : build_identifier('$1', nil).
|
||||
identifier_expr -> max_expr : '$1'.
|
||||
@@ -182,20 +179,20 @@ max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens(
|
||||
max_expr -> dot_alias : '$1'.
|
||||
max_expr -> base_expr : '$1'.
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
bracket_expr -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr list : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol max_expr bracket_access :
|
||||
bracket_at_expr -> at_op_eol max_expr list :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access :
|
||||
bracket_at_expr -> bracket_at_expr list :
|
||||
build_access('$1', '$2').
|
||||
|
||||
base_expr -> atom : ?exprs('$1').
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> atom : build_atom('$1').
|
||||
base_expr -> list : '$1'.
|
||||
base_expr -> list : element(1, '$1').
|
||||
base_expr -> tuple : '$1'.
|
||||
base_expr -> 'true' : ?id('$1').
|
||||
base_expr -> 'false' : ?id('$1').
|
||||
@@ -203,9 +200,10 @@ base_expr -> 'nil' : ?id('$1').
|
||||
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> bin_string : build_bin_string('$1').
|
||||
base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> atom_string : build_atom_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
|
||||
|
||||
%% Blocks
|
||||
|
||||
@@ -364,9 +362,6 @@ dot_bracket_identifier -> matched_expr dot_op bracket_identifier : build_dot('$2
|
||||
dot_paren_identifier -> paren_identifier : '$1'.
|
||||
dot_paren_identifier -> matched_expr dot_op paren_identifier : build_dot('$2', '$1', '$3').
|
||||
|
||||
dot_punctuated_identifier -> punctuated_identifier : '$1'.
|
||||
dot_punctuated_identifier -> matched_expr dot_op punctuated_identifier : build_dot('$2', '$1', '$3').
|
||||
|
||||
parens_call -> dot_paren_identifier : '$1'.
|
||||
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
|
||||
|
||||
@@ -440,21 +435,17 @@ kw -> kw_expr : ['$1'].
|
||||
kw -> kw_comma : lists:reverse('$1').
|
||||
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : {?exprs('$1'),'$2'}.
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { ?exprs('$1'),'$2' }.
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
|
||||
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
|
||||
|
||||
% Lists
|
||||
|
||||
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
|
||||
bracket_access -> open_bracket container_arg close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
|
||||
|
||||
list -> open_bracket ']' : [].
|
||||
list -> open_bracket kw close_bracket : '$2'.
|
||||
list -> open_bracket container_arg close_bracket : ['$2'].
|
||||
list -> open_bracket container_expr ',' container_args close_bracket : ['$2'|'$4'].
|
||||
list -> open_bracket ']' : { [], ?line('$1') }.
|
||||
list -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
|
||||
list -> open_bracket container_arg close_bracket : { ['$2'], ?line('$1') }.
|
||||
list -> open_bracket container_expr ',' container_args close_bracket : { ['$2'|'$4'], ?line('$1') }.
|
||||
|
||||
% Tuple
|
||||
|
||||
@@ -482,6 +473,12 @@ Erlang code.
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
|
||||
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
|
||||
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
|
||||
|
||||
@@ -499,7 +496,6 @@ build_tuple(Marker, Args) ->
|
||||
|
||||
%% Blocks
|
||||
|
||||
build_block([nil]) -> { '__block__', [], [nil] };
|
||||
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
|
||||
build_block([{unquote_splicing,_,Args}]=Exprs) when
|
||||
length(Args) =< 2 -> { '__block__', [], Exprs };
|
||||
@@ -540,8 +536,8 @@ build_identifier({ _, Line, Identifier }, Args) ->
|
||||
{ Identifier, [{line,Line}], Args }.
|
||||
|
||||
extract_identifier({ Kind, _, Identifier }) when
|
||||
Kind == identifier; Kind == punctuated_identifier; Kind == bracket_identifier;
|
||||
Kind == paren_identifier; Kind == do_identifier; Kind == op_identifier ->
|
||||
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
|
||||
Kind == do_identifier; Kind == op_identifier ->
|
||||
Identifier;
|
||||
|
||||
extract_identifier(Other) -> Other.
|
||||
@@ -549,32 +545,56 @@ extract_identifier(Other) -> Other.
|
||||
%% Fn
|
||||
|
||||
build_fn(Op, Stab) ->
|
||||
{ fn, [{line,?line(Op)}], [[{ do, Stab }]] }.
|
||||
{ fn, [{line,?line(Op)}], [Stab] }.
|
||||
|
||||
%% Access
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?id(Access) ] }.
|
||||
build_access(Expr, { List, Line }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [Expr, List] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, string_parts(Parts) }, Modifiers ] }.
|
||||
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) ->
|
||||
H;
|
||||
build_bin_string({ bin_string, Line, Args }) ->
|
||||
{ '<<>>', [{line,Line}], string_parts(Args) }.
|
||||
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> unicode:characters_to_list(H);
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) ->
|
||||
elixir_utils:characters_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, [unicode, characters_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
|
||||
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, string_parts(Args) }] }.
|
||||
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) ->
|
||||
build_atom_string({ atom_string, _Line, Safe, [H] }) when is_binary(H) ->
|
||||
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
|
||||
build_atom_string({ atom_string, Line, Safe, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, [erlang, binary_to_atom] }, Meta, [{ '<<>>', Meta, Args }, utf8] }.
|
||||
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
|
||||
|
||||
binary_to_atom_op(true) -> binary_to_existing_atom;
|
||||
binary_to_atom_op(false) -> binary_to_atom.
|
||||
|
||||
string_parts(Parts) ->
|
||||
[string_part(Part) || Part <- Parts].
|
||||
string_part(Binary) when is_binary(Binary) ->
|
||||
Binary;
|
||||
string_part({ Line, Tokens }) ->
|
||||
Form = string_tokens_parse(Line, Tokens),
|
||||
Meta = [{line,Line}],
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
string_tokens_parse(Line, Tokens) ->
|
||||
case parse(Tokens) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', [{line, Line}], Forms };
|
||||
{ error, _ } = Error -> throw(Error)
|
||||
end.
|
||||
|
||||
%% Keywords
|
||||
|
||||
@@ -608,7 +628,7 @@ unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
|
||||
unwrap_splice(Other) -> Other.
|
||||
|
||||
unwrap_when(Args) ->
|
||||
case elixir_tree_helpers:split_last(Args) of
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Start, { 'when', Meta, [_, _] = End } } ->
|
||||
[{ 'when', Meta, Start ++ End }];
|
||||
{ _, _ } ->
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
%% Responsible handling Elixir partials with &1, &2 and so on.
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= match ->
|
||||
case convert(Args, S, Opt) of
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
handle(_Original, _S, _Opt) ->
|
||||
error.
|
||||
|
||||
validate(Meta, Def, S) ->
|
||||
validate(Meta, lists:sort(Def), 1, S).
|
||||
|
||||
validate(Meta, [{ Pos, Item }|T], Pos, S) ->
|
||||
[Item|validate(Meta, T, Pos + 1, S)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _Pos, _S) ->
|
||||
[].
|
||||
|
||||
%% This function receives arguments and then checks
|
||||
%% the args for partial application. It returns a tuple
|
||||
%% with three elements where the first element is the list
|
||||
%% of call args, the second the list of def args for the
|
||||
%% function definition and the third one is the new scope.
|
||||
convert(List, S, Opt) -> convert(List, S, Opt, [], []).
|
||||
|
||||
convert([{'|', Meta, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
|
||||
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
|
||||
convert(T, NewS, allow_tail, [{ '|', Meta, NewArgs}|CallAcc], NewDef);
|
||||
|
||||
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) ->
|
||||
case lists:keyfind(Pos, 1, DefAcc) of
|
||||
false ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
convert(T, SC, Opt, [Var|CallAcc], [{Pos,Var}|DefAcc]);
|
||||
{Pos,Var} ->
|
||||
convert(T, S, Opt, [Var|CallAcc], DefAcc)
|
||||
end;
|
||||
|
||||
convert([H|T], S, Opt, CallAcc, DefAcc) ->
|
||||
convert(T, S, Opt, [H|CallAcc], DefAcc);
|
||||
|
||||
convert([], S, _Opt, CallAcc, DefAcc) ->
|
||||
{ lists:reverse(CallAcc), lists:reverse(DefAcc), S }.
|
||||
|
||||
is_sequential(List) -> is_sequential(List, 1).
|
||||
|
||||
is_sequential([{ '&', _, [Int] }|T], Int) ->
|
||||
is_sequential(T, Int + 1);
|
||||
|
||||
is_sequential([], Int) when Int > 1 -> true;
|
||||
is_sequential(_, _Int) -> false.
|
||||
+111
-49
@@ -1,33 +1,47 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4,
|
||||
linify/2, linify/3, unquote/4, tail_join/3, join/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
linify(Line, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, Exprs).
|
||||
do_linify(Line, nil, Exprs).
|
||||
|
||||
do_linify(Line, { Left, Meta, Right }) ->
|
||||
linify(Line, Var, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, Var, Exprs).
|
||||
|
||||
do_linify(Line, { Receiver, Counter } = Var, { Left, Meta, Receiver }) when is_atom(Left), is_list(Meta) ->
|
||||
NewMeta = case keyfind(counter, Meta) of
|
||||
{ counter, _ } -> Meta;
|
||||
_ -> keystore(counter, Meta, Counter)
|
||||
end,
|
||||
do_tuple_linify(Line, Var, NewMeta, Left, Receiver);
|
||||
|
||||
do_linify(Line, Var, { Left, Meta, Right }) when is_list(Meta) ->
|
||||
do_tuple_linify(Line, Var, Meta, Left, Right);
|
||||
|
||||
do_linify(Line, Var, { Left, Right }) ->
|
||||
{ do_linify(Line, Var, Left), do_linify(Line, Var, Right) };
|
||||
|
||||
do_linify(Line, Var, List) when is_list(List) ->
|
||||
[do_linify(Line, Var, X) || X <- List];
|
||||
|
||||
do_linify(_, _, Else) -> Else.
|
||||
|
||||
do_tuple_linify(Line, Var, Meta, Left, Right) ->
|
||||
NewMeta = case ?line(Meta) of
|
||||
0 -> keystore(line, Meta, Line);
|
||||
_ -> Meta
|
||||
end,
|
||||
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
|
||||
|
||||
do_linify(Line, { Left, Right }) ->
|
||||
{ do_linify(Line, Left), do_linify(Line, Right) };
|
||||
|
||||
do_linify(Line, List) when is_list(List) ->
|
||||
[do_linify(Line, X) || X <- List];
|
||||
|
||||
do_linify(_, Else) -> Else.
|
||||
{ do_linify(Line, Var, Left), NewMeta, do_linify(Line, Var, Right) }.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
unquote(Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
unquote(Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
@@ -35,33 +49,48 @@ unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
unquote(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
unquote(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Line, _Meta, _Left, Right, nil) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
|
||||
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
|
||||
unquote(_Meta, _Left, Right, nil) ->
|
||||
argument_error(<<"expected unquote after dot to return an atom, an alias or a quoted call, got: ",
|
||||
('Elixir.Macro':to_string(Right))/binary>>);
|
||||
|
||||
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
unquote(Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
unquote(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Line, _Meta, _Left, Right, _Args) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
|
||||
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
|
||||
unquote(_Meta, _Left, Right, _Args) ->
|
||||
argument_error(<<"expected unquote after dot with args to return an atom or a quoted call, got: ",
|
||||
('Elixir.Macro':to_string(Right))/binary>>).
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
|
||||
Rest ++ Left ++ Right;
|
||||
join(Left, Right) when is_list(Right) ->
|
||||
validate_join(Left),
|
||||
Left ++ Right.
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) ->
|
||||
[H|T] = lists:reverse(Rest ++ Left),
|
||||
tail_join(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
|
||||
validate_join(Left),
|
||||
Tail ++ Left ++ Right;
|
||||
|
||||
tail_join(Left, Right, Tail) when is_list(Left) ->
|
||||
validate_join(Left),
|
||||
[H|T] = lists:reverse(Tail ++ Left),
|
||||
lists:reverse([{ '|', [], [H, Right] }|T]).
|
||||
|
||||
validate_join(List) when is_list(List) ->
|
||||
ok;
|
||||
validate_join(List) when not is_list(List) ->
|
||||
argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
|
||||
('Elixir.Kernel':inspect(List))/binary>>).
|
||||
|
||||
argument_error(Message) ->
|
||||
error('Elixir.ArgumentError':exception([{message,Message}])).
|
||||
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
@@ -113,7 +142,24 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
|
||||
%% Context mark
|
||||
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
do_quote({ defmodule, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) ->
|
||||
%% Only store the context if we actually have a full alias as
|
||||
%% argument, otherwise the expression is being automatically
|
||||
%% generated and we don't want the alias to count.
|
||||
NewMeta =
|
||||
case hd(Args) of
|
||||
{ '__aliases__', _, Atoms } ->
|
||||
case lists:all(fun is_atom/1, Atoms) of
|
||||
true -> keystore(context, Meta, Q#elixir_quote.context);
|
||||
false -> Meta
|
||||
end;
|
||||
_ ->
|
||||
Meta
|
||||
end,
|
||||
|
||||
do_quote_tuple({ defmodule, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ Def, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
|
||||
|
||||
@@ -128,7 +174,7 @@ do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S
|
||||
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
@@ -150,13 +196,9 @@ do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=tr
|
||||
|
||||
%% Imports
|
||||
|
||||
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(function, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S);
|
||||
do_quote_fa('&', Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
@@ -199,8 +241,18 @@ do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
|
||||
{ { '{}', [], TT }, TQ };
|
||||
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, S) when is_list(List) ->
|
||||
% The improper case is pretty inefficient, but improper lists are hopefully
|
||||
% rare.
|
||||
case reverse_improper(List) of
|
||||
{ L } -> do_splice(L, Q, S);
|
||||
{ L, R } ->
|
||||
{ TL, QL } = do_splice(L, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(R, QL, S),
|
||||
{ update_last(TL, fun(X) -> { '|', [], [X, TR] } end), QR }
|
||||
end;
|
||||
do_quote(List, Q, S) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, S);
|
||||
do_splice(lists:reverse(List), Q, S);
|
||||
|
||||
do_quote(Other, Q, _) ->
|
||||
{ Other, Q }.
|
||||
@@ -208,9 +260,9 @@ do_quote(Other, Q, _) ->
|
||||
%% Quote helpers
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
All = [?line(Meta), meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, TAll }, TQ }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
@@ -233,6 +285,16 @@ meta(Meta, #elixir_quote{line=nil}) ->
|
||||
meta(Meta, #elixir_quote{line=Line}) ->
|
||||
keystore(line, Meta, Line).
|
||||
|
||||
reverse_improper(L) -> reverse_improper(L, []).
|
||||
|
||||
reverse_improper([], Acc) -> { Acc };
|
||||
reverse_improper([H|T], Acc) when is_list(T) -> reverse_improper(T, [H|Acc]);
|
||||
reverse_improper([H|T], Acc) -> { [H|Acc], T }.
|
||||
|
||||
update_last([], _) -> [];
|
||||
update_last([H], F) -> [F(H)];
|
||||
update_last([H|T], F) -> [H|update_last(T,F)].
|
||||
|
||||
keyfind(Key, Meta) ->
|
||||
lists:keyfind(Key, 1, Meta).
|
||||
keydelete(Key, Meta) ->
|
||||
@@ -248,24 +310,24 @@ do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_q
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{ TT, QT } = do_splice(T, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, S),
|
||||
|
||||
%% Do the joining at runtime when we are aware of the values.
|
||||
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
|
||||
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
|
||||
{ do_runtime_join(Meta, tail_join, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
|
||||
|
||||
do_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
|
||||
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
|
||||
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_join(Meta, join, [Expr, do_join(Buffer, Acc)]));
|
||||
|
||||
do_splice([H|T], Q, S, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, S),
|
||||
do_splice(T, TQ, S, [TH|Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _S, Buffer, Acc) ->
|
||||
{ do_splice_join(Buffer, Acc), Q }.
|
||||
{ do_join(Buffer, Acc), Q }.
|
||||
|
||||
do_splice_join(Left, []) -> Left;
|
||||
do_splice_join([], Right) -> Right;
|
||||
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
do_join(Left, []) -> Left;
|
||||
do_join([], Right) -> Right;
|
||||
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
|
||||
do_runtime_join(Meta, Fun, Args) ->
|
||||
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user