Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
7b3bbe3a36 | ||
|
|
ade2c25e08 | ||
|
|
e2ba0bd649 | ||
|
|
3de4cbbdb9 | ||
|
|
0510512375 | ||
|
|
a3827527a8 | ||
|
|
820d0cb9a0 | ||
|
|
0e1aac32f5 | ||
|
|
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 | ||
|
|
83591b9d76 | ||
|
|
d88138a8af | ||
|
|
b0bc02ef70 | ||
|
|
a7ffebc915 | ||
|
|
7ec5ec80b8 | ||
|
|
071988e8b1 | ||
|
|
e820fb27b1 | ||
|
|
f4b329a796 | ||
|
|
40653e433b | ||
|
|
c0b007af9a | ||
|
|
2fee17e709 | ||
|
|
ebb5500086 | ||
|
|
9754f056dd | ||
|
|
e19dbf04ed | ||
|
|
c654159a2b | ||
|
|
6715c528df | ||
|
|
1c7a37a2b7 | ||
|
|
84a0985a39 | ||
|
|
73b9ac7449 | ||
|
|
8e659930c0 | ||
|
|
095a91a31f | ||
|
|
7393973d96 | ||
|
|
c3c8c3c71d | ||
|
|
73eeec2eec | ||
|
|
c0bb6d73ae | ||
|
|
82da86afcb | ||
|
|
c1a7c039fe | ||
|
|
5b754c8630 | ||
|
|
d195429470 | ||
|
|
6cc9a4b9f4 | ||
|
|
08aa22616a | ||
|
|
e4ddfc2118 | ||
|
|
4955eb9deb | ||
|
|
befa36ce1e | ||
|
|
5e8f9284d2 | ||
|
|
c0a6dc4b47 | ||
|
|
99da29b430 | ||
|
|
3c3c989ef7 | ||
|
|
977ec32476 | ||
|
|
27b4ebe34e | ||
|
|
b0f9d7e9bc | ||
|
|
f9afc520ca | ||
|
|
a6589c62a4 | ||
|
|
77155c55a6 | ||
|
|
776b07f2f0 | ||
|
|
1f00e01679 | ||
|
|
a7fa43338e | ||
|
|
394ccb3f42 | ||
|
|
e7c0a2a3a7 | ||
|
|
f808da8ffb | ||
|
|
c0c8f7f72b | ||
|
|
290403f7d7 | ||
|
|
967ca97455 | ||
|
|
ba00a1ffc6 | ||
|
|
a209dc72e3 | ||
|
|
b6ea6b63ed | ||
|
|
df4cee50d5 | ||
|
|
318eaebf3f | ||
|
|
0a182b4590 | ||
|
|
88ac1f6a7b | ||
|
|
621db32ffb | ||
|
|
5b2f44d1d7 | ||
|
|
ad3cf4e451 | ||
|
|
209c88ab0b | ||
|
|
3c3cf143e7 | ||
|
|
45454ebaf0 | ||
|
|
e321550127 | ||
|
|
2dc1e41aca | ||
|
|
74c82f8590 | ||
|
|
63cfe89096 | ||
|
|
5e5bf1ddb1 | ||
|
|
262a47d6ee | ||
|
|
f8dfc485ea | ||
|
|
52ab64d60b | ||
|
|
3de7f42155 | ||
|
|
3cf4c21ad1 | ||
|
|
802b0d11a6 | ||
|
|
67443d3691 | ||
|
|
85371e4e45 | ||
|
|
2da5326549 | ||
|
|
a83547723e | ||
|
|
879651cba9 | ||
|
|
cc3634ed37 | ||
|
|
5a5ab18a57 | ||
|
|
8fca8a965b | ||
|
|
0beb65298d | ||
|
|
218552af3f | ||
|
|
a2c6630793 | ||
|
|
8c3c0a9149 | ||
|
|
b1181320d3 | ||
|
|
9742c1c17d | ||
|
|
eb4dda0b1e | ||
|
|
bde91c325c | ||
|
|
7c2f25fc85 | ||
|
|
d82f315fa7 | ||
|
|
fc505f8a82 | ||
|
|
241cd1747d | ||
|
|
dd6fb4acc2 | ||
|
|
a056f0ac40 | ||
|
|
b830485541 | ||
|
|
15d347fb96 | ||
|
|
c11d50e451 | ||
|
|
64e6037986 | ||
|
|
8a9927ea1b | ||
|
|
889de92187 | ||
|
|
033fb6a5e9 | ||
|
|
7ad7847596 | ||
|
|
9dcfc53f60 | ||
|
|
26f9ce121a | ||
|
|
0c557d2766 | ||
|
|
e13a516715 | ||
|
|
accf7dac82 | ||
|
|
b3a65eaca1 | ||
|
|
3e29d9fa2e | ||
|
|
0edf1882ff | ||
|
|
35d019559d | ||
|
|
7048236988 | ||
|
|
b335b1663d | ||
|
|
f69ecfe8aa | ||
|
|
10a456823c | ||
|
|
a26d4cf0cd | ||
|
|
3f11eef086 | ||
|
|
ad5885ba2e | ||
|
|
a42725910f | ||
|
|
a6f8e178c0 | ||
|
|
7c2a661a5f | ||
|
|
15af0356a2 | ||
|
|
ebe522ec50 | ||
|
|
6d491325d5 | ||
|
|
f697ff4160 | ||
|
|
4ae58d3bf4 | ||
|
|
650648a687 | ||
|
|
b561c93db2 | ||
|
|
15c1a9891c | ||
|
|
10697628f9 | ||
|
|
89da6e032d | ||
|
|
e969a330d5 | ||
|
|
0f4598fca0 | ||
|
|
cb5bb2e548 | ||
|
|
acf912c2c8 | ||
|
|
6b82967dd6 | ||
|
|
789d01ae2a | ||
|
|
531434959f | ||
|
|
d9baf3c39e | ||
|
|
91be13d128 | ||
|
|
62527cae9c | ||
|
|
466c2b2e1b | ||
|
|
2ba6238dd1 | ||
|
|
48cef0c65b | ||
|
|
3d208e219d | ||
|
|
f8fd11bef0 | ||
|
|
9703c9968b | ||
|
|
82f5868257 | ||
|
|
3ab74433c0 | ||
|
|
31dec377f0 | ||
|
|
a6db102d05 | ||
|
|
628d1b630e | ||
|
|
fbd7c34211 | ||
|
|
25f5546d6f | ||
|
|
288925c2fa | ||
|
|
4a0b1aa384 | ||
|
|
ee1b87089d | ||
|
|
074db53fbb | ||
|
|
60693e48e5 | ||
|
|
e49a00d3cb | ||
|
|
1fe756a2db | ||
|
|
1da36c5b04 | ||
|
|
7a2aea160c | ||
|
|
2837ce381b | ||
|
|
3ae417cbb2 | ||
|
|
12c5cf0515 | ||
|
|
26eafd1a4c | ||
|
|
cdd8e5862d | ||
|
|
eee1993cdd | ||
|
|
46e898e413 | ||
|
|
82e1c9337c | ||
|
|
3bac4ddec6 | ||
|
|
0703be3863 | ||
|
|
d087686f3d | ||
|
|
595d343e05 | ||
|
|
dce2bee57e | ||
|
|
d512f545f4 | ||
|
|
1bdefbb3d1 | ||
|
|
0e4bef3ac8 | ||
|
|
c5121feac5 | ||
|
|
08f592610e | ||
|
|
664d1ec0d3 | ||
|
|
8ca28496e4 | ||
|
|
c5cd8c3fcd | ||
|
|
cf98f9b975 | ||
|
|
26e6fdbeb2 | ||
|
|
35edf833b6 | ||
|
|
1a343b01d6 | ||
|
|
bc76627feb | ||
|
|
0e0d93076c | ||
|
|
c04dcc1882 | ||
|
|
5822cd153f | ||
|
|
65c624ef55 | ||
|
|
6c56bb8682 | ||
|
|
b666c696b4 | ||
|
|
1f8c8f5292 | ||
|
|
03323b9bfa | ||
|
|
8195662b98 | ||
|
|
d6c0533395 | ||
|
|
474d4f6532 | ||
|
|
e548807605 | ||
|
|
25f82dcb46 | ||
|
|
596670ab9c | ||
|
|
c20ee8f7e9 | ||
|
|
3e13f77d1b | ||
|
|
76be461964 | ||
|
|
31431fa795 | ||
|
|
3bf1b08af3 | ||
|
|
f1baed6434 | ||
|
|
e9aa9b213b | ||
|
|
93b3c6fc3d | ||
|
|
17eb2c77c8 | ||
|
|
1b043c36bb | ||
|
|
cda2986a63 | ||
|
|
9bc67bbfdf | ||
|
|
83c964ae46 | ||
|
|
1b2d8bb915 | ||
|
|
34f0f93e8f | ||
|
|
e6cc35fbbd | ||
|
|
6e9b8fb454 | ||
|
|
83173698da | ||
|
|
72b7b8ebe2 | ||
|
|
358e49378c | ||
|
|
e8bd90a1c8 | ||
|
|
096711b517 | ||
|
|
d599e76366 | ||
|
|
1c16ebe2ff | ||
|
|
eb813f71d6 | ||
|
|
ce683fc10e | ||
|
|
7dd8fa4c8d | ||
|
|
fd7e038927 | ||
|
|
f3a0a52c10 | ||
|
|
b7018f77f3 | ||
|
|
01230d68c7 | ||
|
|
e1bca3c65f | ||
|
|
f726b6bbdf | ||
|
|
00e9098d19 | ||
|
|
7b7c691196 | ||
|
|
94d714bb2f | ||
|
|
1be0a5329b | ||
|
|
73fdf7442b | ||
|
|
5598eb68d0 | ||
|
|
99a03ffcf9 | ||
|
|
07e8a7318c | ||
|
|
b7dac7e24a | ||
|
|
9b01bae463 | ||
|
|
ca4997e7f4 | ||
|
|
4c1ee4e261 | ||
|
|
cacb7d5997 | ||
|
|
4b502985d4 | ||
|
|
48ecb7d6f0 | ||
|
|
023866b2b0 | ||
|
|
72c3432042 | ||
|
|
d7facd11df | ||
|
|
76b8b9f3a9 | ||
|
|
20f26909c8 | ||
|
|
276eee072d | ||
|
|
37621d4a07 | ||
|
|
a768da2980 | ||
|
|
8e5f14dc74 | ||
|
|
c832d3a78e | ||
|
|
c052558cbf | ||
|
|
a0d225f891 | ||
|
|
45862f8472 | ||
|
|
dbe2851cdb | ||
|
|
33d6780bb1 | ||
|
|
8924fa4aee | ||
|
|
e4812ec185 | ||
|
|
ea32d951ab | ||
|
|
598f0e0141 | ||
|
|
d484499917 | ||
|
|
00a6753497 | ||
|
|
edb5016112 | ||
|
|
672dc8c595 | ||
|
|
f203560832 | ||
|
|
e3d086abb5 | ||
|
|
74ea72246e | ||
|
|
447a57750f | ||
|
|
e1c3df3bef | ||
|
|
4c5cb1bc7a | ||
|
|
81d36cd921 | ||
|
|
88e4b5833f | ||
|
|
d5323e3311 | ||
|
|
1e73ea0527 | ||
|
|
dc3ca821fb | ||
|
|
c9fe36c28b | ||
|
|
7ba5733496 | ||
|
|
e035dda2db | ||
|
|
8e8ebe73b9 | ||
|
|
30e56696c9 | ||
|
|
c817e6a078 | ||
|
|
c8a04ac0ad | ||
|
|
e66fe507bb | ||
|
|
6ea09bb2ef | ||
|
|
7a1065809a | ||
|
|
8f194b7d19 | ||
|
|
6a4fa93b00 | ||
|
|
cad30a73fe | ||
|
|
0ba2162113 | ||
|
|
42841a021e | ||
|
|
690687353f | ||
|
|
25862cb7a3 | ||
|
|
227e7690a3 | ||
|
|
a6f256fbe8 | ||
|
|
3ebca00872 | ||
|
|
7e11cea780 | ||
|
|
ad7f1c6a3b | ||
|
|
a8b7663f37 | ||
|
|
3b6afdd7d4 | ||
|
|
943fabb548 | ||
|
|
53f5fe71bb | ||
|
|
c406ce4aac | ||
|
|
4b4a7e8d4a | ||
|
|
7d2214678e | ||
|
|
81972106d9 | ||
|
|
6a95d0517f | ||
|
|
8abed86b1a | ||
|
|
5a882161ac | ||
|
|
ffb1d9be4f | ||
|
|
d5e2b41c37 | ||
|
|
c049c1388a | ||
|
|
eb93b9f7a9 | ||
|
|
867d1eaf9b | ||
|
|
e8fd78b85d | ||
|
|
1a578f6574 | ||
|
|
001c923f43 | ||
|
|
8170ff7b2c | ||
|
|
f442fc0d77 | ||
|
|
c608f8279a | ||
|
|
d99a5c5e9e | ||
|
|
5999caed62 | ||
|
|
163485b5ba | ||
|
|
f7c8ed0dd6 | ||
|
|
54097e11d7 | ||
|
|
a41528d297 | ||
|
|
0060190580 | ||
|
|
1da4844103 | ||
|
|
7f2862762a | ||
|
|
3e2af17a83 | ||
|
|
1f7a8065fa |
+162
-5
@@ -1,18 +1,175 @@
|
||||
# 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 fix
|
||||
* [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 fix
|
||||
* [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
|
||||
* [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
|
||||
* [Kernel] Add document algebra library used as the foundation for pretty printing
|
||||
* [Kernel] Add `defrecordp/3` that enables specifying the first element of the tuple
|
||||
* [Kernel] Add the `Set` API and a hash based implementation via `HashSet`
|
||||
* [Kernel] Add `Stream` as composable, lazy-enumerables
|
||||
* [Mix] `mix archive` now includes the version of the generated archive
|
||||
* [Mix] Mix now requires explicit dependency overriding to be given with `override: true`
|
||||
* [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
|
||||
* [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
|
||||
* [Kernel] Ensure variables defined in the module body are not passed to callbacks
|
||||
* [Mix] On dependencies conflict, show from where each source is coming from
|
||||
* [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
|
||||
* [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`
|
||||
* [Kernel] Deprecate recently added `quote binding: ...` in favor of the clearer `quote bind_quoted: ...`
|
||||
* [Kernel] Deprecate `Kernel.float/1` in favor of a explicit conversion
|
||||
* [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
|
||||
* [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:
|
||||
|
||||
do_something 1, is_list [], 3
|
||||
[1, is_atom :foo, 3]
|
||||
|
||||
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
|
||||
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
|
||||
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
|
||||
|
||||
# v0.9.3 (2013-06-23)
|
||||
|
||||
* enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
* [Kernel] Add --warnings-as-errors to Elixir's compiler options
|
||||
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
|
||||
* [Kernel] Print warnings to stderr
|
||||
* [Kernel] Warn on undefined module attributes
|
||||
* [Kernel] Emit warning for `x in []` in guards
|
||||
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
|
||||
* [Kernel] `quote` now allows a binding as an option
|
||||
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
|
||||
* [Mix] Implement `Mix.Version` for basic versioniong semantics
|
||||
* [Mix] Implement `Mix.Version` for basic versioning semantics
|
||||
* [Mix] Support creation and installation of archives (.ez files)
|
||||
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
|
||||
* [Record] Allow types to be given to `defrecord` and `defrecordp`
|
||||
* [Record] Allow types to be given to `defrecordp`
|
||||
|
||||
* bug fix
|
||||
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
|
||||
@@ -29,7 +186,7 @@
|
||||
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed. Check `elixir_parser.yrl` for more information.
|
||||
* [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
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
|
||||
@@ -49,7 +206,7 @@
|
||||
* bug fix
|
||||
* [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
|
||||
* [IEx] Ensure `--cookie` is set before accessing remote shell
|
||||
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
|
||||
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
|
||||
* [Mix] Start the application by default on escripts
|
||||
|
||||
+91
-14
@@ -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
|
||||
@@ -144,16 +211,26 @@ in order to craft an excellent pull request:
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Push your topic branch up to your fork:
|
||||
5. Make sure all the tests are still passing.
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
This command will compile the code in your branch and use that
|
||||
version of Elixir to run the tests. This is needed to ensure your changes can
|
||||
pass all the tests.
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
6. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
7. If you haven't updated your pull request for a while, you should consider
|
||||
8. If you haven't updated your pull request for a while, you should consider
|
||||
rebasing on master and resolving any conflicts.
|
||||
|
||||
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
|
||||
|
||||
@@ -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,9 +1,15 @@
|
||||
REBAR := $(shell echo `pwd`/rebar)
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
REBAR := "$(shell echo `pwd`/rebar)"
|
||||
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))
|
||||
INSTALL_PATH := /usr/local
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
LIBDIR := lib
|
||||
INSTALL = install
|
||||
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
|
||||
@@ -14,15 +20,15 @@ define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(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)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@ cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
endef
|
||||
|
||||
#==> Compilation tasks
|
||||
@@ -35,11 +41,11 @@ default: compile
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
@ rm -rf lib/elixir/src/elixir.app.src
|
||||
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
|
||||
$(Q) rm -rf lib/elixir/src/elixir.app.src
|
||||
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
@@ -48,21 +54,21 @@ elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@ if [ ! -f $(KERNEL) ]; then \
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ $(MAKE) unicode
|
||||
@ rm -rf lib/elixir/ebin/elixir.app
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
$(Q) $(MAKE) unicode
|
||||
$(Q) rm -rf lib/elixir/ebin/elixir.app
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -72,28 +78,28 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
install: compile
|
||||
@ echo "==> elixir (install)"
|
||||
for dir in lib/*; do \
|
||||
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
|
||||
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
done
|
||||
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
|
||||
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
|
||||
install -m755 -d $(INSTALL_PATH)/bin
|
||||
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
|
||||
|
||||
clean:
|
||||
@ 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:
|
||||
@ rm -f lib/*/ebin/Elixir.*.beam
|
||||
$(Q) rm -f lib/*/ebin/Elixir.*.beam
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
@@ -115,13 +121,13 @@ 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
|
||||
|
||||
release_erl: compile
|
||||
@ rm -rf rel/elixir
|
||||
@ cd rel && ../rebar generate
|
||||
$(Q) rm -rf rel/elixir
|
||||
$(Q) cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
@@ -129,29 +135,29 @@ test: test_erlang test_elixir
|
||||
|
||||
test_erlang: compile
|
||||
@ echo "==> elixir (eunit)"
|
||||
@ mkdir -p lib/elixir/test/ebin
|
||||
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
$(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 ""
|
||||
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
|
||||
@ 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
|
||||
@ rm -f .dialyzer_plt
|
||||
@ cp .dialyzer.base_plt .dialyzer_plt
|
||||
$(Q) rm -f .dialyzer_plt
|
||||
$(Q) cp .dialyzer.base_plt .dialyzer_plt
|
||||
@ echo "==> Adding Elixir to PLT..."
|
||||
@ 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..."
|
||||
@ 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
-6
@@ -2,23 +2,27 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `+dev` extension from VERSION
|
||||
1) Remove `-dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
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`, update elixir-lang.org
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to Elixir's elixir-lang.org/packages.html
|
||||
7) Release new zip with `make release_zip`, push new zip to Github Releases
|
||||
|
||||
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) 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
|
||||
|
||||
|
||||
+4
-13
@@ -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
|
||||
@@ -43,7 +43,7 @@ while [ $I -le $# ]; do
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh)
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--app)
|
||||
S=2
|
||||
;;
|
||||
--detached|--hidden)
|
||||
@@ -74,21 +74,12 @@ done
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ "$ERL_LIBS" = "" ]
|
||||
then
|
||||
ERL_LIBS="$SCRIPT_PATH/../lib"
|
||||
else
|
||||
PATHSEP=":"
|
||||
if [ "$OSTYPE" = "msys" ]; then PATHSEP=";" ; fi
|
||||
ERL_LIBS="$ERL_LIBS$PATHSEP$SCRIPT_PATH/../lib"
|
||||
fi
|
||||
|
||||
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" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS "$ERL_LIBS" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS "$ERL_LIBS" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
fi
|
||||
|
||||
+33
-60
@@ -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 (*)
|
||||
@@ -47,69 +46,43 @@ set beforeExtra=
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par=%1
|
||||
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 \--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
|
||||
@@ -38,4 +38,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-nouser -s Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
+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
|
||||
|
||||
@@ -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,13 +67,28 @@ 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
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
|
||||
quote(do: Keyword.get var!(assigns), unquote(name))
|
||||
quote do: Keyword.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
@@ -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 %>
|
||||
@@ -128,11 +128,11 @@ 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -312,7 +306,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: binary_to_list(__FILE__), line: 7]
|
||||
[file: to_char_list(__FILE__), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +315,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: binary_to_list(__FILE__), line: 18]
|
||||
[file: to_char_list(__FILE__), line: 18]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,15 +327,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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ExUnit.start []
|
||||
ExUnit.start [trace: "--trace" in System.argv]
|
||||
@@ -6,16 +6,18 @@
|
||||
|
||||
-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
|
||||
@@ -41,7 +43,7 @@
|
||||
imports_hygiene=true,
|
||||
unquote=true,
|
||||
unquoted=false,
|
||||
mark=true
|
||||
escape=false
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
@@ -61,7 +63,7 @@
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
|
||||
%% Atoms
|
||||
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
|
||||
@@ -53,15 +53,36 @@ defmodule Behaviour do
|
||||
do_defcallback(fun, quote(do: term), __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, caller) do
|
||||
@doc """
|
||||
Defines 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 +95,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 +106,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 +134,7 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
def __behaviour__(:docs) do
|
||||
@__behaviour_docs
|
||||
unquote(Macro.escape(docs))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,92 +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.
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_binary(thing) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
@@ -1,462 +0,0 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
|
||||
defprotocol Binary.Inspect do
|
||||
@moduledoc """
|
||||
The `Binary.Inspect` protocol is responsible for
|
||||
converting any structure to a binary for textual
|
||||
representation. All basic data structures
|
||||
(tuple, list, function, pid, etc) implement the
|
||||
inspect protocol. Other structures are advised to
|
||||
implement the protocol in order to provide pretty
|
||||
printing.
|
||||
"""
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defmodule Binary.Inspect.Utils do
|
||||
@moduledoc false
|
||||
|
||||
## container_join
|
||||
|
||||
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
|
||||
container_join(tuple_to_list(tuple), first, last, opts)
|
||||
end
|
||||
|
||||
def container_join(list, first, last, opts) do
|
||||
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
|
||||
end
|
||||
|
||||
defp do_container_join(_, _opts, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp do_container_join([h], opts, _counter) do
|
||||
Kernel.inspect(h, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, counter) when is_list(t) do
|
||||
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, _counter) do
|
||||
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([], _opts, _counter) do
|
||||
""
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
## escape
|
||||
|
||||
def escape(other, char) do
|
||||
b = do_escape(other, char, <<>>)
|
||||
<< char, b :: binary, char >>
|
||||
end
|
||||
|
||||
@compile {:inline, do_escape: 3}
|
||||
defp do_escape(<<>>, _char, binary), do: binary
|
||||
defp do_escape(<< char, t :: binary >>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
defp do_escape(<<?\a, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
defp do_escape(<<?\b, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
end
|
||||
defp do_escape(<<?\d, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
end
|
||||
defp do_escape(<<?\e, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
end
|
||||
defp do_escape(<<?\f, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
end
|
||||
defp do_escape(<<?\n, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
end
|
||||
defp do_escape(<<?\r, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
end
|
||||
defp do_escape(<<?\\, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
end
|
||||
defp do_escape(<<?\t, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
end
|
||||
defp do_escape(<<?\v, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp do_escape(<<h, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, h >>)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
require Macro
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
Represents the atom as an Elixir term. The atoms false, true
|
||||
and nil are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
|
||||
def inspect(false, _), do: "false"
|
||||
def inspect(true, _), do: "true"
|
||||
def inspect(nil, _), do: "nil"
|
||||
def inspect(:"", _), do: ":\"\""
|
||||
def inspect(Elixir, _), do: "Elixir"
|
||||
|
||||
def inspect(atom, _) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
Module.to_binary(atom)
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
":" <> escape(binary, ?")
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
end
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
defp valid_ref_piece?(<<>>), do: true
|
||||
defp valid_ref_piece?(_), do: false
|
||||
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
when h == ?_ do
|
||||
valid_identifier? t
|
||||
end
|
||||
|
||||
defp valid_identifier?(other), do: other
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: BitString do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %B"""
|
||||
Represents the string as itself escaping
|
||||
all necessary characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
"""
|
||||
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
escape(thing, ?")
|
||||
else
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing, opts) do
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp as_bitstring(bitstring, opts) do
|
||||
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
|
||||
end
|
||||
|
||||
defp each_bit(_, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
|
||||
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter) do
|
||||
integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: List do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %B"""
|
||||
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.
|
||||
|
||||
Inspecting a list is conservative as it does not try
|
||||
to guess how the list is encoded. That said, `'josé'`
|
||||
will likely be inspected as `[106,111,115,195,169]`
|
||||
because we can't know if it is encoded in utf-8
|
||||
or iso-5569-1, which is common in Erlang libraries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
iex> inspect([0|'bar'])
|
||||
"[0,98,97,114]"
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo,:bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _), do: "[]"
|
||||
|
||||
def inspect(thing, opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
escape(:unicode.characters_to_binary(thing), ?')
|
||||
keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
|
||||
end, ", ")
|
||||
end
|
||||
|
||||
defp key_to_binary(key, opts) do
|
||||
case Binary.Inspect.Atom.inspect(key, opts) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
Inspect tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1,2,3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect({}, _), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts[:raw] do
|
||||
record_inspect(tuple, opts)
|
||||
end || container_join(tuple, "{", "}", opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
record_join(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp record_join(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
|
||||
record_join(fields, tail, opts) <> "]"
|
||||
end
|
||||
|
||||
defp record_join([f], [v], opts) do
|
||||
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
|
||||
end
|
||||
|
||||
defp record_join([fh|ft], [vh|vt], opts) do
|
||||
atom_to_binary(fh, :utf8) <> ": " <>
|
||||
Kernel.inspect(vh, opts) <> ", " <>
|
||||
record_join(ft, vt, opts)
|
||||
end
|
||||
|
||||
defp record_join([], [], _opts) do
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Number do
|
||||
@moduledoc """
|
||||
Represents the number as a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def inspect(thing, _) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Regex do
|
||||
@moduledoc %B"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
|
||||
def inspect(regex, _opts) when size(regex) == 5 do
|
||||
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect other, Keyword.put(opts, :raw, true)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Function do
|
||||
@moduledoc """
|
||||
Inspect functions, when possible, in a literal form.
|
||||
"""
|
||||
|
||||
def inspect(function, _opts) do
|
||||
fun_info = :erlang.fun_info(function)
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
"function(#{Kernel.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
|
||||
else
|
||||
'#Fun' ++ rest = :erlang.fun_to_list(function)
|
||||
"#Function" <> list_to_binary(rest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: PID do
|
||||
@moduledoc "Inspect PIDs"
|
||||
|
||||
def inspect(pid, _) do
|
||||
"#PID" <> list_to_binary pid_to_list(pid)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Port do
|
||||
@moduledoc "Inspect ports"
|
||||
|
||||
def inspect(port, _) do
|
||||
list_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Reference do
|
||||
@moduledoc "Inspect references"
|
||||
|
||||
def inspect(ref, _) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> list_to_binary(rest)
|
||||
end
|
||||
end
|
||||
+12
-12
@@ -46,83 +46,83 @@ defmodule Bitwise do
|
||||
Bitwise not.
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise not as operator.
|
||||
"""
|
||||
defmacro ~~~expr do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and.
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and as operator.
|
||||
"""
|
||||
defmacro left &&& right do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or.
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or as operator.
|
||||
"""
|
||||
defmacro left ||| right do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor.
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor as operator.
|
||||
"""
|
||||
defmacro left ^^^ right do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left.
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left as operator.
|
||||
"""
|
||||
defmacro left <<< right do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right.
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right as operator.
|
||||
"""
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
+85
-90
@@ -6,18 +6,16 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
The Code module is responsible to manage code compilation,
|
||||
The Code module is responsible for managing code compilation,
|
||||
code evaluation and code loading.
|
||||
|
||||
It complements (Erlang's code module)[1] to add behavior
|
||||
which is specific to Elixir.
|
||||
|
||||
[1]: (www.erlang.org/doc/man/code.html)
|
||||
It 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.
|
||||
Returns all loaded files.
|
||||
"""
|
||||
def loaded_files do
|
||||
:elixir_code_server.call :loaded
|
||||
@@ -26,75 +24,86 @@ defmodule Code do
|
||||
@doc """
|
||||
Removes the given files from the loaded files list.
|
||||
The modules defined in the file are not removed,
|
||||
calling this function only removes it from the list,
|
||||
allowing it to be required again.
|
||||
calling this function only removes them from the list,
|
||||
allowing them to be required again.
|
||||
"""
|
||||
def unload_files(files) do
|
||||
:elixir_code_server.cast { :unload_files, files }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends a path to Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before added.
|
||||
Appends 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 added.
|
||||
Prepends 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 deleted.
|
||||
Deletes 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
|
||||
Evaluates the contents given by `string`. The second argument is
|
||||
a keyword list of variable bindings, followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line the script starts
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
|
||||
Besides, the following scope values can be configured:
|
||||
Additionally, the following scope values can be configured:
|
||||
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `: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 required the `Kernel` module, in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros as `if`, `case`,
|
||||
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`; `binding`
|
||||
is a keyword list with the value of all variable bindings after evaluating
|
||||
`string`. If an error occurs while evaluating `string` an exception will be raised.
|
||||
|
||||
## 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 my pass `__ENV__` as 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 // [])
|
||||
@@ -109,29 +118,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.
|
||||
|
||||
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 my pass `__ENV__` as 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 // [])
|
||||
@@ -188,36 +195,30 @@ 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 quoted form. It returns `{ :ok, quoted_form }`
|
||||
Converts 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
|
||||
|
||||
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/1`, 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) }
|
||||
@@ -225,24 +226,18 @@ 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 quoted form. It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a TokenMissingError
|
||||
Converts 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
|
||||
|
||||
@@ -253,14 +248,14 @@ defmodule Code do
|
||||
@doc """
|
||||
Loads 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
|
||||
in many times. I.e. if `load_file` is called N times with
|
||||
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)
|
||||
@@ -272,16 +267,16 @@ defmodule Code do
|
||||
|
||||
@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 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)
|
||||
@@ -307,22 +302,22 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options. Those options are global
|
||||
Sets 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, for such reasons, `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
|
||||
@@ -334,10 +329,10 @@ defmodule Code do
|
||||
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 """
|
||||
@@ -351,10 +346,10 @@ defmodule Code do
|
||||
|
||||
@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,
|
||||
loaded, it works as no-op. If the module was not yet loaded,
|
||||
it tries to load it.
|
||||
|
||||
If it succeeds loading the module anyhow, it returns
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
|
||||
@@ -362,13 +357,13 @@ defmodule Code do
|
||||
|
||||
Erlang has two modes to load code: interactive and embedded.
|
||||
|
||||
By default, the Erlang VM runs on interactive mode, where modules
|
||||
By default, the Erlang VM runs in interactive mode, where modules
|
||||
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
|
||||
@@ -377,15 +372,15 @@ 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)
|
||||
@@ -402,10 +397,10 @@ defmodule Code do
|
||||
@doc """
|
||||
Ensures 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 anyhow, it returns
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
|
||||
|
||||
+86
-63
@@ -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,25 +11,21 @@ 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}"
|
||||
@@ -44,12 +40,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 +62,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
|
||||
@@ -73,18 +74,19 @@ defmodule Dict do
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
ListDict
|
||||
true ->
|
||||
unsupported_dict(unquote(dict))
|
||||
end
|
||||
end
|
||||
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]
|
||||
|
||||
@@ -95,11 +97,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]
|
||||
|
||||
@@ -114,7 +117,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
|
||||
|
||||
@@ -125,11 +128,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)
|
||||
@@ -143,11 +146,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)
|
||||
@@ -160,18 +163,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)
|
||||
@@ -189,7 +187,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)
|
||||
@@ -207,7 +205,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
|
||||
@@ -223,7 +221,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
|
||||
@@ -235,17 +233,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
|
||||
|
||||
@@ -256,13 +254,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]
|
||||
@@ -274,14 +276,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)
|
||||
@@ -300,17 +305,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]}
|
||||
@@ -327,13 +332,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
|
||||
@@ -345,7 +355,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
|
||||
@@ -365,17 +375,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], [] }
|
||||
@@ -392,12 +402,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]
|
||||
@@ -410,11 +420,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)
|
||||
@@ -432,6 +443,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
|
||||
@@ -439,17 +458,17 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal, if the dicts are of different types they're
|
||||
Check if two dicts are equal. 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
|
||||
@@ -480,4 +499,8 @@ defmodule Dict do
|
||||
def to_list(dict) do
|
||||
target(dict).to_list(dict)
|
||||
end
|
||||
|
||||
defp unsupported_dict(dict) do
|
||||
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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 size(dict1) == 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
|
||||
+506
-186
File diff suppressed because it is too large
Load Diff
+85
-44
@@ -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
|
||||
|
||||
@@ -48,7 +48,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 +59,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 +67,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 +96,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.
|
||||
"""
|
||||
@@ -157,39 +166,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 +209,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 +236,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 +255,20 @@ 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()"
|
||||
|
||||
"""
|
||||
def format_mfa(module, fun, arity) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
<< ?:, erl :: binary >> -> erl
|
||||
@@ -256,19 +283,29 @@ defmodule Exception do
|
||||
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
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
|
||||
def format_file_line(file, line, cwd) do
|
||||
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 +317,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
|
||||
|
||||
+157
-186
@@ -33,14 +33,14 @@ 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}"
|
||||
@@ -49,7 +49,7 @@ end
|
||||
|
||||
defexception File.IteratorError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"error during file iteration: #{formatted}"
|
||||
end
|
||||
end
|
||||
@@ -59,20 +59,20 @@ defmodule File do
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `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`, `IO.binwrite` and
|
||||
`IO.binreadline` 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.
|
||||
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.
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
@@ -104,7 +104,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 +116,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 +161,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 +186,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 +210,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 +218,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 +233,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 +280,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 +290,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 +314,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 +323,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 +345,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 +358,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 +384,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 +392,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 +410,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 +459,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 +467,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,13 +476,13 @@ 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
|
||||
@@ -489,12 +492,12 @@ defmodule File do
|
||||
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 +526,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 +545,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 +579,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 +610,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 +626,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.rddir('tmp_dir')
|
||||
File.rmdir('tmp_dir')
|
||||
#=> :ok
|
||||
|
||||
File.rmdir('file.txt')
|
||||
@@ -641,7 +644,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 +654,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 +672,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 +683,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 +699,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 +746,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,12 +754,11 @@ 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`, `IO.binwrite` and
|
||||
`IO.binreadline` 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.
|
||||
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.
|
||||
|
||||
The allowed modes:
|
||||
|
||||
@@ -757,10 +786,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:
|
||||
|
||||
@@ -776,7 +805,7 @@ defmodule File do
|
||||
## Examples
|
||||
|
||||
{ :ok, file } = File.open("foo.tar.gz", [:read, :compressed])
|
||||
IO.readline(file)
|
||||
IO.read(file, :line)
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@@ -807,7 +836,7 @@ defmodule File do
|
||||
## Examples
|
||||
|
||||
File.open("file.txt", [:read, :write], fn(file) ->
|
||||
IO.readline(file)
|
||||
IO.read(file, :line)
|
||||
end)
|
||||
|
||||
"""
|
||||
@@ -831,7 +860,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
|
||||
|
||||
@@ -843,7 +872,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
|
||||
|
||||
@@ -855,7 +884,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
|
||||
@@ -865,7 +894,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
|
||||
@@ -880,13 +909,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
|
||||
|
||||
@@ -916,7 +945,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
|
||||
@@ -929,7 +958,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
|
||||
|
||||
@@ -946,45 +975,18 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the file device into an iterator that can be
|
||||
passed into `Enum`. The device is iterated line
|
||||
by line, at the end of iteration the file is closed.
|
||||
|
||||
This reads the file as utf-8. Check out `File.biniterator`
|
||||
to handle the file as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
An example that lazily iterates a file replacing all double
|
||||
quotes per single quotes and writes each line to a target file
|
||||
is shown below:
|
||||
|
||||
{ :ok, device } = File.open("README.md")
|
||||
source = File.iterator(device)
|
||||
File.open "NEWREADME.md", [:write], fn(target) ->
|
||||
Enum.each source, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
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 when streaming begins.
|
||||
"""
|
||||
def iterator(device) do
|
||||
fn(fun, acc) ->
|
||||
do_iterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def iterator!(file, mode // []) do
|
||||
fn(fun, acc) ->
|
||||
def stream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(acc, fun) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_iterator(device, fun, acc)
|
||||
IO.stream(device, line_or_bytes, acc, fun)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
@@ -992,27 +994,18 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the file device into an iterator that can
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
def biniterator(device) do
|
||||
fn(fun, acc) ->
|
||||
do_biniterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
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`.
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only when streaming begins.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
def binstream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
IO.binstream(device, line_or_bytes, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
@@ -1020,9 +1013,9 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
def chmod(file, mode) do
|
||||
F.change_mode(file, mode)
|
||||
@@ -1035,14 +1028,14 @@ 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
|
||||
|
||||
@doc """
|
||||
Changes the user group given by the group id `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
Changes the user group given by the group id `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
def chgrp(file, gid) do
|
||||
F.change_group(file, gid)
|
||||
@@ -1055,16 +1048,16 @@ 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
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
Changes the owner given by the user id `gid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
def chown(file, uid) do
|
||||
def chown(file, uid) do
|
||||
F.change_owner(file, uid)
|
||||
end
|
||||
|
||||
@@ -1075,7 +1068,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
|
||||
|
||||
@@ -1095,26 +1088,4 @@ defmodule File do
|
||||
|
||||
defp open_defaults([], true), do: [:binary]
|
||||
defp open_defaults([], false), do: []
|
||||
|
||||
defp do_iterator(device, acc, fun) do
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_biniterator(device, acc, fun) do
|
||||
case F.read_line(device) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define GenEvent callbacks in Elixir.
|
||||
This module is a convenience for defining GenEvent callbacks in Elixir.
|
||||
|
||||
GenEvent is an OTP behaviour that encapsulates event handling functionality.
|
||||
|
||||
## Example
|
||||
|
||||
Bellow follows an example of a GenEvent that stores notifications
|
||||
Below is an example of a GenEvent that stores notifications
|
||||
until they are fetched:
|
||||
|
||||
defmodule MyEventHandler do
|
||||
@@ -29,11 +29,16 @@ defmodule GenEvent.Behaviour do
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
#=> {:ok,#PID<0.42.0>}
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
#=> :ok
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
#=> :ok
|
||||
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
#=> :ok
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> [1, 2]
|
||||
@@ -44,12 +49,12 @@ defmodule GenEvent.Behaviour do
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server.
|
||||
|
||||
Starting and sending messages to gen_event is done
|
||||
Starting and sending messages to the GenEvent is done
|
||||
via Erlang's `:gen_event` module. For more information,
|
||||
please refer to the following:
|
||||
|
||||
http://www.erlang.org/doc/man/gen_event.html
|
||||
http://learnyousomeerlang.com/event-handlers
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
"""
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -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, :default_implementation, 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
|
||||
@@ -1,15 +1,15 @@
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define GenServer callbacks in Elixir.
|
||||
This module is a convenience for defining GenServer callbacks in Elixir.
|
||||
|
||||
A server is responsible to react to messages received from client
|
||||
and a GenServer is an OTP behaviour that encapsulates common server
|
||||
A server is responsible for reacting to messages received from a client.
|
||||
A GenServer is an OTP behaviour that encapsulates common server
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Bellow follows an example of a GenServer that push and pop items
|
||||
into a stack:
|
||||
Below is an example of a GenServer that pushes and pops items
|
||||
onto a stack:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
@@ -20,17 +20,17 @@ defmodule GenServer.Behaviour do
|
||||
{ :reply, h, t }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, _config) do
|
||||
def handle_call(request, from, config) do
|
||||
# Call the default implementation from GenServer.Behaviour
|
||||
super
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, config) do
|
||||
{ :noreply, [item|config] }
|
||||
end
|
||||
|
||||
def handle_cast(_request, _config) do
|
||||
super
|
||||
def handle_cast(request, config) do
|
||||
super(request, config)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,8 +47,8 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server. **cast** messages are
|
||||
asynchronous while **call** ones are synchronous. In the case of
|
||||
GenServer's, there are 8 different values a callback such as
|
||||
asynchronous while **call** ones are synchronous. For a
|
||||
GenServer, there are 8 different values a callback such as
|
||||
`handle_call` or `handle_cast` can return:
|
||||
|
||||
{ :reply, reply, new_state }
|
||||
@@ -62,7 +62,7 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenServer. The
|
||||
`GenServer.Behaviour` module defines all of them automatically, but
|
||||
allows us to customize the ones we need. The list of callbacks are:
|
||||
allows us to customize the ones we need. The required callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started;
|
||||
* `handle_call(msg, from, state)` - invoked to handle call messages;
|
||||
@@ -74,13 +74,13 @@ defmodule GenServer.Behaviour do
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Starting and sending messages to the gen_server is done
|
||||
Starting and sending messages to the GenServer is done
|
||||
via Erlang's `:gen_server` module. For more information,
|
||||
please refer to the following:
|
||||
|
||||
http://www.erlang.org/doc/man/gen_server.html
|
||||
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
http://learnyousomeerlang.com/clients-and-servers
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
"""
|
||||
|
||||
@doc false
|
||||
@@ -122,4 +122,4 @@ defmodule GenServer.Behaviour do
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+67
-190
@@ -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.
|
||||
#
|
||||
@@ -35,7 +35,7 @@ defmodule HashDict do
|
||||
# The ordered record contains a single bucket
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered,
|
||||
defrecordp :ordered, HashDict,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule HashDict do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie,
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @node_size * @expand_load,
|
||||
@@ -57,6 +57,7 @@ defmodule HashDict do
|
||||
import Bitwise
|
||||
|
||||
# Let's inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
|
||||
@doc """
|
||||
@@ -101,28 +102,18 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key and value in the dict.
|
||||
"""
|
||||
def put(dict, key, value) do
|
||||
{ dict, _ } = dict_put(dict, key, { :put, value })
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given value under key in the dictionary
|
||||
only if one does not exist yet.
|
||||
"""
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
@doc false
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
IO.write "HashDict.update/3 is deprecated, please use HashDict.update!/3 instead\n#{Exception.format_stacktrace}"
|
||||
update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dictionary according
|
||||
to the given function. Raises if the key does
|
||||
not exist in the dictionary.
|
||||
"""
|
||||
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
|
||||
@@ -131,55 +122,19 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dictionary according
|
||||
to the given function. Adds initial value if
|
||||
the key does not exist in the dicionary.
|
||||
"""
|
||||
def update(dict, key, initial, fun) when is_function(fun, 1) do
|
||||
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value under key from the dict.
|
||||
"""
|
||||
def get(dict, key, default // nil) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
end
|
||||
def fetch(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value under key from the dict
|
||||
and return it in a tagged tuple.
|
||||
"""
|
||||
def fetch(dict, key) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
def fetch(trie(root: root, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
def fetch!(dict, key) when is_tuple(dict) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if the dict has the given key.
|
||||
"""
|
||||
def has_key?(dict, key) do
|
||||
match? { ^key, _ }, dict_get(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the dict as well as the dict without key.
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
@@ -187,87 +142,35 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a value from the dict.
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the dict size.
|
||||
"""
|
||||
def size(dict) do
|
||||
elem(dict, 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict.
|
||||
"""
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two dicts are equal
|
||||
"""
|
||||
def equal?(dict1, dict2) do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all keys in the dict.
|
||||
"""
|
||||
def keys(dict) do
|
||||
dict_fold(dict, [], fn { k, _ }, acc -> [k|acc] end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all values in the dict.
|
||||
"""
|
||||
def values(dict) do
|
||||
dict_fold(dict, [], fn { _, v }, acc -> [v|acc] end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two dictionaries.
|
||||
"""
|
||||
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 ->
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
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 ->
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
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
|
||||
@@ -285,42 +188,17 @@ 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
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
def reduce(trie() = dict, acc, fun) do
|
||||
dict_fold(dict, acc, fun)
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_get(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp dict_get(trie(root: root, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
@@ -381,30 +259,15 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_equal?(dict1, dict2) do
|
||||
try do
|
||||
reduce(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
|
||||
defp bucket_get([{k, _}|_bucket], key) when k > key do
|
||||
false
|
||||
defp bucket_get([[k|_]|_bucket], key) when k > key do
|
||||
:error
|
||||
end
|
||||
|
||||
defp bucket_get([{key, _}=e|_bucket], key) do
|
||||
e
|
||||
defp bucket_get([[key|value]|_bucket], key) do
|
||||
{ :ok, value }
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
@@ -412,24 +275,24 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
false
|
||||
:error
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([{k, _}|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [{key, value}|bucket], 1 }
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [[key|value]|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k, _}|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [{key, initial}|bucket], 1 }
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [[key|initial]|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{key, _}|bucket], key, { :put, value }) do
|
||||
{ [{key, value}|bucket], 0 }
|
||||
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
|
||||
{ [[key|value]|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{key, value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key, fun.(value)}|bucket], 0 }
|
||||
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
|
||||
{ [[key|fun.(value)]|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
@@ -438,26 +301,26 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [{key, value}], 1 }
|
||||
{ [[key|value]], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [{key, initial}], 1 }
|
||||
{ [[key|initial]], 1 }
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([{k, _}|_]=bucket, key, value) when k > key, do: [{key, value}|bucket]
|
||||
defp bucket_put!([{key, _}|bucket], key, value), do: [{key, value}|bucket]
|
||||
defp bucket_put!([{_, _}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [{key, value}]
|
||||
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
|
||||
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
|
||||
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [[key|value]]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([{k, _}|_]=bucket, key) when k > key do
|
||||
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{key, value}|bucket], key) do
|
||||
defp bucket_delete([[key|value]|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
@@ -470,7 +333,15 @@ defmodule HashDict do
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Folds the bucket
|
||||
# Reduces the bucket
|
||||
defp bucket_reduce([[k|v]|t], acc, fun) do
|
||||
bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
end
|
||||
|
||||
defp bucket_reduce([], acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
@@ -533,6 +404,20 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(bucket, -1, acc, fun, _) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_reduce(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
@@ -574,13 +459,13 @@ defmodule HashDict do
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract([{k, _v}=e|acc], [{key, _value}|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
:lists.foldl fn [key|value], acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
@@ -597,11 +482,3 @@ end
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: HashDict do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(dict, opts) do
|
||||
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is meant to work well with both small and
|
||||
large sets. It is an implementation of the `Set` behaviour.
|
||||
For more information about the functions and their APIs,
|
||||
please consult the `Set` module.
|
||||
"""
|
||||
|
||||
@behaviour Set
|
||||
|
||||
# The ordered record contains a single bucket.
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashSet,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b1111
|
||||
@node_shift 4
|
||||
@node_size 16
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@expand_default (@node_size * @expand_load)
|
||||
@contract_default @contract_load
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @expand_default,
|
||||
contract_on: @contract_default,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
def new() do
|
||||
ordered()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
def new(members) do
|
||||
Enum.reduce members, ordered(), fn member, set ->
|
||||
put(set, member)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_fold set1, set2, fn v1, acc ->
|
||||
put(acc, v1)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_fold set2, set1, fn v, acc ->
|
||||
put(acc, v)
|
||||
end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_filter set1, fn e -> set_member?(set2, e) end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set2, fn e -> set_member?(set1, e) end
|
||||
end
|
||||
|
||||
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set1, fn m -> not set_member?(set2, m) end
|
||||
end
|
||||
|
||||
def member?(set, member) when is_record(set, HashSet) do
|
||||
set_member?(set, member)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def size(set) do
|
||||
elem(set, 1)
|
||||
end
|
||||
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
{ set, _ } = set_put(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def delete(set, member) do
|
||||
{ set, _ } = set_delete(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def equal?(set1, set2) do
|
||||
size = elem(set1, 1)
|
||||
case elem(set2, 1) do
|
||||
^size ->
|
||||
set_equal?(set1, set2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(set1, set2) do
|
||||
set_equal?(set1, set2)
|
||||
end
|
||||
|
||||
def disjoint?(set1, set2) do
|
||||
set_disjoint?(set1, set2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
def reduce(trie() = set, acc, fun) do
|
||||
set_fold(set, acc, fun)
|
||||
end
|
||||
|
||||
## HashSet-wide functions
|
||||
|
||||
defp set_filter(ordered(bucket: bucket, size: size), fun) do
|
||||
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
|
||||
ordered(bucket: new, size: size - removed_count)
|
||||
end
|
||||
|
||||
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
|
||||
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
|
||||
|
||||
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
|
||||
contract_trie(trie(root: new,
|
||||
size: size - removed_count,
|
||||
depth: depth,
|
||||
contract_on: trie(set, :contract_on),
|
||||
expand_on: trie(set, :expand_on)))
|
||||
else
|
||||
trie(size: size - removed_count, root: new, depth: depth)
|
||||
end
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
|
||||
root = node_relocate(bucket, 0)
|
||||
set_put(trie(size: @ordered_threshold, root: root), member)
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
|
||||
{ new, count } = bucket_put(bucket, member)
|
||||
{ ordered(set, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
set = trie(set, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
set_put(set, member)
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
{ root, count } = node_put(root, depth, pos, member)
|
||||
{ trie(set, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp set_member?(ordered(bucket: bucket), member) do
|
||||
:lists.member(member, bucket)
|
||||
end
|
||||
|
||||
defp set_member?(trie(root: root, depth: depth), member) do
|
||||
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
|
||||
end
|
||||
|
||||
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
|
||||
case bucket_delete(bucket, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ new_bucket, -1 } ->
|
||||
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
case node_delete(root, depth, pos, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ root, -1 } ->
|
||||
{ if depth > 0 and trie(set, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(set,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(set, :expand_on), @node_size))
|
||||
else
|
||||
trie(set, size: size - 1, root: root)
|
||||
end, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_equal?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp set_disjoint?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
false -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp set_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp set_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
defp bucket_filter([e|bucket], fun, acc, count) do
|
||||
case fun.(e) do
|
||||
true -> bucket_filter(bucket, fun, [e|acc], count)
|
||||
false -> bucket_filter(bucket, fun, acc, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_filter([], _fun, acc, count) do
|
||||
{ :lists.reverse(acc), count }
|
||||
end
|
||||
|
||||
defp bucket_put([m|_]=bucket, member) when m > member do
|
||||
{ [member|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([member|bucket], member) do
|
||||
{ [member|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], member) do
|
||||
{ rest, count } = bucket_put(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([], member) do
|
||||
{ [member], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
|
||||
defp bucket_put!([member|bucket], member), do: [member|bucket]
|
||||
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
|
||||
defp bucket_put!([], member), do: [member]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([m,_|_]=bucket, member) when m > member do
|
||||
{ bucket, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([member|bucket], member) do
|
||||
{ bucket, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], member) do
|
||||
{ rest, count } = bucket_delete(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _member) do
|
||||
{ [], 0 }
|
||||
end
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
# Trie resizing
|
||||
|
||||
defp contract_trie(trie(depth: 0) = set) do
|
||||
set
|
||||
end
|
||||
|
||||
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
|
||||
new_contract_on = div(contract_on, @node_size)
|
||||
new_expand_on = div(expand_on, @node_size)
|
||||
|
||||
if new_contract_on == 0, do: new_contract_on = @contract_default
|
||||
if new_expand_on == 0, do: new_expand_on = @expand_default
|
||||
|
||||
contract_trie(trie(set, root: node_contract(root, depth),
|
||||
size: size,
|
||||
depth: depth - 1,
|
||||
contract_on: new_contract_on,
|
||||
expand_on: new_expand_on))
|
||||
end
|
||||
|
||||
defp contract_trie(set) do
|
||||
set
|
||||
end
|
||||
|
||||
# Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
defp node_put(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp node_filter(bucket, -1, fun, _) do
|
||||
bucket_filter(bucket, fun, [], 0)
|
||||
end
|
||||
|
||||
defp node_filter(node, depth, fun, count) when count >= 1 do
|
||||
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
|
||||
{ _, 0 } ->
|
||||
node_filter(node, depth, fun, count - 1)
|
||||
{ new_element, count1 } ->
|
||||
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
|
||||
{ new_node, count1 + count2 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_filter(node, _, _fun, 0) do
|
||||
{ node, 0 }
|
||||
end
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn member, acc ->
|
||||
pos = member |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
|
||||
end, node, bucket
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
|
||||
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
|
||||
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
|
||||
node_relocate(b16, n) }
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
|
||||
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
|
||||
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
|
||||
node_expand(b16, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
|
||||
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
|
||||
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
|
||||
node_contract(b16, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|
||||
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|
||||
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
|
||||
end
|
||||
|
||||
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
|
||||
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
|
||||
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
|
||||
def member?(set, v), do: HashSet.member?(set, v)
|
||||
def count(set), do: HashSet.size(set)
|
||||
end
|
||||
@@ -0,0 +1,476 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
The `Inspect` protocol is responsible for converting any Elixir
|
||||
data structure into an algebra document. This document is then
|
||||
formatted, either in pretty printing format or a regular one.
|
||||
|
||||
The `inspect/2` function receives the entity to be inspected
|
||||
followed by the inspecting options, represented by the record
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in
|
||||
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
|
||||
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
|
||||
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 in function
|
||||
of existing entities. For example, here is `HashSet`'s `inspect`
|
||||
implementation:
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
The `concat` function comes from `Inspect.Algebra` and it
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
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 string,
|
||||
although that will devoid it of any pretty-printing.
|
||||
"""
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
@doc """
|
||||
Represents the atom as an Elixir term. The atoms `false`, `true`
|
||||
and `nil` are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
def inspect(atom, _opts) do
|
||||
inspect(atom)
|
||||
end
|
||||
|
||||
def inspect(false), do: "false"
|
||||
def inspect(true), do: "true"
|
||||
def inspect(nil), do: "nil"
|
||||
def inspect(:""), do: ":\"\""
|
||||
def inspect(Elixir), do: "Elixir"
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :[], :<<>>] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
end
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
defp valid_ref_piece?(<<>>), do: true
|
||||
defp valid_ref_piece?(_), do: false
|
||||
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
when h == ?_ do
|
||||
valid_identifier? t
|
||||
end
|
||||
|
||||
defp valid_identifier?(other), do: other
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"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
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
inspect_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing, opts) do
|
||||
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 inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
each_bit(bitstring, opts.limit, "<<") <> ">>"
|
||||
end
|
||||
|
||||
defp each_bit(_, 0, acc) do
|
||||
acc <> "..."
|
||||
end
|
||||
|
||||
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, acc) do
|
||||
acc <> integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter, acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter, acc) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc %S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
printed in keywords syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[] = opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
Kernel.inspect(value, opts)
|
||||
)
|
||||
end
|
||||
|
||||
defp key_to_binary(key) do
|
||||
case Inspect.Atom.inspect(key) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@doc """
|
||||
Represents tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1, 2, 3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect({}, _opts), 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
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
surround_record(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
surround_record(name, fields, tail, opts)
|
||||
end
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
case atom_to_binary(name) do
|
||||
"Elixir." <> _ ->
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, 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) <> ": ",
|
||||
Kernel.inspect(v, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Number do
|
||||
@doc """
|
||||
Represents the number as a string.
|
||||
|
||||
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
|
||||
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
|
||||
Conference on Programming Language Design and Implementation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _opts) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %S"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect(other, opts.raw(true))
|
||||
end
|
||||
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
|
||||
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
|
||||
else
|
||||
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" <> iolist_to_binary(pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
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" <> iolist_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,390 @@
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc %S"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
|
||||
|
||||
An algebra document is represented by an `Inspect.Algebra` node
|
||||
or a regular string.
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
iex> "foo"
|
||||
"foo"
|
||||
|
||||
With the functions in this module, we can concatenate different
|
||||
elements together and render them:
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"foo"
|
||||
|
||||
The functions `nest/2`, `space/2` and `line/2` help you put the
|
||||
document together into a rigid structure. However, the document
|
||||
algebra gets interesting when using functions like `break/2`, which
|
||||
converts the given string into a line break depending on how much space
|
||||
there is to print. Let's glue two docs together with a break and then
|
||||
render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
Finally, this module also contains Elixir related functions, a bit
|
||||
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
|
||||
|
||||
## Implementation details
|
||||
|
||||
The original Haskell implementation of the algorithm by [Wadler][1]
|
||||
relies on lazy evaluation to unfold document groups on two alternatives:
|
||||
`:flat` (breaks as spaces) and `:break` (breaks as newlines).
|
||||
Implementing the same logic in a strict language such as Elixir leads
|
||||
to an exponential growth of possible documents, unless document groups
|
||||
are encoded explictly as `:flat` or `:break`. Those groups are then reduced
|
||||
to a simple document, where the layout is already decided, per [Lindig][0].
|
||||
|
||||
This implementation slightly changes the semantic of Lindig's algorithm
|
||||
to allow elements that belong to the same group to be printed together
|
||||
in the same line, even if they do not fit the line fully. This was achieved
|
||||
by changing `:break` to mean a possible break and `:flat` to force a flat
|
||||
structure. Then deciding if a break works as a newline is just a matter
|
||||
of checking if we have enough space until the next break that is not
|
||||
inside a group (which is still flat).
|
||||
|
||||
Custom pretty printers can be implemented using the documents returned
|
||||
by this module and by providing their own rendering functions.
|
||||
|
||||
[0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
|
||||
[1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
|
||||
|
||||
"""
|
||||
|
||||
@surround_separator ","
|
||||
@tail_separator " |"
|
||||
@newline "\n"
|
||||
@nesting 1
|
||||
@break " "
|
||||
|
||||
defp repeat(_, 0), do: ""
|
||||
defp repeat(s, i), do: :lists.duplicate(i, s)
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@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
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
|
||||
"""
|
||||
@spec empty() :: :doc_nil
|
||||
def empty, do: :doc_nil
|
||||
|
||||
@doc """
|
||||
Concatenates two document entities. Takes two arguments:
|
||||
left doc and right doc. Returns a DocCons doc
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"TastelessArtosis"
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons_t
|
||||
def concat(x, y), do: doc_cons(left: x, right: y)
|
||||
|
||||
@doc """
|
||||
Concatenates a list of documents.
|
||||
"""
|
||||
@spec concat([t]) :: doc_cons_t
|
||||
def concat(docs), do: folddoc(docs, concat(&1, &2))
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
appended to the line breaks.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
" 6"
|
||||
|
||||
"""
|
||||
@spec nest(t, 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)
|
||||
|
||||
@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.
|
||||
|
||||
## Examples
|
||||
|
||||
Let's glue two docs together with a break and then render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break_t
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
|
||||
@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(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(t, binary, t) :: doc_cons_t
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %S"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.group(
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.group(
|
||||
...> Inspect.Algebra.concat(
|
||||
...> "Hello,",
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.break,
|
||||
...> "A"
|
||||
...> )
|
||||
...> )
|
||||
...> ),
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.break,
|
||||
...> "B"
|
||||
...> )
|
||||
...> ))
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hello, A B"
|
||||
iex> Inspect.Algebra.pretty(doc, 6)
|
||||
"Hello,\nA B"
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group_t
|
||||
def group(d), do: doc_group(doc: d)
|
||||
|
||||
@doc """
|
||||
Inserts a mandatory single space between two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes Wadler"
|
||||
|
||||
"""
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %S"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes\nWadler"
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
def line(x, y), do: concat(x, concat(@newline, y))
|
||||
|
||||
@doc """
|
||||
Folds a list of document entities into a document entity
|
||||
using a function that is passed as the first argument.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = ["A", "B"]
|
||||
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
|
||||
...> Inspect.Algebra.concat [x, "!", y]
|
||||
...> end)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"A!B"
|
||||
|
||||
"""
|
||||
@spec folddoc([any], ((any, [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 %S"""
|
||||
Surrounds a document with characters.
|
||||
|
||||
Puts the document between left and right enclosing and nesting it.
|
||||
The document is marked as a group, to show the maximum as possible
|
||||
concisely together.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
|
||||
iex> Inspect.Algebra.pretty(doc, 3)
|
||||
"[a\n b]"
|
||||
|
||||
"""
|
||||
@spec surround(binary, t, binary) :: t
|
||||
def surround(left, doc, right) do
|
||||
group concat [left, nest(doc, @nesting), right]
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
|
||||
## Examples
|
||||
|
||||
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> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
|
||||
def surround_many(left, [], right, _, _fun) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun) do
|
||||
surround(left, surround_many(docs, limit, fun), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun) do
|
||||
fun.(h)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun) when is_list(t) do
|
||||
glue(
|
||||
concat(fun.(h), @surround_separator),
|
||||
surround_many(t, decrement(limit), fun)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun) do
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
)
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc """
|
||||
The pretty printing function.
|
||||
|
||||
Takes the maximum width and a document to print as its arguments
|
||||
and returns the string representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
|
||||
render(sdoc)
|
||||
end
|
||||
|
||||
defp default_mode(:infinity), do: :flat
|
||||
defp default_mode(_), do: :break
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# 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: "" :: 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
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@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
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
|
||||
@doc false
|
||||
@spec format(integer, 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]])
|
||||
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
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
|
||||
s_text(str: s, sdoc: format(w, k, t))
|
||||
else
|
||||
s_line(indent: i, sdoc: format(w, i, t))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec render(sdoc) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary do_render sdoc
|
||||
end
|
||||
|
||||
@spec do_render(sdoc) :: [binary]
|
||||
defp do_render(:s_nil), do: [""]
|
||||
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
|
||||
defp do_render(s_line(indent: i, sdoc: d)) do
|
||||
prefix = repeat " ", i
|
||||
[@newline | [prefix | do_render d]]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
defmodule Integer do
|
||||
@moduledoc """
|
||||
Functions for working with integers.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns true if `n` is an odd number, otherwise false.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
quote do: rem(unquote(n), 2) != 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `n` is an even number, otherwise false.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
quote do: rem(unquote(n), 2) == 0
|
||||
end
|
||||
end
|
||||
+145
-82
@@ -5,7 +5,7 @@ defmodule IO do
|
||||
|
||||
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:
|
||||
|
||||
@@ -18,67 +18,64 @@ defmodule IO do
|
||||
|
||||
* A list of binaries or a list of char lists (as described above);
|
||||
|
||||
* If none of the above, `to_binary` is invoked in the
|
||||
* If none of the above, `to_string` is invoked on the
|
||||
given argument;
|
||||
|
||||
"""
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def read(device // group_leader(), count) do
|
||||
:io.get_chars(map_dev(device), "", count)
|
||||
defmacrop is_iolist(data) do
|
||||
quote do
|
||||
is_list(unquote(data)) or is_binary(unquote(data))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device as binary,
|
||||
no unicode conversion happens.
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
Check `read/2` for more information.
|
||||
* `data` - The input characters.
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def binread(device // group_leader(), count) do
|
||||
case :file.read(map_dev(device), count) do
|
||||
def read(device // group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), '')
|
||||
end
|
||||
|
||||
def read(device, count) when count >= 0 do
|
||||
:io.get_chars(map_dev(device), '', count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes 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.
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
|
||||
This function does the same as `gets/2`,
|
||||
except the prompt is not required as argument.
|
||||
"""
|
||||
def readline(device // group_leader()) do
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device as binary,
|
||||
no unicode conversion happens.
|
||||
|
||||
Check `readline/1` for more information.
|
||||
"""
|
||||
def binreadline(device // group_leader()) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
def binread(device, count) when count >= 0 do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
@@ -101,8 +98,8 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
def write(device // group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_iodata(item)
|
||||
def write(device // group_leader(), item) when is_iolist(item) do
|
||||
:io.put_chars map_dev(device), item
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -111,34 +108,27 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
def binwrite(device // group_leader(), item) do
|
||||
:file.write map_dev(device), to_iodata(item)
|
||||
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.
|
||||
"""
|
||||
def puts(device // group_leader(), item) do
|
||||
def puts(device // group_leader(), item) when is_iolist(item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given arguments to stderr as a warning.
|
||||
|
||||
If `--warnings-as-errors` is true and this function is
|
||||
called during compilation, compilation will fail.
|
||||
"""
|
||||
def warn(item) do
|
||||
:elixir_code_server.cast(:register_warning)
|
||||
:io.put_chars :standard_error, [to_iodata(item), ?\n]
|
||||
:io.put_chars erl_dev, [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
|
||||
of 80 characters.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -152,23 +142,33 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
def inspect(device, item, opts) do
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
|
||||
unless Keyword.get(opts, :width) do
|
||||
opts = case :io.columns(device) do
|
||||
{ :ok, width } -> Keyword.put(opts, :width, width)
|
||||
{ :error, _ } -> opts
|
||||
end
|
||||
end
|
||||
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
|
||||
@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.
|
||||
"""
|
||||
def getn(prompt, count // 1)
|
||||
@@ -183,12 +183,12 @@ 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.
|
||||
"""
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
:io.get_chars(map_dev(device), prompt, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -197,21 +197,84 @@ 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.
|
||||
"""
|
||||
def gets(device // group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_iodata(prompt))
|
||||
:io.get_line(map_dev(device), prompt)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
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/2` to handle the IO as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example on how we mimic an echo server
|
||||
from the command line:
|
||||
|
||||
Enum.each IO.stream(:stdio, :line), IO.write(&1)
|
||||
|
||||
"""
|
||||
def stream(device, line_or_bytes) do
|
||||
stream(map_dev(device), line_or_bytes, &1, &2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device) do
|
||||
IO.write "IO.stream(device) is deprecated, please use IO.stream(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
stream(device, :line)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line.
|
||||
|
||||
This reads the io as a raw binary.
|
||||
"""
|
||||
def binstream(device, line_or_bytes) do
|
||||
binstream(map_dev(device), line_or_bytes, &1, &2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device) do
|
||||
IO.write "IO.binstream(device) is deprecated, please use IO.binstream(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
binstream(device, :line)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device, what, acc, fun) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
stream(device, what, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device, what, acc, fun) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
binstream(device, what, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
quote binding: [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
|
||||
@@ -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,7 +138,7 @@ 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
|
||||
@@ -160,7 +167,7 @@ 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
|
||||
@@ -201,7 +208,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
|
||||
|
||||
+550
-459
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,18 @@
|
||||
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))
|
||||
@@ -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
|
||||
|
||||
@@ -100,6 +84,34 @@ defmodule Kernel.CLI do
|
||||
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([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
|
||||
@@ -141,6 +153,10 @@ defmodule Kernel.CLI 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
|
||||
@@ -201,6 +217,10 @@ defmodule Kernel.CLI do
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
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
|
||||
@@ -322,13 +342,14 @@ defmodule Kernel.CLI do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, Path.wildcard(&1)
|
||||
files = Enum.uniq(List.concat(files))
|
||||
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)
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
:ok
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
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,63 @@ 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
|
||||
* `:each_waiting` - every time a module waits for another module to be compiled,
|
||||
this callback is invoked with the module we are waiting
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks except
|
||||
by `each_waiting`, which must return nil or a file as a hint where the source
|
||||
could be found.
|
||||
|
||||
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 Kernel.CompilationError
|
||||
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 +88,59 @@ 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, 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 <- { self, :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)
|
||||
# If one of the callbacks is each_waiting and we haven't seen
|
||||
# the hinted file before, add it to the list to be processed.
|
||||
if (callback = Keyword.get(callbacks, :each_waiting)) &&
|
||||
(hint = callback.(on)) &&
|
||||
not(hint in original) do
|
||||
files = [hint|files]
|
||||
original = [hint|original]
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -119,7 +119,16 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
defp optimize_expr({ :call, call_line,
|
||||
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
|
||||
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
|
||||
|
||||
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
|
||||
{ value, dict, _ } = optimize_expr(value, module, dict)
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule Kernel.SpecialForms do
|
||||
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 """
|
||||
@@ -51,27 +51,32 @@ 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>>
|
||||
|
||||
@@ -85,7 +90,11 @@ defmodule Kernel.SpecialForms do
|
||||
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 +120,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 +135,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 +150,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.
|
||||
@@ -180,12 +189,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
|
||||
@@ -258,23 +267,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 +309,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
|
||||
@@ -335,15 +342,15 @@ defmodule Kernel.SpecialForms do
|
||||
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 +376,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 +389,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.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the context resolution happens at;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
|
||||
unquote is automatically disabled;
|
||||
`unquote` is automatically disabled;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -424,14 +431,14 @@ 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
|
||||
`ContextSample` module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
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.
|
||||
|
||||
@@ -553,7 +560,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## 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 +583,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,12 +604,12 @@ 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
|
||||
One of Elixir's goals is to provide a 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.
|
||||
@@ -635,7 +642,7 @@ defmodule Kernel.SpecialForms do
|
||||
## 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 +677,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
|
||||
@@ -689,13 +696,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
`kv` representation into the tree. That's when the `:binding`
|
||||
option comes to the rescue. By using `:binding`, we can
|
||||
`kv` representation into the tree. That's when the `:bind_quoted`
|
||||
option comes to the rescue. By using `:bind_quoted`, we can
|
||||
automatically disable unquoting while still injecting the
|
||||
desired variables into the tree:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote binding: [kv: kv] do
|
||||
quote bind_quoted: [kv: kv] do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
@@ -719,7 +726,7 @@ defmodule Kernel.SpecialForms do
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
|
||||
If the context is false, it is not stored in any particular
|
||||
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.
|
||||
@@ -813,6 +820,19 @@ 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: 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:
|
||||
|
||||
erl> [I || I <- [1,2,3], hd(I)].
|
||||
[]
|
||||
|
||||
In Elixir, it will raise:
|
||||
|
||||
iex> lc i inlist [1,2,3], hd(i), do: i
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
defmacro lc(args)
|
||||
|
||||
@@ -839,6 +859,84 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
|
||||
@doc """
|
||||
Captures a call as 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 = &atom_to_binary(&1, :utf8)
|
||||
iex> fun.(:hello)
|
||||
"hello"
|
||||
|
||||
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 = &atom_to_binary(&1, &2)
|
||||
iex> fun.(:hello, :utf8)
|
||||
"hello"
|
||||
|
||||
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 """
|
||||
This is the special form used whenever we have to temporarily
|
||||
change the scope information of a block. Used when `quote` is
|
||||
@@ -867,7 +965,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
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:
|
||||
|
||||
@@ -875,7 +973,7 @@ 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:
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
if export, do:
|
||||
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
|
||||
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
|
||||
type
|
||||
end
|
||||
|
||||
@@ -349,9 +349,9 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, _caller) do
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
raise ArgumentError, message: "invalid type specification #{type_spec}"
|
||||
compile_error caller, "invalid type specification #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
@@ -390,9 +390,9 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, _caller) do
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
raise ArgumentError, message: "invalid function type specification #{spec}"
|
||||
compile_error caller, "invalid function type specification #{spec}"
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
|
||||
@@ -563,8 +563,6 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
IO.warn "(fun(...) -> ...) in typespecs is deprecated, please drop fun() and " <>
|
||||
"write (... -> ...) instead. #{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -593,9 +591,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args}, vars, caller) do
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
|
||||
unless is_atom(remote) do
|
||||
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
|
||||
end
|
||||
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -670,8 +670,8 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l)] }
|
||||
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l, caller)] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
@@ -683,6 +683,10 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
raise CompileError, file: caller.file, line: caller.line, description: desc
|
||||
end
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
@@ -691,9 +695,9 @@ defmodule Kernel.Typespec do
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _) when is_atom(key), do: t
|
||||
defp validate_kw(_, original) do
|
||||
raise ArgumentError, message: "unexpected list #{inspect original} in typespec"
|
||||
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
|
||||
+49
-27
@@ -18,8 +18,8 @@ defmodule Keyword do
|
||||
@type t :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from enum. Differently from `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` do not remove
|
||||
Creates a Keyword from an enum. Unlike `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` does not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
@@ -51,7 +51,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable. Similarly to dicts,
|
||||
Creates a Keyword from an enumerable. Similar to dicts,
|
||||
duplicated entries are removed, the latest one prevails.
|
||||
|
||||
## Examples
|
||||
@@ -87,20 +87,21 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for specific key.
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If key not exist return default value (nil if no default value)
|
||||
exists.
|
||||
If `key` does not exist, return default value (`nil` if no default value).
|
||||
|
||||
If duplicated entries exist, the first one is returned.
|
||||
Use get_values/2 to retrieve all entries.
|
||||
Use `get_values/2` to retrieve all entries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.get([a: 1], :b)
|
||||
nil
|
||||
|
||||
iex> Keyword.get([a: 1], :b, 3)
|
||||
3
|
||||
|
||||
@@ -115,8 +116,8 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetchs the value for specific key and return it in a tuple.
|
||||
If the key does not exist, returns `:error`.
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
If the `key` does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -136,8 +137,8 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific key. If key does not exist,
|
||||
an error is raised.
|
||||
Fetches the value for specific `key`. If `key` does not exist,
|
||||
a `KeyError` is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -157,7 +158,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
Gets all values for a specific `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -179,6 +180,9 @@ defmodule Keyword do
|
||||
iex> Keyword.keys([a: 1, b: 2])
|
||||
[:a,:b]
|
||||
|
||||
iex> Keyword.keys([a: 1, b: 2, a: 3])
|
||||
[:a,:b,:a]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) do
|
||||
@@ -186,7 +190,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all values.
|
||||
Returns all values from the keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -200,8 +204,8 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword list unchanged.
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
|
||||
@@ -209,6 +213,10 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
@@ -219,13 +227,14 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword list unchanged.
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2, a: 3]
|
||||
|
||||
iex> Keyword.delete_first([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
@@ -246,6 +255,9 @@ defmodule Keyword do
|
||||
iex> Keyword.put([a: 1, b: 2], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
@@ -257,6 +269,8 @@ defmodule Keyword do
|
||||
already exists.
|
||||
|
||||
## Examples
|
||||
iex> Keyword.put_new([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
|
||||
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
@@ -326,12 +340,13 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether a given key exists in the given keywords.
|
||||
Returns whether a given `key` exists in the given `keywords`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.has_key?([a: 1], :a)
|
||||
true
|
||||
|
||||
iex> Keyword.has_key?([a: 1], :b)
|
||||
false
|
||||
|
||||
@@ -341,40 +356,47 @@ defmodule Keyword do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
IO.write "Keyword.update/3 is deprecated, please use Keyword.update!/3 instead\n#{Exception.format_stacktrace}"
|
||||
update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
|
||||
## 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
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
[a: 1, b: 11]
|
||||
|
||||
|
||||
+110
-44
@@ -5,40 +5,22 @@ 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]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def concat(list) when is_list(list) do
|
||||
IO.write "List.concat/1 is deprecated, please use Enum.concat/1 instead\n#{Exception.format_stacktrace}"
|
||||
: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]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def concat(list, elements) when is_list(list) and is_list(elements) do
|
||||
IO.write "List.concat/2 is deprecated, please use Enum.concat/2 instead\n#{Exception.format_stacktrace}"
|
||||
list ++ elements
|
||||
end
|
||||
|
||||
@@ -52,13 +34,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 +58,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 +117,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 +137,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 +157,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 +179,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 +191,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 +218,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 +228,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 +250,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
|
||||
@@ -294,8 +311,8 @@ defmodule List do
|
||||
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 +324,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
|
||||
@@ -19,7 +19,19 @@ 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
|
||||
|
||||
+14
-63
@@ -15,7 +15,7 @@ defmodule ListDict do
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.map pairs, fn({ k, v }) -> { k, v } end
|
||||
Enum.to_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -26,38 +26,22 @@ defmodule ListDict do
|
||||
Enum.map list, transform
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys in the dict.
|
||||
"""
|
||||
def keys(dict) do
|
||||
lc { key, _ } inlist dict, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all values in the dict.
|
||||
"""
|
||||
def values(dict) do
|
||||
lc { _, value } inlist dict, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the dict size.
|
||||
"""
|
||||
def size(dict) do
|
||||
length(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the dict has the given key.
|
||||
"""
|
||||
def has_key?(dict, key) do
|
||||
:lists.keymember(key, 1, dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict or default if no key is set.
|
||||
"""
|
||||
def get(dict, key, default // nil) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
@@ -65,10 +49,6 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict in a tagged tuple, otherwise `:error`.
|
||||
"""
|
||||
def fetch(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
@@ -76,10 +56,6 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
raises `KeyError` if the key does not exist.
|
||||
"""
|
||||
def fetch!(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
@@ -87,25 +63,14 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
from the dict as well as the dict without that key.
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key-value pair in the dict.
|
||||
"""
|
||||
def put(dict, key, val) do
|
||||
[{key, val}|delete(dict, key)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key-value pair in the dict
|
||||
if no entry exists yet.
|
||||
"""
|
||||
def put_new(dict, key, val) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, _ } -> dict
|
||||
@@ -113,16 +78,10 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry under the given key from the dict.
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
lc { k, _ } = tuple inlist dict, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given Enumerable into the dict.
|
||||
"""
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, fun) do
|
||||
@@ -133,13 +92,15 @@ defmodule ListDict do
|
||||
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
{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
|
||||
@@ -150,25 +111,24 @@ defmodule ListDict do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dict according to the given function.
|
||||
"""
|
||||
def update([{key, value}|dict], key, fun) do
|
||||
@doc false
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
IO.write "ListDict.update/3 is deprecated, please use ListDict.update!/3 instead\n#{Exception.format_stacktrace}"
|
||||
update!(dict, key, fun)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@doc """
|
||||
Updates the key in the dict according to the given function
|
||||
or uses the given initial value if no entry exists.
|
||||
"""
|
||||
def update([{key, value}|dict], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
@@ -181,20 +141,11 @@ defmodule ListDict do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty `ListDict`.
|
||||
"""
|
||||
def empty(_dict), do: []
|
||||
|
||||
@doc """
|
||||
Check if the ListDict is equal to another ListDict.
|
||||
"""
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) == :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
def to_list(dict), do: dict
|
||||
end
|
||||
|
||||
+232
-128
@@ -5,30 +5,48 @@ defmodule Macro do
|
||||
This module provides conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns a list of binary operators. This is available
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
|
||||
@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
|
||||
|
||||
@typep precedence :: integer # Higher means binds stronger
|
||||
@spec binary_op_props(atom) :: { :left|:right, precedence }
|
||||
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
|
||||
and extracts its arguments. It returns a tuple with the
|
||||
@@ -36,7 +54,7 @@ defmodule Macro do
|
||||
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
|
||||
|
||||
@@ -46,6 +64,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
|
||||
@@ -54,7 +73,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.
|
||||
|
||||
@@ -70,14 +89,28 @@ defmodule Macro do
|
||||
1
|
||||
|
||||
"""
|
||||
@spec escape(Macro.t) :: Macro.t
|
||||
@spec escape(Macro.t, Keyword.t) :: Macro.t
|
||||
def escape(expr, opts // []) do
|
||||
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@doc false
|
||||
def unescape_binary(chars) do
|
||||
IO.write "Macro.unescape_binary/1 is deprecated, please use Macro.unescape_string/1 instead\n#{Exception.format_stacktrace}"
|
||||
unescape_string(chars)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unescape_binary(chars, map) do
|
||||
IO.write "Macro.unescape_binary/2 is deprecated, please use Macro.unescape_string/2 instead\n#{Exception.format_stacktrace}"
|
||||
unescape_string(chars, map)
|
||||
end
|
||||
|
||||
@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`,
|
||||
@@ -85,23 +118,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
|
||||
@@ -122,7 +157,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
|
||||
@@ -132,23 +167,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
|
||||
@@ -156,24 +192,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.
|
||||
|
||||
@@ -183,109 +215,122 @@ 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, :utf8))
|
||||
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)) <> "}"
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
|
||||
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, _, [[do: { :->, _, [{_, _, 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, _, [[do: { :->, _, [_] } = 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, _, [[do: 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, :utf8) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
|
||||
fun.(ast, if right != [] and Keyword.keyword?(right) do
|
||||
to_string(left, fun) <> to_string(right, fun)
|
||||
else
|
||||
to_string(left, fun) <> "[" <> to_string(right, fun) <> "]"
|
||||
end)
|
||||
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 = ast, fun) when is_list(list) do
|
||||
if Keyword.keyword?(list) do
|
||||
fun.(ast, "[" <> kw_list_to_string(list, fun) <> "]")
|
||||
else
|
||||
to_string({ :[], [], list }, 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]
|
||||
@@ -295,64 +340,97 @@ defmodule Macro do
|
||||
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, :utf8)
|
||||
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 "
|
||||
defp kw_block_to_string(key, value, fun) do
|
||||
block = adjust_new_lines block_to_string(value, fun), "\n "
|
||||
atom_to_binary(key, :utf8) <> "\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
|
||||
@@ -369,13 +447,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
|
||||
@@ -439,18 +517,23 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand_once(aliases, env) do
|
||||
expand_once(aliases, env, nil) |> elem(0)
|
||||
def expand_once(ast, env) do
|
||||
expand_once(ast, env, nil) |> elem(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 }
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_tracker.record_alias(receiver, env.module)
|
||||
{ receiver, true, cache }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> { :elixir_aliases.concat(aliases), true, cache }
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_tracker.record_alias(receiver, env.module)
|
||||
{ receiver, true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
@@ -533,8 +616,8 @@ defmodule Macro do
|
||||
|
||||
@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)
|
||||
@@ -553,12 +636,33 @@ defmodule Macro do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc %S"""
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Then it expands all of its children recursively.
|
||||
|
||||
Check `Macro.expand_once/2` for more information on how expansion
|
||||
works.
|
||||
The documentation for `expand_once/2` goes into more detail
|
||||
on how expansion works. Keep in mind that `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.
|
||||
"""
|
||||
def expand_all(tree, env) do
|
||||
expand_all(tree, env, nil) |> elem(0)
|
||||
@@ -590,8 +694,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 }`.
|
||||
"""
|
||||
|
||||
@@ -21,6 +21,7 @@ defmodule Macro.Env do
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `vars` - a list keeping all defined varaibles as { var, context }
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@@ -32,14 +33,15 @@ defmodule Macro.Env do
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom }]
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases]
|
||||
:functions, :macros, :context_modules, :macro_aliases, :vars]
|
||||
|
||||
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]
|
||||
macro_aliases: aliases, vars: vars]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+36
-34
@@ -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.
|
||||
@@ -44,7 +44,7 @@ defmodule Module do
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
|
||||
function/macro must take one argument: the module environment. If it's a
|
||||
function/macro must take one argument: the module environment. If it\'s a
|
||||
macro, its returned value will be injected at the end of the module definition
|
||||
before the compilation starts.
|
||||
|
||||
@@ -91,7 +91,7 @@ defmodule Module do
|
||||
@compile { :inline, myfun: 1 }
|
||||
|
||||
def myfun(arg) do
|
||||
to_binary(arg)
|
||||
to_string(arg)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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,8 +177,11 @@ 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**
|
||||
|
||||
@@ -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
|
||||
@@ -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 """
|
||||
@@ -462,6 +463,19 @@ defmodule Module do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an anonymous function from the given module, function
|
||||
and arity. The module and function are not verified to exist.
|
||||
|
||||
iex> fun = Module.function(Kernel, :is_atom, 1)
|
||||
iex> fun.(:hello)
|
||||
true
|
||||
|
||||
"""
|
||||
def function(mod, fun, arity) do
|
||||
:erlang.make_fun(mod, fun, arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function. It expects
|
||||
the module the function belongs to, the line (a non negative
|
||||
@@ -715,7 +729,7 @@ defmodule Module do
|
||||
|
||||
Expands to:
|
||||
|
||||
Module.get_attribute(__MODULE__, @foo, true)
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
@@ -745,8 +759,8 @@ defmodule Module do
|
||||
if :lists.member(key, acc) do
|
||||
[]
|
||||
else
|
||||
warn && IO.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access"
|
||||
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -785,9 +799,7 @@ defmodule Module do
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are true. Which means that registering
|
||||
an attribute without passing any options will revert the attribute
|
||||
behavior to exactly the same expected in :
|
||||
By default, both options are false.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -802,16 +814,16 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def register_attribute(module, new, opts // []) do
|
||||
def register_attribute(module, new, opts) when is_atom(new) do
|
||||
assert_not_compiled!(:register_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist, true) do
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, { :__persisted_attributes, [new|old] })
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate, true) do
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, { :__acc_attributes, [new|old] })
|
||||
end
|
||||
@@ -827,17 +839,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a module name to binary without the Elixir prefix.
|
||||
"""
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
tl(String.split(String.Chars.to_string(module), "."))
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -854,7 +856,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -887,9 +889,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
|
||||
@@ -897,7 +899,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
|
||||
|
||||
|
||||
@@ -94,8 +94,6 @@ defmodule Module.DispatchTracker do
|
||||
|
||||
@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
|
||||
@@ -114,9 +112,16 @@ defmodule Module.DispatchTracker do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched
|
||||
to. All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
Returns all the aliases defined in the given module.
|
||||
"""
|
||||
@spec aliases(ref) :: [module]
|
||||
def aliases(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :alias)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched to.
|
||||
"""
|
||||
@spec remotes(ref) :: [module]
|
||||
def remotes(ref) do
|
||||
@@ -156,7 +161,11 @@ defmodule Module.DispatchTracker do
|
||||
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
|
||||
@@ -197,16 +206,22 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds an alias.
|
||||
@doc false
|
||||
def add_alias(pid, module) when is_atom(module) do
|
||||
:gen_server.cast(pid, { :add_alias, module })
|
||||
end
|
||||
|
||||
# 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 })
|
||||
:gen_server.cast(pid, { :add_external, :import, function, module, target })
|
||||
end
|
||||
|
||||
# Associates a module with a warn. This adds the given
|
||||
@@ -307,6 +322,7 @@ defmodule Module.DispatchTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :alias)
|
||||
:digraph.add_vertex(d, :import)
|
||||
:digraph.add_vertex(d, :remote)
|
||||
:digraph.add_vertex(d, :warn)
|
||||
@@ -337,13 +353,14 @@ 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)
|
||||
def handle_cast({ :add_alias, module }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :alias, module)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :import, function, module, name, arity)
|
||||
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
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+121
-66
@@ -1,49 +1,69 @@
|
||||
defmodule OptionParser do
|
||||
@moduledoc """
|
||||
This module contains functions to parse command line arguments.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses the argv and returns one tuple with parsed options
|
||||
and the 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 second argument:
|
||||
A set of aliases can be given as the second argument:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [debug: true], [] }
|
||||
{ [debug: true], [], [] }
|
||||
|
||||
## Switches
|
||||
|
||||
Extra information about switches can be given as argument too.
|
||||
This is useful in order to say a switch must behave as a boolean
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
|
||||
* `:boolean` - Mark the given switch as boolean. Boolean switches
|
||||
never consumes the following value unless it is
|
||||
true or false;
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
`true` or `false`;
|
||||
* `: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` - Keep duplicated items in the list instead of overriding;
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
|
||||
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
|
||||
|
||||
@@ -51,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
|
||||
@@ -59,16 +84,18 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to parse but only parses the head of the argv.
|
||||
I.e. as soon as it finds a non switch, it stops parsing.
|
||||
Similar to `parse/2` but only parses the head of `argv`;
|
||||
as soon as it finds a non-switch, it stops parsing.
|
||||
|
||||
Check `parse/2` for more info.
|
||||
See `parse/2` for more information.
|
||||
|
||||
## 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"], [] }
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
@@ -83,90 +110,118 @@ 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
|
||||
end
|
||||
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 String.to_integer(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
end
|
||||
:float in kinds ->
|
||||
case String.to_float(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
end
|
||||
true ->
|
||||
{ nil, value }
|
||||
end
|
||||
|
||||
defp store_option(dict, option, value, kind) do
|
||||
if is_switch_a? :keep, kind do
|
||||
[{ option, value }|dict]
|
||||
if invalid_option do
|
||||
{ dict, [{ option, value }|invalid] }
|
||||
else
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
{ do_store_option(dict, option, value, kinds), invalid }
|
||||
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 do_store_option(dict, option, value, kinds) do
|
||||
cond do
|
||||
:keep in kinds ->
|
||||
[{ option, value }|dict]
|
||||
true ->
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, aliases) do
|
||||
normalize_option(option, aliases)
|
||||
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
|
||||
normalize_option(option, switches, 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
|
||||
|
||||
defp to_underscore(option) do
|
||||
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
|
||||
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
|
||||
|
||||
+32
-15
@@ -4,12 +4,12 @@ defmodule Path do
|
||||
retrieving file system paths.
|
||||
|
||||
The functions in this module may receive a char list or
|
||||
a binary as argument and will return a value of the same
|
||||
a binary as an argument and will return a value of the same
|
||||
type.
|
||||
|
||||
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
|
||||
@@ -18,8 +18,8 @@ defmodule Path do
|
||||
@type r :: char_list | binary
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Differently from
|
||||
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
|
||||
Converts the given path to an absolute one. Unlike
|
||||
`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`.
|
||||
|
||||
Differently from `Path.expand/2`, no attempt is made to
|
||||
Unlike `expand/2`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
|
||||
## Examples
|
||||
@@ -67,7 +67,7 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Path.expand("/foo/bar/../bar")
|
||||
Path.expand("/foo/bar/../bar")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@@ -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.
|
||||
@@ -91,9 +91,9 @@ defmodule Path do
|
||||
Path.expand("foo/bar/../bar", "baz")
|
||||
#=> "/quux/baz/foo/bar"
|
||||
|
||||
iex> Path.expand("foo/bar/../bar", "/baz")
|
||||
Path.expand("foo/bar/../bar", "/baz")
|
||||
"/baz/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar", "/baz")
|
||||
Path.expand("/foo/bar/../bar", "/baz")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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 }
|
||||
|
||||
@@ -30,7 +30,7 @@ defmodule Protocol do
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import :macros, Protocol.DSL
|
||||
import Protocol.DSL, only: :macros
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@@ -71,8 +71,14 @@ defmodule Protocol do
|
||||
|
||||
defmodule name do
|
||||
@behaviour unquote(protocol)
|
||||
@protocol unquote(protocol)
|
||||
@for unquote(for)
|
||||
|
||||
unquote(block)
|
||||
def __impl__, do: unquote(protocol)
|
||||
|
||||
def __impl__(:name), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -169,7 +175,7 @@ defmodule Protocol do
|
||||
impl_bang = if returns_nil do
|
||||
quote do
|
||||
def __impl_for__!(arg) do
|
||||
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, structure: arg)
|
||||
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: arg)
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -359,7 +365,7 @@ defmodule Protocol.DSL do
|
||||
defp nil_clause() do
|
||||
{ [nil],
|
||||
[],
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, value: xA)) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, nil) do
|
||||
@@ -400,7 +406,7 @@ defmodule Protocol.DSL do
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
quote do
|
||||
meta = quote do
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@@ -410,13 +416,17 @@ defmodule Protocol.DSL do
|
||||
# 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
|
||||
|
||||
impl = quote bind_quoted: [name: name, arity: arity] do
|
||||
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
|
||||
Kernel.def unquote(name)(unquote_splicing(args)), do: unquote(body)
|
||||
end
|
||||
|
||||
[meta, impl]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,9 @@ defrecord Range, [:first, :last] do
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
Reduces the range based on the type of the first argument.
|
||||
"""
|
||||
def reduce(first, range, acc, fun)
|
||||
|
||||
@doc """
|
||||
@@ -17,6 +20,7 @@ defimpl Enumerable, for: Range do
|
||||
def reduce(Range[first: 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
|
||||
end
|
||||
@@ -61,10 +65,8 @@ defimpl Range.Iterator, for: Number do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Range do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
|
||||
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+328
-299
@@ -1,164 +1,185 @@
|
||||
defmodule Record do
|
||||
@moduledoc %B"""
|
||||
Functions to define Elixir records
|
||||
@moduledoc %S"""
|
||||
Convenience functions for working with Records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements and the first
|
||||
element is a module. One creates a record by calling `defrecord` or
|
||||
`defrecordp` which are documented in `Kernel`.
|
||||
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:
|
||||
|
||||
## Examples
|
||||
iex> record = { User, "josé", 25 }
|
||||
iex> is_record(record, User)
|
||||
true
|
||||
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
However, manually constructing tuples can be quite error prone.
|
||||
In case 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
|
||||
lose semantic value.
|
||||
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
and other functions to read and set the values in the
|
||||
record:
|
||||
This module solves these problems by allowing us to name each
|
||||
element and encapsulate the generation and manipulation of
|
||||
such tuples. Much of the functionality provided by this module happens
|
||||
at compilation time, meaning they don't add any runtime overhead while
|
||||
considerably improving the quality of our code.
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
For these reasons, Records are frequently used in Elixir and
|
||||
are also very useful when combined with Protocols. This module
|
||||
provides different mechanisms for working with records and we
|
||||
are going to explore them in the following sections.
|
||||
|
||||
# Update multiple attributes at once:
|
||||
file_info.update(atime: now(), accesses: 1)
|
||||
## defrecordp
|
||||
|
||||
# Obtain the keywords representation of a record:
|
||||
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
|
||||
The simplest way of working with records is via `defrecordp`:
|
||||
|
||||
|
||||
A record is simply a tuple where the first element is the record
|
||||
module name. We can get the record raw representation as follow:
|
||||
|
||||
inspect FileInfo.new, raw: true
|
||||
#=> { FileInfo, nil, nil }
|
||||
|
||||
Besides defining readers and writers for each attribute, Elixir also
|
||||
defines an `update_#{attribute}` function to update the value. Such
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one. For example, every time the file
|
||||
is accessed, the accesses counter can be incremented with:
|
||||
|
||||
file_info.update_accesses(fn(old) -> old + 1 end)
|
||||
|
||||
Which can be also written as:
|
||||
|
||||
file_info.update_accesses(&1 + 1)
|
||||
|
||||
## Access syntax
|
||||
|
||||
Records in Elixir can be expanded at compilation time to provide
|
||||
pattern matching and faster operations. For example, the clause
|
||||
below will only match if a `FileInfo` is given and the number of
|
||||
accesses is zero:
|
||||
|
||||
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
|
||||
|
||||
The clause above will expand to:
|
||||
|
||||
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
|
||||
|
||||
The downside of using such syntax is that, every time the record
|
||||
changes, your code now needs to be recompiled (which is usually
|
||||
not a concern since Elixir build tools by default recompiles the
|
||||
whole project whenever there is a change).
|
||||
|
||||
Finally, keep in mind that Elixir triggers some optimizations whenever
|
||||
the access syntax is used. For example:
|
||||
|
||||
def no_access?(FileInfo[] = file_info) do
|
||||
file_info.accesses == 0
|
||||
defmodule User do
|
||||
defrecordp :user, name: "José", age: 25
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
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.
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
Let's see some examples:
|
||||
|
||||
# To create records
|
||||
user() #=> { :user, "José", 25 }
|
||||
user(age: 26) #=> { :user, "José", 26 }
|
||||
|
||||
By using the `user` macro, we no longer need to explicitly create
|
||||
a tuple with all elements. It also allows us to create and modify
|
||||
values by name:
|
||||
|
||||
# Create a new record
|
||||
sample_user = user()
|
||||
|
||||
# And now change its age to 26
|
||||
user(sample_user, age: 26)
|
||||
|
||||
Since `user` is a macro, all the work happens at compilation time.
|
||||
This means all operations, like changing the `age` above, work
|
||||
as a simple tuple operation at runtime:
|
||||
|
||||
# This update operation...
|
||||
user(sample_user, age: 26)
|
||||
|
||||
# Literally translates to this one:
|
||||
set_elem(sample_user, 2, 26)
|
||||
|
||||
For this reason, the following operation is not allowed as all
|
||||
values need to be explicit:
|
||||
|
||||
new_values = [age: 26]
|
||||
user(sample_user, new_values)
|
||||
|
||||
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.
|
||||
|
||||
## defrecord
|
||||
|
||||
By using `defrecord`, a developer can make a Record definition
|
||||
available everywhere within Elixir code. Let's see an example:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
Notice that, unlike `defrecordp`, `defrecord` expects an
|
||||
alias as the first argument. This is because `defrecord` is going
|
||||
to create a module named `User` with all the relevant metadata.
|
||||
This module can then be imported and we can manipulate the user
|
||||
as with `defrecordp`:
|
||||
|
||||
Record.import User, as: :user
|
||||
user() #=> User[name: "José", age: 25]
|
||||
user(age: 26) #=> User[name: "José", age: 26]
|
||||
|
||||
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`:
|
||||
|
||||
inspect user(), raw: true
|
||||
{ User, "José", 25 }
|
||||
|
||||
Since working with external records is common, Elixir allows
|
||||
developers to skip importing the record altogether in favor
|
||||
of a `Module[args]` syntax:
|
||||
|
||||
# Skipping a field uses its default value
|
||||
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.
|
||||
|
||||
Before we sum up the differences between `defrecord` and
|
||||
`defrecordp`, there is one last functionality introduced by
|
||||
`defrecord` that we need to discuss.
|
||||
|
||||
## 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.
|
||||
|
||||
However, there are some situations where we want to set or update
|
||||
fields dynamically. `defrecord` (and `defrecord` only) supports
|
||||
it out of the box:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
opts = [name: "Hello"]
|
||||
user = User.new(opts)
|
||||
#=> User[name: "Hello", age: 25]
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/3` docs. Now
|
||||
let's discuss the usefulness of combining records with protocols.
|
||||
|
||||
## Protocols
|
||||
|
||||
Developers can extend existing protocols by creating their own
|
||||
records and implementing the desired protocols. For instance,
|
||||
imagine that you have created a new representation for storing
|
||||
date and time, represented by the year, the week of the year
|
||||
and the week day:
|
||||
|
||||
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 `String.Chars` protocol for
|
||||
our record:
|
||||
|
||||
defimpl String.Chars, for: WeekDate do
|
||||
def to_string(WeekDate[year: year, week: week, week_day: day]) do
|
||||
"#{year}-W#{week}-#{day}"
|
||||
end
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
Now we can explicitly convert our WeekDate:
|
||||
|
||||
## Runtime introspection
|
||||
to_string WeekDate[year: 2013, week: 26, week_day: 4]
|
||||
"2013-W26-4"
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
about the given record:
|
||||
|
||||
FileInfo.__record__(:name)
|
||||
#=> FileInfo
|
||||
|
||||
FileInfo.__record__(:fields)
|
||||
#=> [atime: nil, accesses: 0]
|
||||
|
||||
In order to quickly access the index of a field, one can use
|
||||
the `__index__` function:
|
||||
|
||||
FileInfo.__index__(:atime)
|
||||
#=> 0
|
||||
|
||||
FileInfo.__index__(:unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At the compile time, one can access following information about the record
|
||||
from within the record module:
|
||||
|
||||
* `@record_fields` — a keyword list of record fields with defaults
|
||||
* `@record_types` — a keyword list of record fields with types
|
||||
|
||||
defrecord Foo, bar: nil do
|
||||
record_type bar: nil | integer
|
||||
IO.inspect @record_fields
|
||||
IO.inspect @record_types
|
||||
end
|
||||
|
||||
prints out
|
||||
|
||||
[bar: nil]
|
||||
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
|
||||
|
||||
where the last line is a quoted representation of
|
||||
|
||||
[bar: nil | integer]
|
||||
|
||||
## Documentation
|
||||
|
||||
By default records are not documented and have `@moduledoc` set to false.
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
Those types can be passed at the moment the record is defined:
|
||||
|
||||
defrecord User,
|
||||
name: "" :: string,
|
||||
age: 0 :: integer
|
||||
|
||||
All the fields without a specified type are assumed to have type `term`.
|
||||
|
||||
Assuming the `User` record defined above, it could be used in typespecs
|
||||
as follow:
|
||||
|
||||
@spec handle_user(User.t) :: boolean()
|
||||
|
||||
If the developer wants to define their own types to be used with the
|
||||
record, Elixir allows a more lengthy definition with the help of the
|
||||
`record_type` macro:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
## Importing records
|
||||
|
||||
It is also possible to import a public record (a record, defined using
|
||||
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
|
||||
|
||||
Record.import Config, as: :config
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
A protocol can be implemented for any record defined via `defrecord`.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
|
||||
@doc """
|
||||
Extract record information from an Erlang file and
|
||||
return the fields as a list of tuples.
|
||||
@@ -173,94 +194,75 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for records definition. It defines a module
|
||||
with the given `name` and the fields specified in `values`.
|
||||
This is invoked directly by `Kernel.defrecord`, so check it
|
||||
for more information and documentation.
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
{ fields, types } = record_split(values)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Elixir.Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types unquote(types)
|
||||
|
||||
# Reassign fields to inner scope to
|
||||
# avoid conflicts in nested records
|
||||
fields = unquoted_fields
|
||||
|
||||
Elixir.Record.deffunctions(fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Elixir.Record.deftypes(fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_split(fields) when is_list(fields) do
|
||||
record_split(fields, [], [])
|
||||
end
|
||||
|
||||
defp record_split(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp record_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
|
||||
record_split t, [{ field, default }|defaults], [{ field, Macro.escape(type) }|types]
|
||||
end
|
||||
|
||||
defp record_split([other|t], defaults, types) do
|
||||
record_split t, [other|defaults], types
|
||||
end
|
||||
|
||||
defp record_split([], defaults, types) do
|
||||
{ :lists.reverse(defaults), types }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import public record definition as a set of private macros,
|
||||
Imports a public record definition as a set of private macros,
|
||||
as defined by `Kernel.defrecordp/2`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name),
|
||||
unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
module = unquote(module)
|
||||
|
||||
fields = if module == __MODULE__ do
|
||||
@record_fields
|
||||
else
|
||||
module.__record__(:fields)
|
||||
end
|
||||
|
||||
Record.defmacros(unquote(name), fields, __ENV__, module)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for private records definition. It defines
|
||||
a set of macros with the given `name` and the fields specified
|
||||
in `values`. This is invoked directly by `Kernel.defrecordp`,
|
||||
so check it for more information and documentation.
|
||||
"""
|
||||
def defrecordp(name, fields) when is_atom(name) and is_list(fields) do
|
||||
{ fields, types, def_type } = recordp_split(fields, [], [], false)
|
||||
type = :"#{name}_t"
|
||||
@doc false
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__)
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Elixir.Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def defrecordp(name, tag, fields) when is_atom(name) and is_atom(tag) and is_list(fields) do
|
||||
{ fields, types, def_type } = recordp_split(fields, [], [], false)
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
tag = tag || name
|
||||
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
|
||||
|
||||
if unquote(def_type) do
|
||||
@typep unquote(type)() :: { unquote(name), unquote_splicing(types) }
|
||||
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -297,7 +299,6 @@ defmodule Record do
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
indexes(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
@@ -355,25 +356,23 @@ defmodule Record do
|
||||
{ key, Macro.escape(value) }
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
|
||||
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
|
||||
Record.to_keywords(unquote(tag), unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, key) when is_atom(key) do
|
||||
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
|
||||
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -449,23 +448,48 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Dispatch the call to either update or to_list depending on the args given.
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_atom, fields, record) do
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
keywords = Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(keywords)
|
||||
end
|
||||
end
|
||||
|
||||
# Dispatch the call to either get, update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
if is_keyword(args) do
|
||||
update(atom, fields, record, args, caller)
|
||||
else
|
||||
to_list(atom, fields, record, args)
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
is_list(args) ->
|
||||
to_list(atom, fields, record, args)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the update macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# the access given by the keywords.
|
||||
@doc false
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
@@ -487,8 +511,7 @@ defmodule Record do
|
||||
# Implements the get macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# getting the value of a given field.
|
||||
@doc false
|
||||
def get(atom, fields, var, key) do
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
@@ -499,40 +522,47 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_atom, fields, record) do
|
||||
Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_list macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(atom, fields, record, keys) do
|
||||
Enum.map keys,
|
||||
unless is_list(fields) do
|
||||
raise ArgumentError, message: "expected arguments to be a compile time list"
|
||||
end
|
||||
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
list = Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(record))
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(var))
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(list)
|
||||
end
|
||||
end
|
||||
|
||||
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
|
||||
{ tuple, [] }
|
||||
end
|
||||
|
||||
defp cache_var(other) do
|
||||
quote do
|
||||
{ x, [x = unquote(other)] }
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
# Define __record__/1 and __record__/2 as reflection functions
|
||||
# that returns the record names and fields.
|
||||
# Define __record__/1, __record__/2, __record__/3 as reflection
|
||||
# functions that return the record names and fields.
|
||||
#
|
||||
# Note that fields are *not* keywords. They are in the same
|
||||
# order as given as parameter and reflects the order of the
|
||||
@@ -544,15 +574,31 @@ defmodule Record do
|
||||
#
|
||||
# FileInfo.__record__(:name) #=> FileInfo
|
||||
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
|
||||
# FileInfo.__record__(:index, :atime) #=> 1
|
||||
# FileInfo.__record__(:index, :mtime) #=> 2
|
||||
#
|
||||
defp reflection(values) do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -592,42 +638,6 @@ 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__)
|
||||
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: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __index__(_), do: nil
|
||||
|
||||
@doc false
|
||||
def __index__(key, _), do: __index__(key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -769,12 +779,12 @@ defmodule Record do
|
||||
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
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __index__(atom) :: non_neg_integer | nil
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -812,16 +822,35 @@ 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}"
|
||||
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)
|
||||
|
||||
@@ -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(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
|
||||
@@ -29,17 +29,18 @@ defmodule Record.Extractor do
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
records = retrieve_from_file(file)
|
||||
form = read_file(file)
|
||||
records = retrieve_records(form)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record)
|
||||
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
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_from_file(file) do
|
||||
lc { :attribute, _, :record, record } inlist read_file(file), do: record
|
||||
defp retrieve_records(form) do
|
||||
lc { :attribute, _, :record, record } inlist form, do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
@@ -50,19 +51,18 @@ 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
|
||||
|
||||
# Parse a tuple with name and fields and returns a
|
||||
# list of tuples where the first element is the field
|
||||
# and the second is its default value.
|
||||
defp parse_record({ _name, fields }) do
|
||||
defp parse_record({ _name, fields }, form) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
end
|
||||
{ :value, list, _ } = :erl_eval.expr(cons, [])
|
||||
list
|
||||
eval_record(cons, form)
|
||||
end
|
||||
|
||||
defp parse_field({ :typed_record_field, record_field, _type }) do
|
||||
@@ -76,4 +76,16 @@ defmodule Record.Extractor do
|
||||
defp parse_field({ :record_field, _, key, value }) do
|
||||
{ :tuple, 0, [key, value] }
|
||||
end
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ { :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ cons ] } ] } ]
|
||||
|
||||
{ :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
end
|
||||
end
|
||||
+110
-73
@@ -1,8 +1,8 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %B"""
|
||||
@moduledoc %S"""
|
||||
Regular expressions for Elixir built on top of the re module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
on 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!
|
||||
or using the special form with `%r`:
|
||||
@@ -10,32 +10,32 @@ defmodule Regex do
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
|
||||
# A regular expression with case insensitive options and handle unicode chars
|
||||
# A regular expression with case insensitive options and handling for unicode chars
|
||||
%r/foo/iu
|
||||
|
||||
The re module provides several options, the one 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 overwritten by setting `(*CR)` or `(*LF)` or
|
||||
* `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
|
||||
`(*CRLF)` or `(*ANY)` according to re documentation
|
||||
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
You need to use `\A` and `\z` to match the end or beginning of the string
|
||||
* extended (x) - whitespace characters are ignored except when escaped and
|
||||
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
Use `\A` and `\z` to match the end or beginning of the string
|
||||
* `extended` (x) - whitespace characters are ignored except when escaped and
|
||||
allow `#` to delimit comments
|
||||
* firstline (f) - forces the unanchored pattern to match before or at the first
|
||||
* `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) - invert the "greediness" of the regexp
|
||||
* `groups` (g) - compile with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
* anchored - not available, use `^` or `\A` instead
|
||||
* dollar_endonly - not available, use `\z` instead
|
||||
* no_auto_capture - not available, use `?:` instead
|
||||
* newline - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
* `anchored` - not available, use `^` or `\A` instead
|
||||
* `dollar_endonly` - not available, use `\z` instead
|
||||
* `no_auto_capture` - not available, use `?:` instead
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
Most of the functions in this module accept either a binary or a char list
|
||||
@@ -43,26 +43,45 @@ defmodule Regex do
|
||||
a binary, a char list will return a char list).
|
||||
"""
|
||||
|
||||
defrecordp :regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, term, term, term }
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, binary, binary, [atom] }
|
||||
|
||||
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.
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def compile(source, options // "") when is_binary(source) do
|
||||
options = to_binary(options)
|
||||
opts = translate_options(options)
|
||||
@spec compile(binary, binary | [term]) :: t
|
||||
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 ->
|
||||
compile(source, 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
|
||||
@@ -80,7 +99,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a boolean if there was a match or not.
|
||||
Returns a boolean indicating whether there was a match or not.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -95,9 +114,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 ocurred, 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,26 +149,23 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a list of tuples.
|
||||
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")
|
||||
[foo: "d"]
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.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
|
||||
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 """
|
||||
@@ -158,7 +176,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the regex source as binary.
|
||||
Returns the regex source as a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -184,11 +202,11 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns list of named groups in regex.
|
||||
Returns a list of named groups in the regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(%r/(?<foo>foo)/g)
|
||||
iex> Regex.groups(%r/(?<foo>bar)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
@@ -197,35 +215,56 @@ 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
|
||||
:nomatch -> []
|
||||
{ :match, results } -> flatten_result(results)
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Split the given target in the number of parts specified.
|
||||
If no ammount of parts is given, it defaults to :infinity.
|
||||
Split the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Examples
|
||||
iex> Regex.split(%r/-/, "a-b-c")
|
||||
["a","b","c"]
|
||||
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
["abc"]
|
||||
"""
|
||||
|
||||
def split(regex, string, options // [])
|
||||
@@ -240,16 +279,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"""
|
||||
Receives a regex, a binary and a replacement and returns a new
|
||||
@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
|
||||
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`
|
||||
if you want to replace just the first occurrence.
|
||||
|
||||
## Examples
|
||||
@@ -273,10 +318,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
|
||||
@@ -295,7 +340,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
|
||||
@@ -318,17 +363,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
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
defmodule Set do
|
||||
@moduledoc %S"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
It also provides functions that redirect to the
|
||||
underlying Set, allowing a developer to work with
|
||||
different Set implementations using one API.
|
||||
|
||||
To create a new set, use the `new` functions defined
|
||||
by each set type:
|
||||
|
||||
HashSet.new #=> creates an empty HashSet
|
||||
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback intersection(t, t) :: t
|
||||
defcallback member?(t, value) :: boolean
|
||||
defcallback put(t, value) :: t
|
||||
defcallback size(t) :: non_neg_integer
|
||||
defcallback subset?(t, t) :: boolean
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback union(t, t) :: t
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
if is_tuple(unquote(set)) do
|
||||
elem(unquote(set), 0)
|
||||
else
|
||||
unsupported_set(unquote(set))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes `value` from `set`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> HashSet.to_list
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> HashSet.to_list
|
||||
[1, 3]
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(set, value) do
|
||||
target(set).delete(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
[1]
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(set1, set2) do
|
||||
target(set1).difference(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` have no members in common.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
true
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(set1, set2) do
|
||||
target(set1).disjoint?(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty set of the same type as `set`.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` are equal.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
|
||||
true
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(set1, set2) do
|
||||
target(set1).equal?(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing only members in common between `set1` and `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(set1, set2) do
|
||||
target(set1).intersection(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set` contains `value`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
|
||||
true
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec member?(t, value) :: boolean
|
||||
def member?(set, value) do
|
||||
target(set).member?(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts `value` into `set` if it does not already contain it.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@spec put(t, value) :: t
|
||||
def put(set, value) do
|
||||
target(set).put(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `set`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.size(HashSet.new([1, 2, 3]))
|
||||
3
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(set) do
|
||||
target(set).size(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
true
|
||||
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
|
||||
false
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
target(set1).subset?(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts `set` to a list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: list
|
||||
def to_list(set) do
|
||||
target(set).to_list(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(set1, set2) do
|
||||
target(set1).union(set1, set2)
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
raise ArgumentError, message: "unsupported set: #{inspect set}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,452 @@
|
||||
defmodule Stream do
|
||||
@moduledoc """
|
||||
Module for creating and composing streams.
|
||||
|
||||
Streams are composable, lazy enumerables. Any enumerable that generates
|
||||
items one by one during enumeration is called a stream. For example,
|
||||
Elixir's `Range` is a stream:
|
||||
|
||||
iex> range = 1..5
|
||||
1..5
|
||||
iex> Enum.map range, &1 * 2
|
||||
[2,4,6,8,10]
|
||||
|
||||
In the example above, as we mapped over the range, the elements being
|
||||
enumerated were created one by one, during enumeration. The `Stream`
|
||||
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)
|
||||
[3,5,7]
|
||||
|
||||
Notice we started with a range and then we created a stream that is
|
||||
meant to multiply each item in the range by 2. At this point, no
|
||||
computation was done yet. Just when `Enum.map/2` is called we
|
||||
enumerate over each item in the range, multiplying it by 2 and adding 1.
|
||||
We say the functions in `Stream` are *lazy* and the functions in `Enum`
|
||||
are *eager*.
|
||||
|
||||
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))
|
||||
1
|
||||
2
|
||||
3
|
||||
2
|
||||
4
|
||||
6
|
||||
#=> [2,4,6]
|
||||
|
||||
Notice that we first printed each item in the list, then multiplied each
|
||||
element by 2 and finally printed each new value. In this example, the list
|
||||
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))
|
||||
Enum.to_list(stream)
|
||||
1
|
||||
2
|
||||
2
|
||||
4
|
||||
3
|
||||
6
|
||||
#=> [2,4,6]
|
||||
|
||||
Although the end result is the same, the order in which the items were
|
||||
printed changed! With streams, we print the first item and then print
|
||||
its double. In this example, the list was iterated just once!
|
||||
|
||||
That's what we meant when we first said that streams are composable,
|
||||
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
|
||||
effectively composing the streams and they are lazy. The computations
|
||||
are performed only when you call a function from the `Enum` module.
|
||||
|
||||
## Creating Streams
|
||||
|
||||
There are many functions in Elixir's standard library that return
|
||||
streams, some examples are:
|
||||
|
||||
* `IO.stream/1` - Streams input lines, one by one;
|
||||
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
|
||||
|
||||
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)
|
||||
[3,5,7]
|
||||
|
||||
By simply passing a list (which is an enumerable) as the first argument
|
||||
to `Stream.map/2`, we have automatically created a stream that will
|
||||
multiply the items in the list by 2 on enumeration.
|
||||
|
||||
This module also provides other functions for creating streams, such as
|
||||
`Stream.cycle/1`.
|
||||
"""
|
||||
|
||||
defrecord Lazy, [:enumerable, :fun, :acc]
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
def reduce(Lazy[] = lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fun, 0)
|
||||
end
|
||||
|
||||
def count(Lazy[] = lazy) do
|
||||
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, 0, true }), else: false
|
||||
end, 0)
|
||||
end
|
||||
|
||||
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, nesting) do
|
||||
do_reduce(enumerable, { acc, side }, f1.(fun, nesting), nesting + 1)
|
||||
end
|
||||
|
||||
defp do_reduce(enumerable, acc, fun, nesting) do
|
||||
Enumerable.reduce(enumerable, acc, fun) |> remove_nesting(nesting)
|
||||
catch
|
||||
{ :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)
|
||||
@type acc :: any
|
||||
@type element :: any
|
||||
@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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.cycle([1,2,3])
|
||||
iex> Enum.take(stream, 5)
|
||||
[1,2,3,1,2]
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: t
|
||||
def cycle(enumerable) do
|
||||
do_cycle(enumerable, &1, &2)
|
||||
end
|
||||
|
||||
defp do_cycle(enumerable, acc, fun) do
|
||||
acc = Enumerable.reduce(enumerable, acc, fun)
|
||||
do_cycle(enumerable, acc, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily drops the next `n` items from the enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.drop(1..10, 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[6,7,8,9,10]
|
||||
|
||||
"""
|
||||
@spec drop(Enumerable.t, non_neg_integer) :: t
|
||||
def drop(enumerable, n) when n >= 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1, _) ->
|
||||
fn
|
||||
_entry, { acc, n } when n > 0 ->
|
||||
{ acc, n - 1 }
|
||||
entry, { acc, n } ->
|
||||
{ f1.(entry, acc), n }
|
||||
end
|
||||
end,
|
||||
acc: n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily drops elements of the enumerable while the given
|
||||
function returns true.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.drop_while(1..10, &1 <= 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[6,7,8,9,10]
|
||||
|
||||
"""
|
||||
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def drop_while(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1, _) ->
|
||||
fn
|
||||
entry, { acc, true } ->
|
||||
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
|
||||
entry, { acc, false } ->
|
||||
{ f1.(entry, acc), false }
|
||||
end
|
||||
end,
|
||||
acc: true]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will filter elements according to
|
||||
the given function on enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2]
|
||||
|
||||
"""
|
||||
@spec filter(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def filter(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
if f.(entry), do: f1.(entry, acc), else: acc
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Emit a sequence of values, starting with `start_value`. Successive
|
||||
values are generated by calling `next_fun` on the previous value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
|
||||
[0,1,2,3,4]
|
||||
|
||||
"""
|
||||
|
||||
@spec iterate(element, (element -> element)) :: t
|
||||
def iterate(start_value, next_fun) do
|
||||
fn acc, fun ->
|
||||
do_iterate(start_value, next_fun, fun.(start_value, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_iterate(value, next_fun, acc, fun) do
|
||||
next = next_fun.(value)
|
||||
do_iterate(next, next_fun, fun.(next, acc), fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on
|
||||
enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2,4,6]
|
||||
|
||||
"""
|
||||
@spec map(Enumerable.t, (element -> any)) :: t
|
||||
def map(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
f1.(f.(entry), acc)
|
||||
end
|
||||
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) ->
|
||||
Enumerable.reduce(f.(entry), acc, f1)
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will reject elements according to
|
||||
the given function on enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,3]
|
||||
|
||||
"""
|
||||
@spec reject(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def reject(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
unless f.(entry), do: f1.(entry, acc), else: acc
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns a stream generated by calling `generator_fun` repeatedly.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
|
||||
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
|
||||
|
||||
"""
|
||||
@spec repeatedly((() -> element)) :: t
|
||||
def repeatedly(generator_fun)
|
||||
when is_function(generator_fun, 0) do
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.take(1..100, 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5]
|
||||
|
||||
iex> stream = Stream.cycle([1, 2, 3]) |> Stream.take(5)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,1,2]
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: t
|
||||
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: & &1]
|
||||
|
||||
def take(enumerable, n) when n > 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
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]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily takes elements of the enumerable while the given
|
||||
function returns true.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.take_while(1..100, &1 <= 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def take_while(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
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 """
|
||||
Creates a stream where each item in the enumerable will
|
||||
be accompanied by its index.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.with_index([1, 2, 3])
|
||||
iex> Enum.to_list(stream)
|
||||
[{1,0},{2,1},{3,2}]
|
||||
|
||||
"""
|
||||
@spec with_index(Enumerable.t) :: t
|
||||
def with_index(enumerable) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1, _) ->
|
||||
fn(entry, { acc, counter }) ->
|
||||
acc = f1.({ entry, counter }, acc)
|
||||
{ acc, counter + 1 }
|
||||
end
|
||||
end,
|
||||
acc: 0]
|
||||
end
|
||||
end
|
||||
+321
-65
@@ -1,27 +1,27 @@
|
||||
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/2`,
|
||||
`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
|
||||
|
||||
@@ -93,7 +93,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 +108,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
|
||||
|
||||
@@ -139,7 +139,7 @@ defmodule String do
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc """
|
||||
Splits a string on sub strings at each Unicode whitespace
|
||||
Splits a string on substrings at each Unicode whitespace
|
||||
occurrence with leading and trailing whitespace ignored.
|
||||
|
||||
## Examples
|
||||
@@ -156,12 +156,15 @@ defmodule String do
|
||||
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
|
||||
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
|
||||
|
||||
@@ -169,6 +172,8 @@ defmodule String do
|
||||
["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"]
|
||||
|
||||
iex> String.split("1,2 3,4", [" ", ","])
|
||||
["1", "2", "3", "4"]
|
||||
@@ -185,17 +190,25 @@ defmodule String do
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options // [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
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
|
||||
|
||||
@@ -211,7 +224,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
|
||||
|
||||
@@ -228,11 +241,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
|
||||
@@ -280,7 +293,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
|
||||
@@ -288,12 +301,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
|
||||
@@ -324,7 +341,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
|
||||
|
||||
@@ -364,14 +381,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 = String.length(subject)
|
||||
|
||||
cond do
|
||||
subject_len >= len ->
|
||||
subject
|
||||
subject_len < len ->
|
||||
fill = String.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
|
||||
|
||||
@@ -379,6 +463,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)
|
||||
@@ -390,7 +487,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
|
||||
@@ -405,11 +508,37 @@ 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(String.Unicode.next_grapheme(string), [])
|
||||
end
|
||||
|
||||
defp do_reverse({grapheme, rest}, acc) do
|
||||
do_reverse(String.Unicode.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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.duplicate("abc", 0)
|
||||
""
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
iex> String.duplicate("abc", 2)
|
||||
@@ -417,7 +546,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec duplicate(t, pos_integer) :: t
|
||||
def duplicate(subject, n) when is_integer(n) and n > 0 do
|
||||
def duplicate(subject, n) when is_integer(n) and n >= 0 do
|
||||
:binary.copy(subject, n)
|
||||
end
|
||||
|
||||
@@ -444,7 +573,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.
|
||||
@@ -458,7 +587,7 @@ defmodule String do
|
||||
@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
|
||||
@@ -489,7 +618,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
|
||||
@@ -513,7 +642,7 @@ defmodule String do
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
@doc %S"""
|
||||
Checks whether `str` is a valid codepoint.
|
||||
|
||||
Note that the empty string is considered invalid, as are
|
||||
@@ -585,7 +714,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
|
||||
|
||||
@@ -673,7 +802,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
|
||||
|
||||
@@ -697,11 +826,18 @@ 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) <= String.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
|
||||
def slice(string, start, len) when start < 0 and len >= 0 do
|
||||
real_start_pos = do_length(next_grapheme(string)) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
@@ -738,8 +874,8 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Converts a string to an integer. If successful, returns a
|
||||
tuple of form {integer, remainder of string}. If unsuccessful,
|
||||
returns :error.
|
||||
tuple of the form `{integer, remainder of string}`. If unsuccessful,
|
||||
returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -754,18 +890,17 @@ defmodule String do
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(binary_to_list(string))
|
||||
{result, remainder} = :string.to_integer(:binary.bin_to_list(string))
|
||||
case result do
|
||||
:error -> :error
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
_ -> {result, :binary.list_to_bin(remainder)}
|
||||
end
|
||||
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.
|
||||
tuple of the form `{float, remainder of string}`.
|
||||
If unsuccessful, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -782,22 +917,22 @@ defmodule String do
|
||||
@spec to_float(t) :: {integer, t} | :error
|
||||
|
||||
def to_float(string) do
|
||||
charlist = binary_to_list(string)
|
||||
charlist = :binary.bin_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
|
||||
_ -> {float(int_result), list_to_binary(int_remainder)}
|
||||
_ -> {:erlang.float(int_result), :binary.list_to_bin(int_remainder)}
|
||||
end
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
_ -> {result, :binary.list_to_bin(remainder)}
|
||||
end
|
||||
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
|
||||
|
||||
@@ -819,7 +954,7 @@ defmodule String do
|
||||
do_starts_with(string, prefix)
|
||||
end
|
||||
|
||||
defp do_starts_with(_, "") do
|
||||
defp do_starts_with(string, "") when is_binary(string) do
|
||||
true
|
||||
end
|
||||
|
||||
@@ -832,8 +967,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
|
||||
|
||||
@@ -855,7 +990,7 @@ defmodule String do
|
||||
do_ends_with(string, suffix)
|
||||
end
|
||||
|
||||
defp do_ends_with(_, "") do
|
||||
defp do_ends_with(string, "") when is_binary(string) do
|
||||
true
|
||||
end
|
||||
|
||||
@@ -871,7 +1006,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
|
||||
@@ -894,7 +1029,7 @@ defmodule String do
|
||||
do_contains(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, "") do
|
||||
defp do_contains(string, "") when is_binary(string) do
|
||||
true
|
||||
end
|
||||
|
||||
@@ -905,4 +1040,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,81 @@
|
||||
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)`.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Number, Atom, Record]
|
||||
|
||||
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, :utf8)
|
||||
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: Number do
|
||||
@doc """
|
||||
Simply converts the number (integer or a float) to a binary.
|
||||
"""
|
||||
def to_string(thing) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
+78
-60
@@ -53,7 +53,7 @@ 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 """
|
||||
@@ -79,12 +79,21 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current working directory or nil if one
|
||||
Changes the list of command-line arguments. Use it with caution,
|
||||
as it destory any previous argv information.
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(argv) do
|
||||
:elixir_code_server.cast({ :argv, argv })
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
@@ -98,15 +107,18 @@ defmodule System do
|
||||
|
||||
@doc """
|
||||
Returns the user home (platform independent).
|
||||
It returns nil if no user home is set.
|
||||
It 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.
|
||||
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,14 +129,16 @@ 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"""
|
||||
Returns a writable temporary directory.
|
||||
It searches for directories in the following order:
|
||||
|
||||
@@ -134,40 +148,41 @@ 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.
|
||||
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
|
||||
|
||||
defp write_tmp_dir(dir) do
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__index__ :type
|
||||
access_index = File.Stat.__index__ :access
|
||||
type_index = File.Stat.__record__(:index, :type)
|
||||
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
|
||||
@@ -193,14 +208,17 @@ 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 """
|
||||
@@ -213,39 +231,45 @@ 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.
|
||||
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 """
|
||||
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
|
||||
|
||||
@@ -255,15 +279,15 @@ defmodule System do
|
||||
|
||||
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 """
|
||||
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
|
||||
|
||||
@@ -289,8 +313,7 @@ defmodule System do
|
||||
|
||||
@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.
|
||||
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;
|
||||
@@ -304,30 +327,25 @@ 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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
defmodule Tuple do
|
||||
@moduledoc false
|
||||
end
|
||||
+77
-73
@@ -1,23 +1,58 @@
|
||||
defmodule URI do
|
||||
@on_load :preload_parsers
|
||||
|
||||
defrecord Info, [scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil,
|
||||
specifics: nil]
|
||||
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
Utilities for working with and creating URIs.
|
||||
"""
|
||||
|
||||
defrecord Info, [scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil]
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports [
|
||||
{ "ftp", 21 },
|
||||
{ "http", 80 },
|
||||
{ "https", 443 },
|
||||
{ "ldap", 389 },
|
||||
{ "sftp", 22 },
|
||||
{ "tftp", 69 },
|
||||
]
|
||||
|
||||
Enum.each @ports, fn { scheme, port } ->
|
||||
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
|
||||
def default_port(unquote(scheme)), do: unquote(port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes the scheme according to the spec by downcasing it.
|
||||
"""
|
||||
def normalize_scheme(nil), do: nil
|
||||
def normalize_scheme(scheme), do: String.downcase(scheme)
|
||||
|
||||
@doc """
|
||||
Returns the default port for a given scheme.
|
||||
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)
|
||||
Dict.get(dict, scheme)
|
||||
end
|
||||
|
||||
@doc """
|
||||
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)
|
||||
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
|
||||
end
|
||||
|
||||
@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))
|
||||
|
||||
@@ -26,7 +61,7 @@ defmodule URI do
|
||||
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
|
||||
@@ -63,13 +98,13 @@ 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 """
|
||||
Percent (URL) encodes a URI.
|
||||
"""
|
||||
def encode(s), do: bc <<c>> inbits s, do: <<percent(c) :: binary>>
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
@@ -115,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
|
||||
@@ -141,81 +176,50 @@ defmodule URI do
|
||||
if port, do: authority = authority <> ":" <> integer_to_binary(port)
|
||||
end
|
||||
|
||||
info = URI.Info[
|
||||
scheme: scheme && String.downcase(scheme), path: path, query: query,
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
URI.Info[
|
||||
scheme: scheme, path: path, query: query,
|
||||
fragment: fragment, authority: authority,
|
||||
userinfo: userinfo, host: host, port: port
|
||||
]
|
||||
|
||||
scheme_specific(scheme, info)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def scheme_module(scheme) do
|
||||
if scheme do
|
||||
module =
|
||||
try do
|
||||
Module.safe_concat(URI, String.upcase(scheme))
|
||||
rescue
|
||||
ArgumentError -> nil
|
||||
end
|
||||
|
||||
if module && Code.ensure_loaded?(module) do
|
||||
module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
if module = scheme_module(scheme) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
end
|
||||
end
|
||||
|
||||
defp default_port(info, module) do
|
||||
if info.port, do: info, else: info.port(module.default_port)
|
||||
end
|
||||
|
||||
# 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)
|
||||
{ userinfo, host && String.downcase(host), port }
|
||||
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
|
||||
|
||||
{ userinfo, host, port }
|
||||
end
|
||||
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
lc s inlist l do
|
||||
if size(s) > 0 do
|
||||
s
|
||||
else
|
||||
nil
|
||||
end
|
||||
if size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
|
||||
# Reference parsers so the parse/1 doesn't fail
|
||||
# on safe_concat.
|
||||
defp preload_parsers do
|
||||
parsers = [URI.FTP, URI.HTTP, URI.HTTPS, URI.LDAP, URI.SFTP, URI.TFTP]
|
||||
Enum.each parsers, Code.ensure_loaded(&1)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: URI.Info do
|
||||
def to_binary(uri) do
|
||||
result = ""
|
||||
defimpl String.Chars, for: URI.Info do
|
||||
def to_string(URI.Info[] = uri) do
|
||||
scheme = uri.scheme
|
||||
|
||||
if module = URI.scheme_module(uri.scheme) do
|
||||
if module.default_port == uri.port, do: uri = uri.port(nil)
|
||||
if scheme && (port = URI.default_port(scheme)) do
|
||||
if uri.port == port, do: uri = uri.port(nil)
|
||||
end
|
||||
|
||||
result = ""
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.FTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 21
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.HTTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 80
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.HTTPS do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 443
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
defmodule URI.LDAP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 389
|
||||
|
||||
# TODO: LDAP specific parsing.
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
defmodule URI.Parser do
|
||||
@moduledoc """
|
||||
Defines the behavior for each URI.Parser.
|
||||
Check URI.HTTP for a possible implementation.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@doc """
|
||||
Responsible for parsing extra URL information.
|
||||
"""
|
||||
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
|
||||
|
||||
@doc """
|
||||
Responsible for returning the default port.
|
||||
"""
|
||||
defcallback default_port() :: integer
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.SFTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 22
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.TFTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 69
|
||||
def parse(info), do: info
|
||||
end
|
||||
+17
-20
@@ -18,7 +18,7 @@ defmodule String.Unicode do
|
||||
|
||||
data_path = Path.join(__DIR__, "UnicodeData.txt")
|
||||
|
||||
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
{ codes, whitespace } = Enum.reduce File.stream!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
[ codepoint, _name, _category,
|
||||
_class, bidi, _decomposition,
|
||||
_numeric_1, _numeric_2, _numeric_3,
|
||||
@@ -39,7 +39,7 @@ defmodule String.Unicode do
|
||||
|
||||
special_path = Path.join(__DIR__, "SpecialCasing.txt")
|
||||
|
||||
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
|
||||
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
|
||||
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
|
||||
key = to_binary.(codepoint)
|
||||
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
|
||||
@@ -47,7 +47,7 @@ defmodule String.Unicode do
|
||||
|
||||
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
|
||||
|
||||
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
|
||||
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, "")
|
||||
@@ -57,11 +57,11 @@ defmodule String.Unicode do
|
||||
|
||||
# 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 +73,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 +118,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,11 +133,11 @@ 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
|
||||
|
||||
def split(""), do: ""
|
||||
def split(""), do: [""]
|
||||
|
||||
def split(string) when is_binary(string) do
|
||||
:lists.reverse do_split(string, "", [])
|
||||
@@ -145,11 +145,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,13 +154,14 @@ 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
|
||||
|
||||
@compile { :inline, add_buffer_to_acc: 2 }
|
||||
|
||||
defp add_buffer_to_acc("", acc), do: acc
|
||||
defp add_buffer_to_acc(buffer, acc), do: [buffer|acc]
|
||||
|
||||
# Graphemes
|
||||
|
||||
lc codepoints inlist seqs do
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
{require_otp_vsn,"(R16B).*"}.
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
-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/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -44,14 +43,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 +57,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,
|
||||
|
||||
@@ -137,48 +128,13 @@ 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, Scope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
}).
|
||||
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
|
||||
case ParseTree of
|
||||
[] -> { nil, Binding, NewScope };
|
||||
_ ->
|
||||
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, normalize_binding(Binding)),
|
||||
{Value, final_binding(NewBinding, NewScope#elixir_scope.vars), NewScope }
|
||||
end.
|
||||
|
||||
%% INTERNAL HELPERS
|
||||
|
||||
to_binary(Bin) when is_binary(Bin) -> Bin;
|
||||
to_binary(List) when is_list(List) -> list_to_binary(List).
|
||||
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
|
||||
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
|
||||
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case lists:member($@, atom_to_list(Var)) of
|
||||
true ->
|
||||
final_binding(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch({ Var, nil }, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
|
||||
final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
|
||||
|
||||
normalize_binding(Binding) ->
|
||||
Keyword = orddict:from_list(Binding),
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_@MODULE', nil, Keyword)
|
||||
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
|
||||
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
|
||||
end.
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
File = filename:absname(Relative),
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
string(unicode:characters_to_list(Bin), File).
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
|
||||
%% Compiles a file to the given path (directory).
|
||||
|
||||
@@ -84,17 +84,17 @@ eval_forms(Forms, Line, Vars, S) ->
|
||||
%% 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, S, Callback) ->
|
||||
module(Forms, File, Callback) ->
|
||||
Options = case get_opt(debug_info) of
|
||||
true -> [debug_info];
|
||||
_ -> []
|
||||
end,
|
||||
module(Forms, S#elixir_scope.file, Options, false, Callback).
|
||||
module(Forms, File, Options, 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),
|
||||
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,12 +102,12 @@ 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.
|
||||
@@ -166,13 +166,17 @@ module_form(Fun, Exprs, Line, File, Module, Vars) when
|
||||
end, { nil, Line }, Vars),
|
||||
|
||||
Args = [{ var, Line, '_@MODULE'}, Cons],
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { binary_to_list(File), 1 } },
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 2 }] },
|
||||
{ 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)] }
|
||||
] }
|
||||
].
|
||||
|
||||
@@ -195,7 +199,7 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
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,28 +210,31 @@ 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/binary/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/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/module/dispatch_tracker.ex">>
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
@@ -236,15 +243,15 @@ 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).
|
||||
|
||||
@@ -152,21 +152,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.
|
||||
|
||||
@@ -204,7 +204,7 @@ translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
|
||||
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
@@ -239,9 +239,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 +252,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 +309,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 +333,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 +387,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.
|
||||
|
||||
@@ -85,5 +85,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,7 +17,7 @@ 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 },
|
||||
@@ -45,9 +45,13 @@ import_function(Meta, Name, Arity, S) ->
|
||||
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) ->
|
||||
@@ -119,6 +123,28 @@ 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
|
||||
@@ -133,13 +159,7 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
{ 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) ->
|
||||
@@ -156,19 +176,22 @@ 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,
|
||||
|
||||
Fun = (Module == Receiver) andalso (Function /= nil) andalso
|
||||
(Function /= Tuple) andalso elixir_def_local:macro_for(Tuple, false, S),
|
||||
|
||||
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),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, 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) }
|
||||
elixir_tracker:record_local(Tuple, Module, Function),
|
||||
{ ok, Receiver, expand_macro_fun(Meta, Fun(), Receiver, Name, Args, Module, S) }
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
@@ -189,7 +212,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
apply(Fun, [SArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
@@ -200,10 +223,29 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(?line(Meta), Tree),
|
||||
S
|
||||
).
|
||||
Line = ?line(Meta),
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, Tree),
|
||||
S
|
||||
)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
|
||||
{ Module, Name, Arity, location(Line, S) }.
|
||||
|
||||
location(Line, S) ->
|
||||
[{ file, S#elixir_scope.file }, { line, Line }].
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -227,7 +269,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,
|
||||
@@ -262,17 +304,19 @@ not_an_import(Tuple, Context, Dict) ->
|
||||
true
|
||||
end.
|
||||
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
munge_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
|
||||
munge_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, [H|T], S) ->
|
||||
[H|munge_stacktrace(Info, T, S)];
|
||||
|
||||
munge_stacktrace(_, [], _) ->
|
||||
[].
|
||||
munge_stacktrace(Info, [], _) ->
|
||||
[Info].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
@@ -293,10 +337,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)]).
|
||||
@@ -353,8 +401,6 @@ in_erlang_functions() ->
|
||||
{ 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 },
|
||||
@@ -362,7 +408,7 @@ in_erlang_functions() ->
|
||||
{ byte_size, 1 },
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
% { float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
@@ -389,7 +435,6 @@ 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 },
|
||||
@@ -447,9 +492,6 @@ in_erlang_macros() ->
|
||||
{'and',2},
|
||||
{apply,2},
|
||||
{apply,3},
|
||||
{binding,0},
|
||||
{binding,1},
|
||||
{binding,2},
|
||||
{'case',2},
|
||||
{def,1},
|
||||
{def,2},
|
||||
@@ -464,8 +506,6 @@ in_erlang_macros() ->
|
||||
{defp,1},
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{function,1},
|
||||
{function,3},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
{'or',2},
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
% A bunch of helpers to help to deal with errors in Elixir source code.
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([syntax_error/3, syntax_error/4, inspect/1,
|
||||
-export([syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_no_function_scope/3, assert_function_scope/3,
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_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
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
|
||||
'Elixir.Inspect.Atom':inspect(Atom).
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
@@ -28,7 +33,17 @@ 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));
|
||||
|
||||
compile_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
compile_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
@@ -52,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 >>,
|
||||
@@ -60,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(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
@@ -75,11 +90,6 @@ deprecation(Meta, File, Message, Args) ->
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% output warning based on warnings going to stdout or stderr.
|
||||
output_warning(Warning) ->
|
||||
elixir_code_server:cast(register_warning),
|
||||
io:format(standard_error, Warning, []).
|
||||
|
||||
%% Ignore on bootstrap
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
|
||||
@@ -93,42 +103,72 @@ 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)]),
|
||||
output_warning(file_format(Line, File, Message));
|
||||
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}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
output_warning(file_format(Line, File, Message))
|
||||
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) }),
|
||||
output_warning(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 }),
|
||||
output_warning(file_format(Line, File, Message));
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
%% Default behavior
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
output_warning(file_format(Line, File, Message)).
|
||||
warn(file_format(Line, File, Message)).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
-spec handle_file_error(file:filename(), {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,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
@@ -167,15 +207,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))).
|
||||
@@ -201,4 +247,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,156 @@
|
||||
-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) ->
|
||||
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) ->
|
||||
capture(Meta, { '[]', Meta, List }, S);
|
||||
|
||||
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,43 +2,41 @@
|
||||
%% 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,
|
||||
|
||||
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.
|
||||
{ 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}.
|
||||
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
@@ -48,30 +46,27 @@ record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
|
||||
|
||||
%% 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 +77,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 +136,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 +153,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,58 +178,53 @@ 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},
|
||||
{'__ambiguousop__','*'},
|
||||
{'__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('[]',_) -> 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.
|
||||
|
||||
@@ -159,17 +159,14 @@ build_interpol(_Kind, _Line, _File, [], Output) ->
|
||||
Output;
|
||||
|
||||
build_interpol(s, _Line, _File, Buffer, Output) ->
|
||||
[unicode:characters_to_binary(lists:reverse(Buffer))|Output];
|
||||
[elixir_utils: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 }]}.
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case elixir_translator:forms(String, StartLine, File, []) of
|
||||
|
||||
@@ -45,7 +45,7 @@ translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
|
||||
{ elixir_utils:list_to_cons(?line(Meta), Exprs, Tail), ST };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
@@ -55,17 +55,13 @@ translate({ Left, Right }, S) ->
|
||||
translate(Args, S) when is_list(Args) ->
|
||||
translate({ '[]', [], Args }, S);
|
||||
|
||||
translate(Number, S) when is_integer(Number) ->
|
||||
{ { integer, 0, Number }, 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(Number, S) when is_float(Number) ->
|
||||
{ { float, 0, Number }, S };
|
||||
|
||||
translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
|
||||
translate(Other, S) ->
|
||||
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -98,9 +94,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, _ } ->
|
||||
@@ -113,16 +116,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
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
-module(elixir_macros).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
-import(elixir_scope, [umergec/2, umergea/2]).
|
||||
-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").
|
||||
@@ -33,11 +33,11 @@ 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),
|
||||
@@ -47,42 +47,6 @@ translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
||||
{ 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 -> syntax_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, [_] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
|
||||
|
||||
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 ->
|
||||
@@ -123,70 +87,21 @@ 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)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Binding
|
||||
|
||||
translate({ 'binding', Meta, [] }, S) ->
|
||||
translate({ 'binding', Meta, [false] }, S);
|
||||
|
||||
translate({ 'binding', Meta, [false] }, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Var) || { { Name, nil }, Var } <- S#elixir_scope.vars]), S };
|
||||
|
||||
translate({ 'binding', Meta, [true] }, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- S#elixir_scope.vars]), S };
|
||||
|
||||
translate({ 'binding', Meta, [List] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
|
||||
Line = ?line(Meta),
|
||||
Dict = lists:foldl(fun
|
||||
(Name, Acc) when is_atom(Name) ->
|
||||
case orddict:find({ Name, nil }, Vars) of
|
||||
{ ok, Var } ->
|
||||
orddict:store(Name, Var, Acc);
|
||||
error ->
|
||||
Acc
|
||||
end;
|
||||
(Name, _Acc) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/1 expects a list of atoms "
|
||||
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Name)])
|
||||
end, [], List),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Var) || { Name, Var } <- Dict]), S };
|
||||
|
||||
translate({ 'binding', Meta, [List, true] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
|
||||
Line = ?line(Meta),
|
||||
Dict = lists:foldl(fun
|
||||
(Tuple, Acc) when is_tuple(Tuple) ->
|
||||
case orddict:find(Tuple, Vars) of
|
||||
{ ok, Var } ->
|
||||
orddict:store(Tuple, Var, Acc);
|
||||
error ->
|
||||
Acc
|
||||
end;
|
||||
(Tuple, _Acc) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/2 expects a list of tuples "
|
||||
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Tuple)])
|
||||
end, [], List),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- Dict]), S };
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
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,
|
||||
@@ -196,27 +111,27 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, S) ->
|
||||
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = elixir_translator:translate_each(After, umergec(S, SC)),
|
||||
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
|
||||
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
|
||||
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) ->
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
|
||||
@@ -227,19 +142,19 @@ translate({'receive', Meta, [KV] }, S) ->
|
||||
_ ->
|
||||
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;
|
||||
|
||||
%% Definitions
|
||||
|
||||
translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
||||
{ TRef, _ } = translate_each(Ref, S),
|
||||
|
||||
Block = case lists:keyfind(do, 1, KV) of
|
||||
{ do, DoValue } -> DoValue;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "expected do: argument in defmodule")
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
|
||||
end,
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
@@ -275,6 +190,7 @@ translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
||||
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "~ts/4 is deprecated, please use ~ts/2 instead", [Kind, Kind]),
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TName, SN } = translate_each(Name, S),
|
||||
@@ -292,7 +208,11 @@ translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
|
||||
translate({ apply, Meta, Args }, S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS }.
|
||||
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
|
||||
|
||||
translate({ Name, Meta, Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -315,12 +235,12 @@ 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),
|
||||
@@ -351,11 +271,12 @@ translate_in(Meta, Left, Right, S) ->
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
|
||||
"compile time list or range on the right side when used in guard expressions")
|
||||
end
|
||||
end,
|
||||
|
||||
case InGuard of
|
||||
case TCache of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { Var, TExpr, SV }
|
||||
end.
|
||||
@@ -401,19 +322,6 @@ is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
to_var_value_tuple(Line, Name, Var) ->
|
||||
{ tuple, Line,
|
||||
[ { atom, Line, Name },
|
||||
{ var, Line, Var } ] }.
|
||||
|
||||
to_var_value_tuple(Line, Name, Kind, Var) ->
|
||||
{ tuple, Line,
|
||||
[ { tuple, Line, [
|
||||
{ atom, Line, Name },
|
||||
{ atom, Line, Kind }
|
||||
] },
|
||||
{ var, Line, Var } ] }.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(opaque) -> defopaque;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
@@ -27,8 +27,6 @@ scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
scope_for_eval(Module, Opts) ->
|
||||
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
|
||||
|
||||
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
@@ -45,7 +43,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],
|
||||
@@ -61,16 +59,16 @@ 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),
|
||||
|
||||
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),
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
|
||||
@@ -80,11 +78,11 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
|
||||
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
|
||||
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_tracker:ensure_all_imports_used(Line, File, Module),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
warn_unused_docs(Line, File, Module, All),
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
@@ -151,18 +149,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) ->
|
||||
@@ -246,8 +246,8 @@ spec_for_macro(Else) -> Else.
|
||||
|
||||
%% Loads the form into the code server.
|
||||
|
||||
load_form(Line, Forms, S) ->
|
||||
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
|
||||
load_form(Line, Forms, #elixir_scope{file=File} = S) ->
|
||||
elixir_compiler:module(Forms, File, 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),
|
||||
@@ -255,11 +255,15 @@ 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 ->
|
||||
PID ! { module_available, self(), File, Module, Binary },
|
||||
receive { PID, ack } -> ok end
|
||||
end;
|
||||
_ -> []
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Binary
|
||||
@@ -278,31 +282,43 @@ check_module_availability(Line, File, Module, Compiler) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
warn_unused_docs(_Line, _File, 'Elixir.Kernel', _All) -> ok;
|
||||
warn_unused_docs(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
warn_unused_docs(_Line, File, Module, All) ->
|
||||
ets:foldl(fun
|
||||
({ Tuple, Line, _, _, _ }, Acc) ->
|
||||
case lists:member(Tuple, All) of
|
||||
true -> Acc;
|
||||
false ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_doc, Tuple } })
|
||||
end
|
||||
end, ok, docs_table(Module)).
|
||||
|
||||
% 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,
|
||||
@@ -314,14 +330,14 @@ module_clause(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)] };
|
||||
{ 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 }] }.
|
||||
@@ -332,8 +348,9 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, S) ->
|
||||
Binding = binding_for_eval(Module, []),
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
Binding = elixir_scope:binding_for_eval([], Module),
|
||||
S = elixir_scope:vars_from_binding(RawS, Binding),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
@@ -351,14 +368,27 @@ eval_callbacks(Line, Module, Name, Args, S) ->
|
||||
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, munge_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
munge_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, [H|T]) ->
|
||||
[H|munge_stacktrace(Info, T)];
|
||||
|
||||
munge_stacktrace(Info, []) ->
|
||||
[Info].
|
||||
|
||||
% ERROR HANDLING
|
||||
|
||||
format_error({ invalid_doc, { Name, Arity } }) ->
|
||||
io_lib:format("docs provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
|
||||
+158
-102
@@ -1,7 +1,8 @@
|
||||
Nonterminals
|
||||
grammar expr_list
|
||||
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
expr container_expr block_expr no_parens_expr identifier_expr max_expr
|
||||
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
|
||||
@@ -10,33 +11,38 @@ Nonterminals
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens parens_call
|
||||
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
|
||||
stab stab_eol stab_expr stab_maybe_expr
|
||||
kw_eol kw_expr kw_comma kw_base
|
||||
matched_kw_expr matched_kw_comma matched_kw_base
|
||||
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
|
||||
container_comma_expr_base container_comma_expr container_arg container_args
|
||||
call_args_parens_comma_expr call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
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_bracket_identifier
|
||||
var list bracket_access 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
|
||||
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
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&' '...'
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
|
||||
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 ','.
|
||||
@@ -57,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)
|
||||
@@ -74,27 +80,37 @@ grammar -> '$empty' : [nil].
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> empty_paren : [nil].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
|
||||
paren_expr -> empty_paren : nil.
|
||||
paren_expr -> expr : '$1'.
|
||||
|
||||
expr -> empty_paren : nil.
|
||||
expr -> matched_expr : '$1'.
|
||||
expr -> no_parens_expr : '$1'.
|
||||
expr -> unmatched_expr : '$1'.
|
||||
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> fn_expr : '$1'.
|
||||
matched_expr -> identifier_expr : '$1'.
|
||||
|
||||
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').
|
||||
|
||||
unmatched_expr -> empty_paren op_expr : build_op(element(1, '$2'), nil, element(2, '$2')).
|
||||
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
|
||||
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_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
|
||||
op_expr -> match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> add_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op_eol expr : { '$1', '$2' }.
|
||||
@@ -113,6 +129,24 @@ op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
|
||||
no_parens_op_expr -> match_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> add_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> mult_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
|
||||
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
|
||||
@@ -131,29 +165,19 @@ 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' }.
|
||||
|
||||
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_punctuated_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
|
||||
block_expr -> dot_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
|
||||
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
fn_expr -> call_expr : '$1'.
|
||||
|
||||
call_expr -> dot_punctuated_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_op_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
|
||||
call_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
call_expr -> var : build_identifier('$1', nil).
|
||||
call_expr -> max_expr : '$1'.
|
||||
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_do_identifier : build_identifier('$1', nil).
|
||||
identifier_expr -> var : build_identifier('$1', nil).
|
||||
identifier_expr -> max_expr : '$1'.
|
||||
|
||||
max_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
max_expr -> bracket_expr : '$1'.
|
||||
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
|
||||
max_expr -> dot_ref : '$1'.
|
||||
max_expr -> dot_alias : '$1'.
|
||||
max_expr -> base_expr : '$1'.
|
||||
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
@@ -177,9 +201,8 @@ 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 -> bit_string : '$1'.
|
||||
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> '...' : { ?id('$1'), [{line,?line('$1')}], [] }.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
|
||||
|
||||
%% Blocks
|
||||
|
||||
@@ -208,12 +231,12 @@ stab_eol -> stab eol : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op_eol stab_maybe_expr :
|
||||
stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op_eol stab_maybe_expr :
|
||||
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> call_args_parens_not_one when_op matched_expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
@@ -324,7 +347,7 @@ dot_op -> '.' eol : '$1'.
|
||||
dot_identifier -> identifier : '$1'.
|
||||
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
|
||||
|
||||
dot_ref -> matched_expr dot_op aliases : build_dot_ref('$2', '$1', '$3').
|
||||
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
|
||||
|
||||
dot_op_identifier -> op_identifier : '$1'.
|
||||
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').
|
||||
@@ -338,87 +361,105 @@ 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
|
||||
|
||||
% Function calls
|
||||
% Function calls with no parentheses
|
||||
|
||||
matched_comma_expr -> matched_expr : ['$1'].
|
||||
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
|
||||
call_args_no_parens_expr -> matched_expr : '$1'.
|
||||
call_args_no_parens_expr -> empty_paren : nil.
|
||||
call_args_no_parens_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
|
||||
|
||||
call_args_no_parens_strict -> call_args_no_parens : '$1'.
|
||||
call_args_no_parens_strict -> empty_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_comma_expr -> matched_expr ',' call_args_no_parens_expr : ['$3', '$1'].
|
||||
call_args_no_parens_comma_expr -> call_args_no_parens_comma_expr ',' call_args_no_parens_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
call_args_no_parens_all -> call_args_no_parens_one : '$1'.
|
||||
call_args_no_parens_all -> call_args_no_parens_many : '$1'.
|
||||
|
||||
call_args_parens_not_one -> empty_paren : [].
|
||||
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
|
||||
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
|
||||
call_args_no_parens_one -> call_args_no_parens_kw : ['$1'].
|
||||
call_args_no_parens_one -> matched_expr : ['$1'].
|
||||
call_args_no_parens_one -> no_parens_expr : ['$1'].
|
||||
|
||||
base_comma_expr -> paren_expr ',' : ['$1'].
|
||||
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$1'].
|
||||
call_args_no_parens_many -> matched_expr ',' call_args_no_parens_kw : ['$1', '$3'].
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : lists:reverse(['$3'|'$1']).
|
||||
|
||||
comma_expr -> paren_expr : ['$1'].
|
||||
comma_expr -> kw_base : ['$1'].
|
||||
comma_expr -> base_comma_expr : '$1'.
|
||||
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
|
||||
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
|
||||
call_args_no_parens_many_strict -> call_args_no_parens_many : '$1'.
|
||||
call_args_no_parens_many_strict -> empty_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_many_strict -> open_paren call_args_no_parens_kw close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_many_strict -> open_paren call_args_no_parens_many close_paren : throw_no_parens_strict('$1').
|
||||
|
||||
optional_comma_expr -> paren_expr : '$1'.
|
||||
optional_comma_expr -> paren_expr ',' : '$1'.
|
||||
stab_parens_many -> empty_paren : [].
|
||||
stab_parens_many -> open_paren call_args_no_parens_kw close_paren : ['$2'].
|
||||
stab_parens_many -> open_paren call_args_no_parens_many close_paren : '$2'.
|
||||
|
||||
call_args -> comma_expr : lists:reverse('$1').
|
||||
% Containers and function calls with parentheses
|
||||
|
||||
container_expr -> empty_paren : nil.
|
||||
container_expr -> matched_expr : '$1'.
|
||||
container_expr -> unmatched_expr : '$1'.
|
||||
container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
|
||||
|
||||
container_comma_expr_base -> container_expr ',' : ['$1'].
|
||||
container_comma_expr_base -> container_comma_expr_base container_expr ',' : ['$2'|'$1'].
|
||||
|
||||
container_comma_expr -> container_expr : ['$1'].
|
||||
container_comma_expr -> kw : ['$1'].
|
||||
container_comma_expr -> container_comma_expr_base : '$1'.
|
||||
container_comma_expr -> container_comma_expr_base container_expr : ['$2'|'$1'].
|
||||
container_comma_expr -> container_comma_expr_base kw : ['$2'|'$1'].
|
||||
|
||||
container_arg -> container_expr : '$1'.
|
||||
container_arg -> container_expr ',' : '$1'.
|
||||
container_args -> container_comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens_comma_expr -> container_expr : ['$1'].
|
||||
call_args_parens_comma_expr -> call_args_parens_comma_expr ',' container_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_parens -> empty_paren : [].
|
||||
call_args_parens -> open_paren call_args close_paren : '$2'.
|
||||
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
|
||||
call_args_parens -> open_paren kw close_paren : ['$2'].
|
||||
call_args_parens -> open_paren call_args_parens_comma_expr close_paren : lists:reverse('$2').
|
||||
call_args_parens -> open_paren call_args_parens_comma_expr ',' kw close_paren : lists:reverse(['$4'|'$2']).
|
||||
|
||||
% KV
|
||||
|
||||
kw_eol -> kw_identifier : '$1'.
|
||||
kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
|
||||
|
||||
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
|
||||
kw_comma -> kw_expr ',' : ['$1'].
|
||||
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
|
||||
kw -> kw_expr : ['$1'].
|
||||
kw -> kw_comma : lists:reverse('$1').
|
||||
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
kw_base -> kw_expr : ['$1'].
|
||||
kw_base -> kw_comma : lists:reverse('$1').
|
||||
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
|
||||
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
|
||||
matched_kw_comma -> matched_kw_expr : ['$1'].
|
||||
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
|
||||
matched_kw_base -> matched_kw_comma : '$1'.
|
||||
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 optional_comma_expr close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket kw_base close_bracket : { '$2', ?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_base close_bracket : '$2'.
|
||||
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
|
||||
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
|
||||
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'].
|
||||
|
||||
% Tuple
|
||||
|
||||
tuple -> open_curly '}' : build_tuple('$1', []).
|
||||
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
tuple -> open_curly container_arg close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly container_expr ',' container_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
|
||||
% Bitstrings
|
||||
|
||||
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
|
||||
bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
|
||||
bit_string -> open_bit container_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
|
||||
|
||||
Erlang code.
|
||||
|
||||
@@ -435,6 +476,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] }] };
|
||||
|
||||
@@ -452,19 +499,18 @@ 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 };
|
||||
build_block([Expr]) when not is_list(Expr) -> Expr;
|
||||
build_block([Expr]) -> Expr;
|
||||
build_block(Exprs) -> { '__block__', [], Exprs }.
|
||||
|
||||
%% Dots
|
||||
|
||||
build_dot_ref(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
|
||||
build_dot_alias(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
|
||||
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
|
||||
|
||||
build_dot_ref(Dot, Other, { 'aliases', _, Right }) ->
|
||||
build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
|
||||
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
|
||||
|
||||
build_dot(Dot, Left, Right) ->
|
||||
@@ -486,15 +532,15 @@ build_identifier({ '.', Meta, _ } = Dot, Args) ->
|
||||
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
|
||||
{ fn, [{line,Line}], Args };
|
||||
|
||||
build_identifier({ op_identifier, Line, Identifier }, Args) ->
|
||||
{ '__ambiguousop__', [{line,Line}], [{ Identifier, [{line,Line}], nil }|Args] };
|
||||
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
|
||||
{ Identifier, [{ambiguous_op,nil},{line,Line}], [Arg] };
|
||||
|
||||
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.
|
||||
@@ -519,10 +565,10 @@ build_sigil({ sigil, Line, Sigil, 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_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, Args}] }.
|
||||
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) ->
|
||||
@@ -561,7 +607,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 }];
|
||||
{ _, _ } ->
|
||||
@@ -574,6 +620,16 @@ throw(Line, Error, Token) ->
|
||||
throw({ error, { Line, ?MODULE, [Error, Token] }}).
|
||||
|
||||
throw_no_parens_strict(Token) ->
|
||||
throw(?line(Token), "invalid comma inside parenthesis. If you are making a "
|
||||
throw(?line(Token), "unexpected parenthesis. If you are making a "
|
||||
"function call, do not insert spaces in between the function name and the "
|
||||
"opening parentheses. Syntax error before: ", ")").
|
||||
"opening parentheses. Syntax error before: ", "'('").
|
||||
|
||||
throw_no_parens_many_strict(Token) ->
|
||||
Line =
|
||||
case lists:keyfind(line, 1, element(2, Token)) of
|
||||
{ line, L } -> L;
|
||||
false -> 0
|
||||
end,
|
||||
|
||||
throw(Line, "unexpected comma. Parentheses are required to solve ambiguity "
|
||||
"in nested calls. Syntax error before: ", "','").
|
||||
|
||||
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
elixir_fn:fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
@@ -41,7 +41,7 @@ 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) ->
|
||||
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) when is_integer(Pos) ->
|
||||
case lists:keyfind(Pos, 1, DefAcc) of
|
||||
false ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/5]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -9,7 +9,7 @@ linify(Line, Exprs) when is_integer(Line) ->
|
||||
|
||||
do_linify(Line, { Left, Meta, Right }) ->
|
||||
NewMeta = case ?line(Meta) of
|
||||
0 -> lists:keystore(line, 1, Meta, { line, Line });
|
||||
0 -> keystore(line, Meta, Line);
|
||||
_ -> Meta
|
||||
end,
|
||||
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
|
||||
@@ -24,10 +24,10 @@ do_linify(_, Else) -> Else.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
@@ -35,11 +35,32 @@ unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
unquote(_File, _Line, 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) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Meta, _Left, _Right, _Args) ->
|
||||
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
|
||||
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(_File, _Line, 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) ->
|
||||
{ { '.', 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)]).
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
|
||||
Rest ++ Left ++ Right;
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) ->
|
||||
[H|T] = lists:reverse(Rest ++ Left),
|
||||
lists:reverse([{ '|', [], [H, Right] }|T]).
|
||||
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
@@ -50,7 +71,7 @@ escape(Expr, Unquote) ->
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=Unquote,
|
||||
mark=false
|
||||
escape=true
|
||||
}, nil).
|
||||
|
||||
erl_escape(Expr, Unquote, S) ->
|
||||
@@ -92,12 +113,12 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
|
||||
%% Context mark
|
||||
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
|
||||
|
||||
%% Aliases
|
||||
@@ -111,7 +132,7 @@ do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=t
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
|
||||
AliasMeta = keystore(alias, Meta, Annotation),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
|
||||
|
||||
%% Vars
|
||||
@@ -129,31 +150,33 @@ 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_fa('&', 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({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
end,
|
||||
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso
|
||||
case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, S) of
|
||||
false -> do_quote_tuple(Tuple, Q, S);
|
||||
false ->
|
||||
AmbMeta =
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
_ -> Meta
|
||||
end,
|
||||
do_quote_tuple({ Name, AmbMeta, ArgsOrAtom }, Q, S);
|
||||
Receiver ->
|
||||
ImportMeta = lists:keystore(import, 1,
|
||||
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
{ import, Receiver }),
|
||||
ImportMeta = keystore(import,
|
||||
keystore(context, Meta, Q#elixir_quote.context),
|
||||
Receiver),
|
||||
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
|
||||
end;
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, Q, S) ->
|
||||
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
|
||||
do_quote_tuple(Tuple, Q, S);
|
||||
|
||||
%% Literals
|
||||
@@ -168,12 +191,22 @@ do_quote({ Left, Right }, Q, S) ->
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
|
||||
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 }.
|
||||
@@ -181,16 +214,16 @@ do_quote(Other, Q, _) ->
|
||||
%% Quote helpers
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
All = [?line(Meta), 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 }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
|
||||
case (keyfind(import_fa, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
false -> Meta;
|
||||
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
|
||||
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
|
||||
end,
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
|
||||
@@ -202,15 +235,39 @@ do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
meta(Meta, #elixir_quote{line=keep}) ->
|
||||
Meta;
|
||||
meta(Meta, #elixir_quote{line=nil}) ->
|
||||
lists:keydelete(line, 1, Meta);
|
||||
keydelete(line, Meta);
|
||||
meta(Meta, #elixir_quote{line=Line}) ->
|
||||
lists:keystore(line, 1, Meta, { 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) ->
|
||||
lists:keydelete(Key, 1, Meta).
|
||||
keystore(Key, Meta, Value) ->
|
||||
lists:keystore(Key, 1, Meta, { Key, Value }).
|
||||
|
||||
%% Quote splicing
|
||||
|
||||
do_splice([{ '|', _, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
{ TT, TQ } = do_splice(T, Q, S, [], []),
|
||||
{ do_splice_join(do_splice_join(TT, Left), Right), TQ#elixir_quote{unquoted=true} };
|
||||
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
%% Process the remaining entries on the list.
|
||||
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
|
||||
%% 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_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
serialize/1, deserialize/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1,
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
vars_from_binding/2, binding_for_eval/2, binding_from_vars/2,
|
||||
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -80,23 +81,29 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases, Vars }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases}.
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
list_vars=Vars}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
vars=Vars,list_vars=ListVars} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
|
||||
list_vars(ListVars, Vars) }.
|
||||
|
||||
list_vars(nil, Vars) -> [K || { K, _ } <- Vars];
|
||||
list_vars(Other, _) -> Other.
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
serialize(S) ->
|
||||
elixir_tree_helpers:elixir_to_erl(
|
||||
elixir_utils:elixir_to_erl(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
|
||||
@@ -134,8 +141,8 @@ deserialize_with_vars({ File, Functions, Requires, Macros,
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the second
|
||||
% with their variables merged.
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
|
||||
umergev(S1, S2) ->
|
||||
V1 = S1#elixir_scope.vars,
|
||||
@@ -147,11 +154,22 @@ umergev(S1, S2) ->
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
}.
|
||||
|
||||
% Receives two scopes and return the later scope
|
||||
% keeping the variables from the first (counters,
|
||||
% imports and everything else are passed forward).
|
||||
%% Receives two scopes and return the first scope with
|
||||
%% counters and flags from the later.
|
||||
|
||||
umergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
|
||||
umergea(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=S1#elixir_scope.vars,
|
||||
temp_vars=S1#elixir_scope.temp_vars,
|
||||
@@ -183,3 +201,40 @@ var_merger(_Var, K1, K2) ->
|
||||
var_number([$@|T], _Acc) -> var_number(T, []);
|
||||
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
|
||||
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
|
||||
|
||||
%% Setup the vars in scope from binding
|
||||
|
||||
vars_from_binding(Scope, Binding) ->
|
||||
Scope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
}.
|
||||
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
|
||||
binding_for_eval(Binding, Module) ->
|
||||
Keyword = orddict:from_list(Binding),
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_@MODULE', Module, Keyword)
|
||||
end.
|
||||
|
||||
binding_from_vars(#elixir_scope{vars=Vars}, Binding) ->
|
||||
binding_from_vars(Binding, [], Binding, Vars).
|
||||
|
||||
binding_from_vars([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case lists:member($@, atom_to_list(Var)) of
|
||||
true ->
|
||||
binding_from_vars(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch({ Var, nil }, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
binding_from_vars(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
|
||||
binding_from_vars([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
|
||||
|
||||
+133
-111
@@ -15,13 +15,11 @@
|
||||
T1 == $[, T2 == $]
|
||||
).
|
||||
|
||||
%% New ops table
|
||||
|
||||
-define(at_op(T),
|
||||
T == $@).
|
||||
|
||||
-define(unary_op(T),
|
||||
% T == $&;
|
||||
T == $&;
|
||||
T == $!;
|
||||
T == $^).
|
||||
|
||||
@@ -259,7 +257,7 @@ tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $& ->
|
||||
?match_op(T); ?tail_op(T) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
@@ -271,9 +269,13 @@ tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
|
||||
tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, [Part], Rest } when is_binary(Part) ->
|
||||
Atom = unsafe_to_atom(Part, Scope),
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
case unsafe_to_atom(Part, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)])
|
||||
end;
|
||||
Error ->
|
||||
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
|
||||
end;
|
||||
@@ -298,18 +300,28 @@ tokenize([$'|T], Line, Scope, Tokens) ->
|
||||
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = case unescape_tokens(Parts) of
|
||||
[Part] when is_list(Part) or is_binary(Part) -> { atom, Line, unsafe_to_atom(Part, Scope) };
|
||||
Unescaped -> { atom, Line, Unescaped }
|
||||
SafeAtom = case unescape_tokens(Parts) of
|
||||
[Part] when is_list(Part) or is_binary(Part) -> unsafe_to_atom(Part, Line, Scope);
|
||||
Unescaped -> Unescaped
|
||||
end,
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
case SafeAtom of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize(Rest, NewLine, Scope, [{ atom, Line, Atom }|Tokens])
|
||||
end;
|
||||
Error ->
|
||||
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_new_atom(T) ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_atom_start(T) ->
|
||||
{ Rest, Part } = tokenize_atom([T|String], []),
|
||||
case unsafe_to_atom(Part, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens])
|
||||
end;
|
||||
|
||||
% Atom identifiers/operators
|
||||
|
||||
@@ -339,7 +351,7 @@ tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $&; T == $. ->
|
||||
?match_op(T); ?tail_op(T); T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -364,10 +376,6 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
% ## &
|
||||
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
|
||||
|
||||
% ## Three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
@@ -429,6 +437,11 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
|
||||
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
|
||||
%% Handle &1 and friends with special precedence.
|
||||
tokenize([$&,D|Rest], Line, Scope, Tokens) when ?is_digit(D) ->
|
||||
tokenize([D|Rest], Line, Scope, [{ '&', Line }|Tokens]);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
@@ -463,28 +476,28 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
{ Rest, Alias } = tokenize_identifier(String, []),
|
||||
Atom = unsafe_to_atom(Alias, Scope),
|
||||
case Rest of
|
||||
[$:|T] when ?is_space(hd(T)) ->
|
||||
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
|
||||
case unsafe_to_atom(Alias, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
case Rest of
|
||||
[$:|T] when ?is_space(hd(T)) ->
|
||||
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
|
||||
end
|
||||
end;
|
||||
|
||||
% Identifier
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
case tokenize_any_identifier(String, Line, [], Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
{ Rest, { Kind, _, Identifier } } ->
|
||||
case check_keyword(Line, Kind, Identifier, Tokens) of
|
||||
nomatch ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
{ ok, [Check|T] } ->
|
||||
handle_terminator(Rest, Line, Scope, Check, T);
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
end
|
||||
case tokenize_any_identifier(String, Line, Scope, Tokens) of
|
||||
{ keyword, Rest, Check, T } ->
|
||||
handle_terminator(Rest, Line, Scope, Check, T);
|
||||
{ identifier, Rest, Token } ->
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
{ error, _ } = Error ->
|
||||
Error
|
||||
end;
|
||||
|
||||
% Ambiguous unary/binary operators tokens
|
||||
@@ -494,7 +507,7 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
Identifier == identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{ dual_op, Line, list_to_atom([Sign]) }, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
@@ -530,13 +543,17 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
|
||||
case Parts of
|
||||
[Bin] when is_binary(Bin) ->
|
||||
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
case unsafe_to_atom(unescape_chars(Bin), Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens])
|
||||
end;
|
||||
_ ->
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
|
||||
end;
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H),Line,unescape_tokens(Parts) },
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
@@ -564,13 +581,17 @@ eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
|
||||
|
||||
unsafe_to_atom(Binary, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
|
||||
unsafe_to_atom(Part, Line, #scope{}) when
|
||||
is_binary(Part) andalso size(Part) > 255;
|
||||
is_list(Part) andalso length(Part) > 255 ->
|
||||
{ error, { Line, "atom length must be less than system limit", ":" } };
|
||||
unsafe_to_atom(Binary, _Line, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
|
||||
binary_to_existing_atom(Binary, utf8);
|
||||
unsafe_to_atom(Binary, #scope{}) when is_binary(Binary) ->
|
||||
unsafe_to_atom(Binary, _Line, #scope{}) when is_binary(Binary) ->
|
||||
binary_to_atom(Binary, utf8);
|
||||
unsafe_to_atom(List, #scope{existing_atoms_only=true}) when is_list(List) ->
|
||||
unsafe_to_atom(List, _Line, #scope{existing_atoms_only=true}) when is_list(List) ->
|
||||
list_to_existing_atom(List);
|
||||
unsafe_to_atom(List, #scope{}) when is_list(List) ->
|
||||
unsafe_to_atom(List, _Line, #scope{}) when is_list(List) ->
|
||||
list_to_atom(List).
|
||||
|
||||
collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
|
||||
@@ -586,7 +607,7 @@ extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Body, Rest } ->
|
||||
case elixir_interpolation:extract(Line, File, Interpol, Body, 0) of
|
||||
case elixir_interpolation:extract(Line + 1, File, Interpol, Body, 0) of
|
||||
{ _, Parts, [] } -> { Parts, Rest };
|
||||
Error -> interpolation_error(Error, " (for heredoc starting at line ~B)", [Line])
|
||||
end
|
||||
@@ -743,14 +764,14 @@ tokenize_comment([]) -> [].
|
||||
%% Atoms
|
||||
%% Handle atoms specially since they support @
|
||||
|
||||
tokenize_atom([H|T], Acc, Scope) when ?is_atom(H) ->
|
||||
tokenize_atom(T, [H|Acc], Scope);
|
||||
tokenize_atom([H|T], Acc) when ?is_atom(H) ->
|
||||
tokenize_atom(T, [H|Acc]);
|
||||
|
||||
tokenize_atom([H|T], Acc, Scope) when H == $?; H == $! ->
|
||||
{ T, unsafe_to_atom(lists:reverse([H|Acc]), Scope) };
|
||||
tokenize_atom([H|T], Acc) when H == $?; H == $! ->
|
||||
{ T, lists:reverse([H|Acc]) };
|
||||
|
||||
tokenize_atom(Rest, Acc, Scope) ->
|
||||
{ Rest, unsafe_to_atom(lists:reverse(Acc), Scope) }.
|
||||
tokenize_atom(Rest, Acc) ->
|
||||
{ Rest, lists:reverse(Acc) }.
|
||||
|
||||
%% Identifiers
|
||||
%% At this point, the validity of the first character was already verified.
|
||||
@@ -763,50 +784,43 @@ tokenize_identifier(Rest, Acc) ->
|
||||
|
||||
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
|
||||
tokenize_any_identifier(String, Line, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc),
|
||||
tokenize_any_identifier(String, Line, Scope, Tokens) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, []),
|
||||
|
||||
case Rest of
|
||||
[H|T] when H == $?; H == $! ->
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
tokenize_kw_or_call_identifier(punctuated_identifier, Line, Atom, T);
|
||||
_ ->
|
||||
Atom = unsafe_to_atom(Identifier, Scope),
|
||||
tokenize_kw_or_call_identifier(identifier, Line, Atom, Rest)
|
||||
{ AllIdentifier, AllRest } =
|
||||
case Rest of
|
||||
[H|T] when H == $?; H == $! -> { Identifier ++ [H], T };
|
||||
_ -> { Identifier, Rest }
|
||||
end,
|
||||
|
||||
case unsafe_to_atom(AllIdentifier, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize_kw_or_other(AllRest, identifier, Line, Atom, Tokens)
|
||||
end.
|
||||
|
||||
%% Tokenize kw or call identifier (paren | bracket | do)
|
||||
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, _Tokens) when ?is_space(H) ->
|
||||
{ identifier, [H|T], { kw_identifier, Line, Atom } };
|
||||
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|T]) when ?is_space(H) ->
|
||||
{ [H|T], { kw_identifier, Line, Atom } };
|
||||
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_new_atom(H) ->
|
||||
tokenize_kw_or_other([$:,H|_], _Kind, Line, Atom, _Tokens) when ?is_atom_start(H) ->
|
||||
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
|
||||
|
||||
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
{ Rest, check_call_identifier(Kind, Line, Atom, Rest) }.
|
||||
tokenize_kw_or_other(Rest, Kind, Line, Atom, Tokens) ->
|
||||
case check_keyword(Line, Atom, Tokens) of
|
||||
nomatch ->
|
||||
{ identifier, Rest, check_call_identifier(Kind, Line, Atom, Rest) };
|
||||
{ ok, [Check|T] } ->
|
||||
{ keyword, Rest, Check, T };
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
end.
|
||||
|
||||
%% Check if it is a call identifier (paren | bracket | do)
|
||||
|
||||
check_call_identifier(_Kind, Line, Atom, [$(|_]) -> { paren_identifier, Line, Atom };
|
||||
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> { bracket_identifier, Line, Atom };
|
||||
check_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
case next_is_block(Rest) of
|
||||
false -> { Kind, Line, Atom };
|
||||
BlockIdentifier -> { BlockIdentifier, Line, Atom }
|
||||
end.
|
||||
|
||||
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
|
||||
next_is_block(Tokens);
|
||||
|
||||
next_is_block([$d,$o,H|_]) when ?is_identifier(H); ?is_terminator(H) ->
|
||||
false;
|
||||
|
||||
next_is_block([$d,$o|_]) ->
|
||||
do_identifier;
|
||||
|
||||
next_is_block(_) ->
|
||||
false.
|
||||
check_call_identifier(Kind, Line, Atom, _Rest) -> { Kind, Line, Atom }.
|
||||
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
@@ -854,9 +868,10 @@ check_terminator({ E, _ }, [{ S, _ }|Terminators]) when
|
||||
|
||||
check_terminator({ E, Line }, [{ Start, StartLine }|_]) when
|
||||
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { Line, Message, [] } };
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("\"~ts\" starting at line ~B is missing terminator \"~ts\". "
|
||||
"Unexpected token: ", [Start, StartLine, End]),
|
||||
{ error, { Line, Message, atom_to_list(E) } };
|
||||
|
||||
check_terminator({ E, Line }, []) when
|
||||
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
|
||||
@@ -883,40 +898,47 @@ terminator('<<') -> '>>'.
|
||||
|
||||
%% Keywords checking
|
||||
|
||||
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
{ ok, [{ Identifier, Line, Atom }|Tokens] };
|
||||
check_keyword(_Line, _Atom, [{ '.', _ }|_]) ->
|
||||
nomatch;
|
||||
|
||||
check_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
check_keyword(Line, do, [{ Identifier, Line, Atom }|T]) when Identifier == identifier ->
|
||||
{ ok, [{ do, Line }, { do_identifier, Line, Atom }|T] };
|
||||
|
||||
check_keyword(Line, do, Tokens) ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ do, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
|
||||
check_keyword(Line, Atom, Tokens) ->
|
||||
case keyword(Atom) of
|
||||
do ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
false -> nomatch;
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
token -> { ok, [{ Atom, Line }|Tokens] };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
unary_op -> { ok, [{ unary_op, Line, Atom }|Tokens] };
|
||||
Kind -> { ok, add_token_with_nl({ Kind, Line, Atom }, Tokens) }
|
||||
end.
|
||||
|
||||
%% do is only valid after the end, true, false and nil keywords
|
||||
do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
case Atom of
|
||||
'end' -> true;
|
||||
nil -> true;
|
||||
true -> true;
|
||||
false -> true;
|
||||
_ -> keyword(Atom) == false
|
||||
end;
|
||||
|
||||
check_keyword(_, _, _, _) -> nomatch.
|
||||
|
||||
do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
is_boolean(keyword(Atom));
|
||||
do_keyword_valid(_) -> true.
|
||||
do_keyword_valid(_) ->
|
||||
true.
|
||||
|
||||
% Regular keywords
|
||||
keyword('fn') -> true;
|
||||
keyword('end') -> true;
|
||||
keyword('true') -> true;
|
||||
keyword('false') -> true;
|
||||
keyword('nil') -> true;
|
||||
|
||||
% Special handling for do
|
||||
keyword('do') -> do;
|
||||
keyword('fn') -> token;
|
||||
keyword('do') -> token;
|
||||
keyword('end') -> token;
|
||||
keyword('true') -> token;
|
||||
keyword('false') -> token;
|
||||
keyword('nil') -> token;
|
||||
|
||||
% Operators keywords
|
||||
keyword('not') -> unary_op;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
record_warn/4, record_definition/3,
|
||||
record_defaults/4,
|
||||
ensure_no_import_conflict/4, ensure_all_imports_used/3,
|
||||
record_defaults/4, record_alias/2,
|
||||
ensure_no_function_conflict/4, ensure_all_imports_used/3,
|
||||
warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
@@ -65,6 +65,15 @@ record_remote(Tuple, Receiver, Module, Function) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_alias(_Receiver, nil) -> false;
|
||||
record_alias(Receiver, Module) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_alias(Pid, Receiver)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
@@ -89,10 +98,10 @@ if_tracker(Module, Callback) ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, { import_conflict, Error })
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, { function_conflict, Error })
|
||||
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
|
||||
end),
|
||||
ok.
|
||||
@@ -118,11 +127,11 @@ warn_unused_local(File, Module, Private) ->
|
||||
end || Error <- Unused ]
|
||||
end).
|
||||
|
||||
format_error({import_conflict,{Receivers, Name, Arity}}) ->
|
||||
format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user