Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a613445f78 | ||
|
|
e4ea0a6e36 | ||
|
|
a03639fe1a | ||
|
|
e7b1242e98 | ||
|
|
d3d83b17f4 | ||
|
|
78eb14edae | ||
|
|
a7d931ce21 | ||
|
|
b1cd1ee7e9 | ||
|
|
1513ca7c14 | ||
|
|
016480bdcc | ||
|
|
857af06aff | ||
|
|
961099746e | ||
|
|
8247382967 | ||
|
|
9b0d972571 | ||
|
|
b19c389cee | ||
|
|
2243235e08 | ||
|
|
a72457112f | ||
|
|
3f3e86d7f4 | ||
|
|
15bf0fb16e | ||
|
|
5184edd432 | ||
|
|
fc9506e28e | ||
|
|
5c01be91ea | ||
|
|
fed406a2e6 | ||
|
|
26904197d5 | ||
|
|
f552d7620f | ||
|
|
ce55936b45 | ||
|
|
3167d78711 | ||
|
|
05eb42612b | ||
|
|
6720522707 | ||
|
|
0520b138f8 | ||
|
|
0ddb31992b | ||
|
|
4a1dfccbec | ||
|
|
c62a7e1001 | ||
|
|
2b8854c63e | ||
|
|
b535f0746e | ||
|
|
4b795a042b | ||
|
|
c80e52ee39 | ||
|
|
520415b013 | ||
|
|
27682320d4 | ||
|
|
98ade3b8fb | ||
|
|
263c76f5e5 | ||
|
|
bd984a545d | ||
|
|
115e022025 | ||
|
|
dc5fe48639 | ||
|
|
155b937327 | ||
|
|
6ef10d4b0d | ||
|
|
4f2c2ceb95 | ||
|
|
078522d014 | ||
|
|
c765a95b91 | ||
|
|
8836405bc5 | ||
|
|
dd2e0f6064 | ||
|
|
23982f1744 | ||
|
|
a6f04a2224 | ||
|
|
7291abb26f | ||
|
|
55cb8ab436 | ||
|
|
32cddf2738 | ||
|
|
7893852f2f | ||
|
|
f1b625ccd2 | ||
|
|
caa7d41e96 | ||
|
|
b96a714240 | ||
|
|
d39a52ae79 | ||
|
|
3e66197318 | ||
|
|
065b7f3f8f | ||
|
|
6004a3614f | ||
|
|
22627c44df | ||
|
|
6e959733dd | ||
|
|
7d2d352236 | ||
|
|
9d61e5886c | ||
|
|
37c8867d9f | ||
|
|
da7e830bf7 | ||
|
|
c78d3b3717 | ||
|
|
cafbd2c2d4 | ||
|
|
ae8def380a | ||
|
|
37cb1801b5 | ||
|
|
dd43e799c8 | ||
|
|
e93d4197ef | ||
|
|
d55a1703bd | ||
|
|
87268e9c78 | ||
|
|
f0ecfbfac9 | ||
|
|
854cab51bc | ||
|
|
ffdf9e9a35 | ||
|
|
6d74511e92 | ||
|
|
75a1373bbb | ||
|
|
d26e4b3dbc | ||
|
|
d636dc0ff0 | ||
|
|
ed0b161992 | ||
|
|
5e640336e0 | ||
|
|
99c7d84f9c | ||
|
|
893a52a95a | ||
|
|
a8d2e45d2f | ||
|
|
ecd3c19c64 | ||
|
|
7f9a2ed17b | ||
|
|
7951f73925 | ||
|
|
a79d965248 | ||
|
|
958550c5cd | ||
|
|
997a798994 | ||
|
|
c0547a15ce | ||
|
|
3918b889f1 | ||
|
|
d1fd5a3c2a | ||
|
|
a3aa1e8de5 | ||
|
|
7c379aa50a | ||
|
|
1fa29be448 | ||
|
|
b9a60b2668 | ||
|
|
ab9735533e | ||
|
|
6b2b928867 | ||
|
|
4b1ea18625 | ||
|
|
7cd98e149c | ||
|
|
036d016053 | ||
|
|
be2aedff98 | ||
|
|
e19d666a14 | ||
|
|
9840ac266b | ||
|
|
bcfc927455 | ||
|
|
014a2ec7a8 | ||
|
|
4846d42101 | ||
|
|
636da42f2a | ||
|
|
7c4516f1ec | ||
|
|
65cc78034d | ||
|
|
f97a384b82 | ||
|
|
7e856e1af0 | ||
|
|
893e333204 | ||
|
|
5e94a69693 | ||
|
|
5775d49c08 | ||
|
|
6397cedec6 | ||
|
|
3dd445bcce | ||
|
|
5e803487ef | ||
|
|
caeea8973c | ||
|
|
6656f7c843 | ||
|
|
e7a872c975 | ||
|
|
ea797815a0 | ||
|
|
453d8835bd | ||
|
|
2272dd1c46 | ||
|
|
41b5f4c826 | ||
|
|
ac0ac7f024 | ||
|
|
5ff3b760d9 | ||
|
|
fe97c4886a | ||
|
|
0b45b61de8 | ||
|
|
406edcb07d | ||
|
|
24c6623a2b | ||
|
|
5ffec637b0 | ||
|
|
bd1633c778 | ||
|
|
bcf9d814dd | ||
|
|
69a4138e8c | ||
|
|
0fa06a2183 | ||
|
|
6c813a140d | ||
|
|
dfc40703f0 | ||
|
|
e208237669 | ||
|
|
3ec014ff77 | ||
|
|
c19e3d0446 | ||
|
|
a459e35537 | ||
|
|
bbaf6b4092 | ||
|
|
99cdb412e3 | ||
|
|
4ce444ad76 | ||
|
|
f06f15184c | ||
|
|
da4565ee88 | ||
|
|
636275c064 | ||
|
|
6137d55d10 | ||
|
|
2fbc8682ca | ||
|
|
2075a9b07a | ||
|
|
abb261443b | ||
|
|
41b0ae4b37 | ||
|
|
4d793f6db1 | ||
|
|
b2d0355ef9 | ||
|
|
c82a8ee0fa | ||
|
|
7044f9c9da | ||
|
|
ec8327967a | ||
|
|
5aa5268c5b | ||
|
|
619f345423 | ||
|
|
99913d7018 | ||
|
|
fce97507de | ||
|
|
3f5e6deb07 | ||
|
|
3c9f486567 | ||
|
|
846048855f | ||
|
|
fed8796d18 | ||
|
|
9a6259d333 | ||
|
|
8c8e8b1193 | ||
|
|
dad7f7bfb7 | ||
|
|
3aaaf5e916 | ||
|
|
a6b81bffd1 | ||
|
|
a9d4cdb7d7 | ||
|
|
64c58f2a3f | ||
|
|
9e56691274 | ||
|
|
3f86e6d555 | ||
|
|
339082053e | ||
|
|
0cbf50c53a | ||
|
|
1d87a778ef | ||
|
|
3b5e06763d | ||
|
|
a7eab9b426 | ||
|
|
1211ed7f0f | ||
|
|
2a08277ef1 | ||
|
|
df94927e09 | ||
|
|
3cd6e54fe9 | ||
|
|
336b9cf126 | ||
|
|
d19f6beca0 | ||
|
|
da84a797b0 | ||
|
|
69df6ea9f9 | ||
|
|
b1fa7d1834 | ||
|
|
6a0244bd88 | ||
|
|
f37e5c6246 | ||
|
|
96a431945d | ||
|
|
fefad03595 | ||
|
|
f76aeba141 | ||
|
|
fbb06f2086 | ||
|
|
ffc91e3004 | ||
|
|
32cc99767b | ||
|
|
c3e7543e48 | ||
|
|
c7e86b0554 | ||
|
|
99ce9250b5 | ||
|
|
adf4075f36 | ||
|
|
3c4b6a636e | ||
|
|
239c41bee2 | ||
|
|
478f9bbb52 | ||
|
|
c3dd4006b0 | ||
|
|
e05448afd7 | ||
|
|
55bc80b4e5 | ||
|
|
ce1b12422d | ||
|
|
bbcc640760 | ||
|
|
e08bcbeef6 | ||
|
|
d757f31ccd | ||
|
|
9a0f8107bd | ||
|
|
d4609c6f4e | ||
|
|
f29c6afe3d | ||
|
|
df51fbe74e | ||
|
|
e6ff35c180 | ||
|
|
c20fe6f396 | ||
|
|
86e7069a0a | ||
|
|
334f3ce78a | ||
|
|
0da2b590ba | ||
|
|
1133581d9e | ||
|
|
d9a10847a6 | ||
|
|
d159e4f592 | ||
|
|
8530694c76 | ||
|
|
a916cf47b4 | ||
|
|
3e9b61855c | ||
|
|
9f862be4eb | ||
|
|
d2c5fa3ad4 | ||
|
|
a8d9ad50d9 | ||
|
|
ffb1863d41 | ||
|
|
156ead2c57 | ||
|
|
7ea8faeada | ||
|
|
82655b61c7 | ||
|
|
088753d901 | ||
|
|
7632c0b17e | ||
|
|
7d594a1953 | ||
|
|
042f5e9c14 | ||
|
|
3864b04ac2 | ||
|
|
5fac52bf12 | ||
|
|
63890ef0b7 | ||
|
|
0dcd5501f0 | ||
|
|
08e9faabe8 | ||
|
|
e5f9dbde1a | ||
|
|
f5b0b7160c | ||
|
|
d0ca92ad4e | ||
|
|
ca25049feb | ||
|
|
76a0a64725 | ||
|
|
c6e64aba18 | ||
|
|
231d65e879 | ||
|
|
91e8e21242 | ||
|
|
d3013aa1d1 | ||
|
|
2c98c0999d | ||
|
|
94f9d4e3f6 | ||
|
|
66476b31d1 | ||
|
|
7f84211bb3 | ||
|
|
474250e6f4 | ||
|
|
65e67cdc27 | ||
|
|
6b91e46702 | ||
|
|
c83f136ad4 | ||
|
|
59a9ddb7d5 | ||
|
|
1ef67c570e | ||
|
|
2403d0c4b9 | ||
|
|
a1ec5f1552 | ||
|
|
45485fba59 | ||
|
|
97ea102a79 | ||
|
|
6e8fb6c630 | ||
|
|
854942d3ee | ||
|
|
d99ec5addf | ||
|
|
3f946d4af0 | ||
|
|
9318f747f4 | ||
|
|
e216f18010 | ||
|
|
ed14ce4bc8 | ||
|
|
0cb936a600 | ||
|
|
328541be87 | ||
|
|
85b76843b0 | ||
|
|
f688899b95 | ||
|
|
e3fb2a5acf | ||
|
|
5a99fbfdcd | ||
|
|
c634a5d232 | ||
|
|
9a6ac4f76c | ||
|
|
6af7e0cc6b | ||
|
|
7ff3e1f833 | ||
|
|
d6d630316e | ||
|
|
4714b54222 | ||
|
|
4d51bd64f0 | ||
|
|
fc1945acff | ||
|
|
5c0ddbb063 | ||
|
|
880ae1116d | ||
|
|
68923112cc | ||
|
|
865608b5de | ||
|
|
26640e3940 | ||
|
|
59d6448609 | ||
|
|
31b4290797 | ||
|
|
39c33c7bb1 | ||
|
|
37054c8226 | ||
|
|
9751ca70c1 | ||
|
|
054ff457be | ||
|
|
b5fce07ec0 | ||
|
|
d722685c6a | ||
|
|
beaa746fcb | ||
|
|
c3d6a96dac | ||
|
|
d238d50579 | ||
|
|
4bde3c85ed | ||
|
|
9b0353a747 | ||
|
|
b6cb6ca88a | ||
|
|
33ab2895a0 | ||
|
|
245bc09d7b | ||
|
|
0abeef0724 | ||
|
|
771708764f | ||
|
|
08e6afe5a9 | ||
|
|
c4c0798c7e | ||
|
|
392a2fbef2 | ||
|
|
bcd4b97253 | ||
|
|
614dc52af4 | ||
|
|
8907edca3e | ||
|
|
a893d805aa | ||
|
|
0142587fd2 | ||
|
|
56a52f162b | ||
|
|
5ec2de4b76 | ||
|
|
d9b98a62c8 | ||
|
|
78a0abc08f | ||
|
|
0328d867a9 | ||
|
|
05b650f373 | ||
|
|
b3e8a3d8fe | ||
|
|
021dc541b8 | ||
|
|
af0970fd0b | ||
|
|
565fc8bffe | ||
|
|
a6317d525e | ||
|
|
e96963c4a4 | ||
|
|
a57be7379b | ||
|
|
79020c768f | ||
|
|
714b4440e3 | ||
|
|
1c505ad451 | ||
|
|
5cbe0baa2d | ||
|
|
98ed3045bb | ||
|
|
441c333939 | ||
|
|
bf356495a4 | ||
|
|
08211b7c52 | ||
|
|
216019c34f | ||
|
|
16555c974a | ||
|
|
869e90f2e5 | ||
|
|
80633a2ec6 | ||
|
|
b5d20d3135 | ||
|
|
911eb23ee2 | ||
|
|
4a3e741070 | ||
|
|
3e44e98299 | ||
|
|
b08289768a | ||
|
|
6e5c4feac0 | ||
|
|
be16bf487a | ||
|
|
647344ea9d | ||
|
|
6589d17527 | ||
|
|
2d870cf528 | ||
|
|
36fab9dafb | ||
|
|
d61a82565b | ||
|
|
a63e4da167 | ||
|
|
de1d47f9d5 | ||
|
|
1051b61605 | ||
|
|
f8f37c2b24 | ||
|
|
5e0f171655 | ||
|
|
521249edfe | ||
|
|
a67e445883 | ||
|
|
a0544478a6 | ||
|
|
056df8c5d3 | ||
|
|
6d6ec1ea11 | ||
|
|
020c5b0dd2 | ||
|
|
d8e374df56 | ||
|
|
37ff5a6fda | ||
|
|
ba5a9326ed | ||
|
|
5d885a97d3 | ||
|
|
eedd68e661 | ||
|
|
1aa589afa0 | ||
|
|
c0b9c2545a | ||
|
|
1bf4d053b9 | ||
|
|
b81bf4a839 | ||
|
|
06f1f3ccd7 | ||
|
|
abc5d6f954 | ||
|
|
69fe431dd9 | ||
|
|
ad2a6774b1 | ||
|
|
44a140490e | ||
|
|
1250c70d93 | ||
|
|
62b6ed1b4c | ||
|
|
66eb369b3d | ||
|
|
6e018c5226 | ||
|
|
95377a36a5 | ||
|
|
05c1630605 | ||
|
|
46fc45df9b | ||
|
|
93f71c8624 | ||
|
|
8154066d79 | ||
|
|
a0a06941ec | ||
|
|
b47bae9e4c | ||
|
|
79dde1e97e | ||
|
|
23cbf3e853 | ||
|
|
51529dab0d | ||
|
|
26aee950fd | ||
|
|
1d6fd3c4c8 | ||
|
|
feed5c13ff | ||
|
|
baf5fcf096 | ||
|
|
e18c1602ba | ||
|
|
32ae6d3424 | ||
|
|
02d0aa1d9a | ||
|
|
5ce473152f | ||
|
|
f38cf28cbd | ||
|
|
1385bde667 | ||
|
|
06f8a12bc8 | ||
|
|
dec997e3a4 | ||
|
|
fcba92606c | ||
|
|
609d66adce | ||
|
|
8de27bf9af | ||
|
|
9118b36069 | ||
|
|
3d9fa35680 | ||
|
|
fc30aaf68c | ||
|
|
b90b8d1385 | ||
|
|
57f1a1c29c | ||
|
|
930be9d99f | ||
|
|
b9d25cb2b7 | ||
|
|
0c6ee2d297 | ||
|
|
9867b8c296 | ||
|
|
dfee7d7443 | ||
|
|
63b93d5dc9 | ||
|
|
e40f0c1ec5 | ||
|
|
26aa75318b | ||
|
|
117667de0c | ||
|
|
da45fde90c | ||
|
|
4df1149ec5 | ||
|
|
16128651dc | ||
|
|
b97343faff | ||
|
|
7b41ff3c01 | ||
|
|
132ea1569e | ||
|
|
95a30f3227 | ||
|
|
10675504d5 | ||
|
|
54988ae535 | ||
|
|
03a11b45fd | ||
|
|
dda7a4daf7 | ||
|
|
dd9ba46641 | ||
|
|
6f26ff3ba0 | ||
|
|
0e8bac7e9e | ||
|
|
bef8caf8c6 | ||
|
|
a514bfa97c | ||
|
|
993c28a4de | ||
|
|
5e2557a3a9 | ||
|
|
3b8b127753 | ||
|
|
7bbb6806e3 | ||
|
|
7bae0029f2 | ||
|
|
65f7d56a8e | ||
|
|
315d5b5608 | ||
|
|
c6222a05da | ||
|
|
1bd8484146 | ||
|
|
18c4e2b810 | ||
|
|
6d2569ff9b | ||
|
|
ce39f23388 | ||
|
|
f09453440a | ||
|
|
5a9cb7970b | ||
|
|
78ce20742d | ||
|
|
45ad425793 | ||
|
|
3f73a73da1 | ||
|
|
1d2b4de7dd | ||
|
|
a3a136bb81 | ||
|
|
a93264be81 | ||
|
|
cbe82e0912 | ||
|
|
560510c71a | ||
|
|
975d7f8dfb | ||
|
|
ddb87a96ca | ||
|
|
c53314dfc4 | ||
|
|
fa3a7031f0 | ||
|
|
f9fe24f524 | ||
|
|
3b7cd15fbc | ||
|
|
90093bd4e2 | ||
|
|
ace80048f7 | ||
|
|
2b866abb6c | ||
|
|
74e244a129 | ||
|
|
a8ab6b5a2a | ||
|
|
5841f6849b | ||
|
|
d77ea82a27 | ||
|
|
7d1a439e29 | ||
|
|
4b65fa1601 | ||
|
|
38539faaf4 | ||
|
|
1019879bd0 | ||
|
|
c14ee51d15 | ||
|
|
a0787fb4bf | ||
|
|
901fedf266 | ||
|
|
c1c4d7571f | ||
|
|
2f981b914d | ||
|
|
4c6dbcf719 | ||
|
|
33367e4224 | ||
|
|
82e597e31e | ||
|
|
e4fd9fd4c1 | ||
|
|
66756e2686 | ||
|
|
b59888a6b7 | ||
|
|
0af35cd011 | ||
|
|
5c96708ce5 | ||
|
|
6bb284d776 | ||
|
|
e33c31e4ee | ||
|
|
5c58956812 | ||
|
|
88d674b0b3 | ||
|
|
44ec3705f3 | ||
|
|
23a625b8de | ||
|
|
83c257937d | ||
|
|
43f8448623 | ||
|
|
47931bb5dd | ||
|
|
3ddf8edc93 | ||
|
|
644dd0f4fb | ||
|
|
475bdea0bb | ||
|
|
6bf1d2b84d | ||
|
|
80ae2e9bf2 | ||
|
|
ee0643c17c | ||
|
|
16e18240c6 | ||
|
|
f484c9960f | ||
|
|
17cbf7bd98 | ||
|
|
3f7dda69fb | ||
|
|
35abb1d7ef | ||
|
|
ee9f77600b | ||
|
|
61727e5c5d | ||
|
|
f791bd117c | ||
|
|
55d1e3767e | ||
|
|
5a22ba8aa2 | ||
|
|
f5c08e9df3 | ||
|
|
034cb18af8 | ||
|
|
1080e290a0 | ||
|
|
6c0d43c1c7 | ||
|
|
0b77a541eb | ||
|
|
028421ffea | ||
|
|
cebbbd5102 | ||
|
|
7d1a7635e5 | ||
|
|
af76eff96a | ||
|
|
6619e3277e | ||
|
|
ebb867d361 | ||
|
|
77bde77d25 | ||
|
|
2e96d1de4d | ||
|
|
b5ab795cdc | ||
|
|
7f9e95e223 | ||
|
|
f134066de3 | ||
|
|
90a9be6cce | ||
|
|
f45dbea495 | ||
|
|
46c5c69211 | ||
|
|
4d1b22b69c | ||
|
|
f76347235b | ||
|
|
a13eaaf1c3 | ||
|
|
7f388d8bf7 | ||
|
|
01e848058a | ||
|
|
2e26d17d83 | ||
|
|
e2fd5c257b | ||
|
|
ae0a6cefd0 | ||
|
|
e9a91d1f7a | ||
|
|
c37101a52c | ||
|
|
aad2d97baf | ||
|
|
f1b2980e8e | ||
|
|
8a4b550a90 | ||
|
|
52f36a4a96 | ||
|
|
92ac9ab144 | ||
|
|
7bd288c7cb | ||
|
|
efb61518d7 | ||
|
|
414fc86a7d | ||
|
|
2eb8fc766a | ||
|
|
2889d7c998 | ||
|
|
4f54dc07f5 | ||
|
|
b71d14fc03 | ||
|
|
02bb4970dd | ||
|
|
4a996bea17 | ||
|
|
3e6e6ee99b | ||
|
|
9f60f46044 | ||
|
|
ea29be40a4 | ||
|
|
e793ee2c67 | ||
|
|
bf3dd3f265 | ||
|
|
c03ea65115 | ||
|
|
cd7879b1b9 | ||
|
|
160631b2d5 | ||
|
|
87d57a38a3 | ||
|
|
907920de44 | ||
|
|
53d34099f1 | ||
|
|
4c70f1b3cd | ||
|
|
d2dbef1611 | ||
|
|
70432ca7f2 | ||
|
|
0cab23e3e7 | ||
|
|
ed27611f48 | ||
|
|
fd29173d6d | ||
|
|
963efc4593 | ||
|
|
cfc21d04c6 | ||
|
|
507fda525b | ||
|
|
e3c6cc9b85 | ||
|
|
c78c8ddf08 | ||
|
|
8f34d58829 | ||
|
|
5b9c3e7aa6 | ||
|
|
c125636535 | ||
|
|
531bd8da0c | ||
|
|
a4d8fc2893 | ||
|
|
b96c3c52ae | ||
|
|
3f7b867740 | ||
|
|
f2e78d3d39 | ||
|
|
5d1d3f23e9 | ||
|
|
01cc9ed0ec | ||
|
|
4096b31cfe | ||
|
|
49a5b8f28c | ||
|
|
f49c351bb4 | ||
|
|
aa45edc82c | ||
|
|
2c36d8f625 | ||
|
|
d3238b8778 | ||
|
|
ec69bdfbbe | ||
|
|
3d66ab35b5 | ||
|
|
8d2034f07c | ||
|
|
b82b51a870 | ||
|
|
be5e20f7d4 | ||
|
|
26599e7812 | ||
|
|
52aa1fd068 | ||
|
|
728d08c3bd | ||
|
|
84cd94c2e5 | ||
|
|
b9ef56df27 | ||
|
|
1ffda62f2e | ||
|
|
0fad1883df | ||
|
|
d4eda432ec | ||
|
|
71831d654c | ||
|
|
13c334f70b | ||
|
|
875b4c7ecf | ||
|
|
bef41d7b55 | ||
|
|
6512fcb06e | ||
|
|
c2d1699248 | ||
|
|
e412ce6b22 | ||
|
|
e01f958b1a | ||
|
|
35d0edb3b1 | ||
|
|
ca456200cc | ||
|
|
fcc7fc4351 | ||
|
|
ca0250a9d7 | ||
|
|
f859c9d350 | ||
|
|
a576540159 | ||
|
|
a34f38ee40 | ||
|
|
de06764691 | ||
|
|
4d0d7e8f2a | ||
|
|
025c425514 | ||
|
|
48da7e8ed7 | ||
|
|
f8c0729f3b | ||
|
|
0c50664817 | ||
|
|
e7b329dbe2 | ||
|
|
5581c8255f | ||
|
|
7be936eb6d | ||
|
|
26e3ec644b | ||
|
|
29ea1df744 | ||
|
|
1334849ff7 | ||
|
|
dbf711b519 | ||
|
|
0b0bbdee5e | ||
|
|
7b9f2bd298 | ||
|
|
9016a18fa5 | ||
|
|
bd6f27a5d5 | ||
|
|
f9e74f944e | ||
|
|
3a98369842 | ||
|
|
a963269244 | ||
|
|
0463407cbf | ||
|
|
c59be9b4a8 | ||
|
|
8d6aa70acb | ||
|
|
4612877e78 | ||
|
|
c0fbea8e9b | ||
|
|
c3e50546ec | ||
|
|
b954d53585 | ||
|
|
2f848953df | ||
|
|
24a49295ef | ||
|
|
02edfed204 | ||
|
|
75fcdac740 | ||
|
|
538a29ef70 | ||
|
|
15ac6e947a | ||
|
|
01d5701419 | ||
|
|
0f91e24f31 | ||
|
|
7e9c5c97b2 | ||
|
|
a114702967 | ||
|
|
25bc992e76 | ||
|
|
196358525a | ||
|
|
f7cad1cffc | ||
|
|
fee5c5d762 | ||
|
|
dc0897ec03 | ||
|
|
369fd7506c | ||
|
|
c6ff84e2b3 | ||
|
|
0e47d8cbdc | ||
|
|
34da5f883b | ||
|
|
1c2d41974c | ||
|
|
4ad1ca375b | ||
|
|
27d958063a | ||
|
|
a8c89e3703 | ||
|
|
0872fdd733 | ||
|
|
e27707ec1a | ||
|
|
5a84acf607 | ||
|
|
5d00a9ff58 | ||
|
|
6024014482 | ||
|
|
01d7e2124c | ||
|
|
179b18a970 | ||
|
|
618f7822b5 | ||
|
|
866558710c | ||
|
|
81d4063fe9 | ||
|
|
2c5fa31ed7 | ||
|
|
238e7f4bc2 | ||
|
|
f10171bfd2 | ||
|
|
ecb23a4b42 | ||
|
|
ff825b89bc | ||
|
|
3788bf4a5e | ||
|
|
6e8bcd9245 | ||
|
|
2817b2ee90 | ||
|
|
affc44d020 | ||
|
|
b5e58473d9 | ||
|
|
30fa89e270 | ||
|
|
9084d2a4c4 | ||
|
|
592d7d9338 | ||
|
|
e811f0a538 | ||
|
|
9ff534c6d1 | ||
|
|
bc163abe1f | ||
|
|
6093aa0384 | ||
|
|
4cc92aaf8d | ||
|
|
e1e3bff3fb | ||
|
|
824168a163 | ||
|
|
07a2283845 | ||
|
|
ae6a51fc83 | ||
|
|
28ebdad467 | ||
|
|
98e488a52d | ||
|
|
0e39a552e6 | ||
|
|
88d915ffb6 | ||
|
|
18f127d69b | ||
|
|
ae0cad4ec3 | ||
|
|
22da62600e | ||
|
|
81aae93726 | ||
|
|
62eddd822d | ||
|
|
e7274fdb51 | ||
|
|
c10b5e7e4f | ||
|
|
b396696c5c | ||
|
|
6bf3ca1e89 | ||
|
|
6a24bc4ef3 | ||
|
|
24ca9c0af5 | ||
|
|
8a58722dd0 | ||
|
|
5844698f39 | ||
|
|
b55a917048 | ||
|
|
90ff71b0cc | ||
|
|
7dd0aabbe2 | ||
|
|
658d7c7913 | ||
|
|
1f491dfbe2 | ||
|
|
8ca4517563 | ||
|
|
4e8690cd8e | ||
|
|
8f57b4c9af | ||
|
|
19d402b7d5 | ||
|
|
cf6751a3c8 | ||
|
|
42a0719c8a | ||
|
|
4527c1e849 | ||
|
|
5557e620ff | ||
|
|
683153ef05 | ||
|
|
8a77e878ab | ||
|
|
a19a4b893d | ||
|
|
b8689da1f5 | ||
|
|
a05592fdd9 | ||
|
|
3147df88fa | ||
|
|
1946feb4e1 | ||
|
|
a0101bc056 | ||
|
|
129725a168 | ||
|
|
e93613bc05 | ||
|
|
7b51500a60 | ||
|
|
f6058afb26 | ||
|
|
67b5236874 | ||
|
|
1e18d8e08e | ||
|
|
9ebce19f36 | ||
|
|
fb82b0c679 | ||
|
|
9dc7f44049 | ||
|
|
5e2c6e14be | ||
|
|
e896e311c1 | ||
|
|
ced13c159f | ||
|
|
5fe1544ff3 | ||
|
|
96c7f68a62 | ||
|
|
d0dc9f5c2c | ||
|
|
8a130978b2 | ||
|
|
71e2252064 | ||
|
|
ddc9733cf2 | ||
|
|
9bcc4be3f5 | ||
|
|
f66206b4f3 | ||
|
|
28211b2714 | ||
|
|
93f1bb920b | ||
|
|
109c07e757 | ||
|
|
eb36c2b288 | ||
|
|
3537bbbbf4 | ||
|
|
cd51c80362 | ||
|
|
a1a576e9d1 | ||
|
|
1dc4bf0989 | ||
|
|
1812e652db | ||
|
|
7f73cb3128 | ||
|
|
0123edd8a3 | ||
|
|
87af330589 | ||
|
|
3ae634a13c | ||
|
|
de3bd8ec86 | ||
|
|
3fea472cc2 | ||
|
|
7cb18091d0 | ||
|
|
8994feb0a3 | ||
|
|
f4aed8e873 | ||
|
|
008ee62f8b | ||
|
|
d543650f70 | ||
|
|
febf5e46b6 | ||
|
|
7196543060 | ||
|
|
fade5452e1 | ||
|
|
9e46866851 | ||
|
|
1dd1f2b213 | ||
|
|
3137bfc614 | ||
|
|
4d3d61e173 | ||
|
|
2e66218f58 | ||
|
|
3a1fbde95d | ||
|
|
088978fa04 | ||
|
|
249f83e82b | ||
|
|
608f0a3dcb | ||
|
|
d8aa534e3a | ||
|
|
c2df5fa5ff | ||
|
|
fee8836366 | ||
|
|
35a43afb22 | ||
|
|
c359f212f8 | ||
|
|
0f2dd3f7d4 | ||
|
|
20942ee329 | ||
|
|
d2f76453d6 | ||
|
|
50d375e33b | ||
|
|
119d48e093 | ||
|
|
6960f856f4 | ||
|
|
eb11254369 | ||
|
|
f686b08e5e | ||
|
|
80bd148852 |
@@ -14,3 +14,4 @@ erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
+1
-3
@@ -6,6 +6,4 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
- R16B
|
||||
|
||||
+175
-3
@@ -1,3 +1,176 @@
|
||||
# v0.9.2 (2013-06-13)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
* [Mix] Automatically import git dependencies from Rebar
|
||||
* [Mix] Support for dependencies directly from the umbrella application
|
||||
* [Regex] Add `Regex.escape`
|
||||
* [String] Add `String.contains?`
|
||||
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
|
||||
|
||||
* 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
|
||||
* [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
|
||||
|
||||
* deprecations
|
||||
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
|
||||
* [Kernel] `super` no longer supports implicit arguments
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
|
||||
* enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
* [Kernel] Add `String.start_with?` and `String.end_with?`
|
||||
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
|
||||
|
||||
* bug fix
|
||||
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
|
||||
* [IEx] Enable shell customization via the `IEx.Options` module
|
||||
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
|
||||
* [Kernel] Unused functions with cyclic dependencies are now also warned as unused
|
||||
* [Mix] Fix a bug where `mix deps.get` was not retrieving nested dependencies
|
||||
* [Record] Fix a bug where nested records cannot be defined
|
||||
* [Record] Fix a bug where a record named Record cannot be defined
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
* [IEx] Add support for .iex files that are loaded during shell's boot process
|
||||
* [IEx] Add `import_file/1` helper
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
|
||||
* enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
|
||||
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
|
||||
* [ExUnit] Add `ExUnit.CaptureIO` for IO capturing during tests
|
||||
* [ExUnit] Consider load time on ExUnit time reports
|
||||
* [IEx] Support `ls` with colored output
|
||||
* [IEx] Add `#iex:break` to break incomplete expressions
|
||||
* [Kernel] Add `Enum.at`, `Enum.fetch` and `Enum.fetch!`
|
||||
* [Kernel] Add `String.to_integer` and `String.to_float`
|
||||
* [Kernel] Add `Dict.take`, `Dict.drop`, `Dict.split`, `Dict.pop` and `Dict.fetch!`
|
||||
* [Kernel] Many optimizations for code compilation
|
||||
* [Kernel] `in` can be used with right side expression outside guards
|
||||
* [Kernel] Add `Node.get_cookie/0` and `Node.set_cookie/2`
|
||||
* [Kernel] Add `__DIR__`
|
||||
* [Kernel] Expand macros and attributes on quote, import, alias and require
|
||||
* [Kernel] Improve warnings related to default arguments
|
||||
* [Keyword] Add `Keyword.delete_first/2`
|
||||
* [Mix] Add `local.rebar` to download a local copy of rebar, and change `deps.compile` to use it if needed
|
||||
* [Mix] Support umbrella applications
|
||||
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
|
||||
* [String] Add `String.valid?` and `String.valid_character?`
|
||||
|
||||
* bug fix
|
||||
* [ExUnit] Handle exit messages from in ExUnit
|
||||
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
|
||||
* [Kernel] Ensure we don't splice keyword args unecessarily
|
||||
* [Kernel] Private functions used by private macros no longer emit an unused warning
|
||||
* [Kernel] Ensure Elixir won't trip on empty receive blocks
|
||||
* [Kernel] `String.slice` now returns an empty string when out of range by 1
|
||||
* [Mix] Generate manifest files after compilation to avoid depending on directory timestamps and to remove unused .beam files
|
||||
* [Path] `Path.expand/2` now correctly expands `~` in the second argument
|
||||
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
|
||||
* [URI] Downcase host and scheme and URIs
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
|
||||
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
|
||||
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
|
||||
* [IO] `IO.getb` is deprecated in favor of `IO.getn`
|
||||
* [List] `List.member?/2` is deprecated in favor of `Enum.member?/2`
|
||||
* [Kernel] `var_context` in quote was deprecated in favor of `context`
|
||||
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Dict] `List.Dict` was moved to `ListDict`
|
||||
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
* [Mix] `mix escriptize` now receives arguments as binaries
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
* [ExUnit] Include suite run time on CLI results
|
||||
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
|
||||
* [File] Add `File.ls` and `File.ls!`
|
||||
* [IEx] Support `pwd` and `cd` helpers
|
||||
* [Kernel] Better error reporting for invalid bitstring generators
|
||||
* [Kernel] Improve meta-programming by allowing `unquote` on `def/2`, `defp/2`, `defmacro/2` and `defmacrop/2`
|
||||
* [Kernel] Add support to R16B new functions: `insert_elem/3` and `delete_elem/2`
|
||||
* [Kernel] Import conflicts are now lazily handled. If two modules import the same functions, it will fail only if the function is invoked
|
||||
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
|
||||
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
|
||||
|
||||
* bug fix
|
||||
* [Binary] inspect no longer escapes standalone hash `#`
|
||||
* [IEx] The `h` helper can now retrieve docs for special forms
|
||||
* [Kernel] Record optimizations were not being triggered in functions inside the record module
|
||||
* [Kernel] Aliases defined inside macros should be carried over
|
||||
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
|
||||
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
|
||||
|
||||
* deprecations
|
||||
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
|
||||
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
|
||||
|
||||
quote do
|
||||
quote do: unquote(x)
|
||||
end
|
||||
|
||||
should become:
|
||||
|
||||
quote do
|
||||
unquote(Macro.escape(x))
|
||||
end
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
|
||||
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
|
||||
* [Kernel] Better support for Unicode lists
|
||||
* [Kernel] Reduce variables footprint in `case`/`receive` clauses
|
||||
* [Kernel] Disable native compilation when on_load attributes is present to work around an Erlang bug
|
||||
* [Macro] `Macro.expand` also considers macros from the current `__ENV__` module
|
||||
* [Mix] Improve support for compilation of `.erl` files
|
||||
* [Mix] Add support for compilation of `.yrl` and `.xrl` files
|
||||
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
|
||||
* [Typespec] Better error reporting for invalid typespecs
|
||||
|
||||
* bug fix
|
||||
* [Mix] Allow Mix projects to be generated with just one letter
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
|
||||
|
||||
* deprecations
|
||||
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
|
||||
* [Kernel] Tidy up and clean `quote` API
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
* [Process] `Process.self` is deprecated in favor `Kernel.self`
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
|
||||
* enhancements
|
||||
@@ -6,7 +179,7 @@
|
||||
* [Binary] Support syntax shortcut for specifying size in bit syntax
|
||||
* [CLI] Support `--app` option to start an application and its dependencies
|
||||
* [Dict] Support `put_new` in `Dict` and `Keyword`
|
||||
* [Dict] Add `List.Dict` and a faster `HashDict` implementation
|
||||
* [Dict] Add `ListDict` and a faster `HashDict` implementation
|
||||
* [ExUnit] ExUnit now supports multiple runs in the same process
|
||||
* [ExUnit] Failures in ExUnit now shows a tailored stacktrace
|
||||
* [ExUnit] Introduce `ExUnit.ExpectationError` to provide better error messages
|
||||
@@ -33,7 +206,7 @@
|
||||
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
|
||||
|
||||
* deprecations
|
||||
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `List.Dict`
|
||||
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
|
||||
* [File] Deprecate path related functions in favor of the module `Path`
|
||||
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
|
||||
* [Mix] `Mix.Project.sources` is deprecated in favor of `Mix.Project.config_files`
|
||||
@@ -62,7 +235,6 @@
|
||||
|
||||
* deprecations
|
||||
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
|
||||
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
|
||||
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
# Contributing to Elixir
|
||||
|
||||
Please take a moment to review this document in order to make the contribution
|
||||
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).
|
||||
|
||||
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
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
|
||||
## Bug reports
|
||||
|
||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||
Good bug reports are extremely helpful - thank you!
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. **Use the GitHub issue search** — check if the issue has already been
|
||||
reported.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `master` or development branch in the repository.
|
||||
|
||||
3. **Isolate the problem** — ideally create a reduced test
|
||||
case.
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report. What is
|
||||
your environment? What steps will reproduce the issue? What version of Erlang
|
||||
and Elixir experience the problem? What would you expect to be the outcome?
|
||||
All these details will help people to fix any potential bugs.
|
||||
|
||||
Example:
|
||||
|
||||
> Short and descriptive example bug report title
|
||||
>
|
||||
> A summary of the issue and the environment in which it occurs. If suitable,
|
||||
> include the steps required to reproduce the bug.
|
||||
>
|
||||
> 1. This is the first step
|
||||
> 2. This is the second step
|
||||
> 3. Further steps, etc.
|
||||
>
|
||||
> `<url>` - a link to the reduced test case (e.g. a GitHub Gist)
|
||||
>
|
||||
> Any other information you want to share that is relevant to the issue being
|
||||
> reported. This might include the lines of code that you have identified as
|
||||
> causing the bug, and potential solutions (and your opinions on their
|
||||
> merits).
|
||||
|
||||
## 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.
|
||||
|
||||
## Contributing
|
||||
|
||||
We incentivate everyone to contribute to Elixir and help us tackle
|
||||
existing issues! To do so, there are a few things you need to know
|
||||
about the code. First, Elixir code is divided in applications inside
|
||||
the `lib` folder:
|
||||
|
||||
* `elixir` - Contains Elixir's kernel and stdlib
|
||||
|
||||
* `eex` - Template engine that allows you to embed Elixir
|
||||
|
||||
* `ex_unit` - Simple test framework that ships with Elixir
|
||||
|
||||
* `iex` — IEx, Elixir's interactive shell
|
||||
|
||||
* `mix` — Elixir's build tool
|
||||
|
||||
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`.
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree that your work will be made
|
||||
public forever and will be licensed under the license used by the project.
|
||||
|
||||
If you have any significant pull request in mind (e.g. implementing features,
|
||||
refactoring code, porting to a different language), **please ask first**
|
||||
otherwise you risk spending a lot of time working on something that the
|
||||
project's developers might not want to merge into the project.
|
||||
|
||||
Please adhere to the coding conventions in the project (indentation,
|
||||
accurate comments, etc.) and don't forget to add your own tests and
|
||||
documentation. When working with git, we recommend the following process
|
||||
in order to craft an excellent pull request:
|
||||
|
||||
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
|
||||
and configure the remotes:
|
||||
|
||||
```bash
|
||||
# Clone your fork of the repo into the current directory
|
||||
git clone https://github.com/<your-username>/elixir
|
||||
# Navigate to the newly cloned directory
|
||||
cd elixir
|
||||
# Assign the original repo to a remote called "upstream"
|
||||
git remote add upstream https://github.com/elixir-lang/elixir
|
||||
```
|
||||
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
```
|
||||
|
||||
3. Create a new topic branch (off of `master`) to contain your feature, change,
|
||||
or fix.
|
||||
|
||||
**IMPORTANT**: Making changes in `master` is discouraged. You should always
|
||||
keep your local `master` in sync with upstream `master` and make your
|
||||
changes in topic branches.
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
```
|
||||
|
||||
4. Commit your changes in logical chunks. Keep your commit messages organized,
|
||||
with a short description in the first line and more detailed information on
|
||||
the following lines. Feel free to use Git's
|
||||
[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:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
6. [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
|
||||
rebasing on master and resolving any conflicts.
|
||||
|
||||
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
|
||||
should always `git rebase` on `master` to bring your changes up to date when
|
||||
necessary.
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
git checkout <your-topic-branch>
|
||||
git rebase master
|
||||
```
|
||||
|
||||
We have saved some excellent pull requests we have received in the past in case
|
||||
you are looking for some examples:
|
||||
|
||||
* https://github.com/elixir-lang/elixir/pull/992
|
||||
* https://github.com/elixir-lang/elixir/pull/1041
|
||||
* https://github.com/elixir-lang/elixir/pull/1058
|
||||
* https://github.com/elixir-lang/elixir/pull/1059
|
||||
|
||||
Thank you for your contributions!
|
||||
@@ -11,12 +11,12 @@ INSTALL_PATH := /usr/local
|
||||
#==> Templates
|
||||
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
|
||||
$(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')"
|
||||
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
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
|
||||
|
||||
@@ -27,8 +27,8 @@ endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
|
||||
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
@@ -44,9 +44,9 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL)
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@ if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
@@ -54,6 +54,7 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
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
|
||||
|
||||
@@ -93,23 +94,29 @@ clean:
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
docs:
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../exdoc/bin/exdoc
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
|
||||
release_zip:
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@ false
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
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
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv output ../elixir-lang.github.com/docs/master
|
||||
mv docs ../elixir-lang.github.com/docs/master
|
||||
|
||||
release_erl:
|
||||
release_erl: compile
|
||||
@ rm -rf rel/elixir
|
||||
@ cd rel && ../rebar generate
|
||||
|
||||
@@ -124,7 +131,11 @@ test_erlang: compile
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
|
||||
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";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
|
||||
|
||||
@@ -14,40 +14,39 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R15B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
|
||||
Erlang R15B (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
# Contributing
|
||||
## Building documentation
|
||||
|
||||
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
|
||||
Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as elixir.
|
||||
|
||||
* `elixir` - Contains Elixir's kernel and stdlib;
|
||||
# After cloning and compiling Elixir
|
||||
$ git clone git://github.com/elixir-lang/ex_doc.git
|
||||
$ cd ../ex_doc && ../elixir/bin/mix compile
|
||||
$ cd ../elixir && make docs
|
||||
|
||||
* `eex` - Template engine that allows you to embed Elixir;
|
||||
## Contributing
|
||||
|
||||
* `ex_unit` - Simple test framework that ships with Elixir;
|
||||
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
* `iex` — IEx, Elixir's interactive shell
|
||||
|
||||
* `mix` — Elixir's build tool
|
||||
|
||||
We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
# Important links
|
||||
## Important links
|
||||
|
||||
* #elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [Mailing list][3]
|
||||
* [elixir-talk Mailing list (questions)][3]
|
||||
* [elixir-core Mailing list (development)][4]
|
||||
|
||||
[1]: http://elixir-lang.org
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: http://groups.google.com/group/elixir-lang-core
|
||||
[3]: http://groups.google.com/group/elixir-lang-talk
|
||||
[4]: http://groups.google.com/group/elixir-lang-core
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
|
||||
|
||||
|
||||
+7
-6
@@ -2,7 +2,7 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove .dev extension from current versions
|
||||
1) Remove `.dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
@@ -12,15 +12,16 @@ This document simply outlines the release process:
|
||||
|
||||
5) Create tag from master branch
|
||||
|
||||
6) Release new docs, update elixir-lang.org
|
||||
6) Release new docs with `make release_docs`, update elixir-lang.org
|
||||
|
||||
7) Push new zip to Elixir's downloads page
|
||||
7) Release new zip with `make release_zip`, push new zip to Elixir's elixir-lang.org/packages.html
|
||||
|
||||
8) Push package to expm
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) After release, bump versions and add .dev back
|
||||
9) After release, bump versions and add `.dev` back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION
|
||||
* CHANGELOG
|
||||
* CHANGELOG
|
||||
* src/elixir.app.src
|
||||
|
||||
+15
-8
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
if [ $# -eq 0 ]; then
|
||||
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version and exit
|
||||
-v Prints version
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
@@ -13,7 +13,8 @@ if [ $# -eq 0 ]; then
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell (with iex)
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
|
||||
@@ -25,7 +26,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -40,14 +41,19 @@ while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt)
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|-S)
|
||||
S=2
|
||||
;;
|
||||
--detached)
|
||||
--detached|--hidden)
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-`"
|
||||
;;
|
||||
--cookie)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL -setcookie "$VAL""
|
||||
;;
|
||||
--sname|--name)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
@@ -69,10 +75,11 @@ SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
fi
|
||||
|
||||
+7
-5
@@ -1,9 +1,11 @@
|
||||
@echo off
|
||||
if "%*" == "" (
|
||||
goto documentation
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
)
|
||||
goto run
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
@@ -20,4 +22,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
|
||||
:run
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -24,4 +24,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir +compile "$@"
|
||||
|
||||
+7
-5
@@ -1,9 +1,11 @@
|
||||
@echo off
|
||||
if "%*" == "" (
|
||||
goto documentation
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
)
|
||||
goto run
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
|
||||
echo.
|
||||
@@ -16,4 +18,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" --compile %*
|
||||
call "%~dp0\elixir.bat" +compile %*
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
#!/bin/sh
|
||||
if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
--app \"app\" Start the given app and its dependencies (*)
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -11,4 +35,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
|
||||
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ defmodule EEx do
|
||||
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))
|
||||
unquote(kind)(unquote(name)(unquote_splicing(args)), do: unquote(source))
|
||||
end
|
||||
Module.eval_quoted module, quote, [], info
|
||||
end
|
||||
|
||||
@@ -76,8 +76,8 @@ defmodule EEx.AssignsEngine do
|
||||
quote(do: Keyword.get var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(_) do
|
||||
super
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
@@ -94,4 +94,4 @@ defmodule EEx.SmartEngine do
|
||||
`AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(binary_to_list(bin), line)
|
||||
tokenize(:unicode.characters_to_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
@@ -112,7 +112,7 @@ defmodule EEx.Tokenizer do
|
||||
else
|
||||
:middle_expr
|
||||
end
|
||||
error ->
|
||||
_error ->
|
||||
:middle_expr
|
||||
end
|
||||
end
|
||||
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
|
||||
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule EEx.SmartEngineTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule EEx.TokenizerTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule EExText.Compiled do
|
||||
{ :erlang, 1, 2 }.tuple_to_list
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
@@ -270,14 +270,23 @@ foo
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "unicode" do
|
||||
template = """
|
||||
• <%= "•" %> •
|
||||
<%= "Jößé Vâlìm" %> Jößé Vâlìm
|
||||
"""
|
||||
result = EEx.eval_string(template)
|
||||
assert result == " • • •\n Jößé Vâlìm Jößé Vâlìm\n"
|
||||
end
|
||||
|
||||
test "evaluates the source from a given file" do
|
||||
filename = Path.expand("../fixtures/eex_template.eex", __FILE__)
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
|
||||
result = EEx.eval_file(filename)
|
||||
assert result == "foo bar.\n"
|
||||
end
|
||||
|
||||
test "evaluates the source from a given file with bindings" do
|
||||
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
result = EEx.eval_file(filename, [bar: 1])
|
||||
assert result == "foo 1\n"
|
||||
end
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
check_requires=true, %% when true, check requires
|
||||
check_clauses=true, %% when true, check def clauses ordering
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
@@ -24,31 +22,55 @@
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
scheduled=[], %% scheduled modules to be loaded
|
||||
file, %% the current scope filename
|
||||
context_modules=[], %% modules defined in the current context
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
macros, %% a list with macros imported by module
|
||||
functions}). %% a list with functions imported by module
|
||||
macro_macros=[], %% a list with macros imported from module inside a macro
|
||||
macros, %% a list with macros imported from module
|
||||
macro_functions=[], %% a list with functions imported from module inside a macro
|
||||
functions %% a list with functions imported from module
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
var_context=nil,
|
||||
expand_aliases=true,
|
||||
expand_imports=true,
|
||||
unquote=true
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
imports_hygiene=true,
|
||||
unquote=true,
|
||||
unquoted=false,
|
||||
mark=true
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
|
||||
|
||||
%% Atoms
|
||||
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
|
||||
@@ -11,7 +11,7 @@ defprotocol Access do
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
|
||||
@only [List, Function, Record, Atom]
|
||||
@only [List, Record, Atom]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
@@ -25,11 +25,13 @@ defimpl Access, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
keywords = [a: 1, b: 2]
|
||||
keywords[:a] #=> 1
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
...> keywords[:a]
|
||||
1
|
||||
|
||||
star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
star_ratings[1.5] #=> "★☆"
|
||||
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
...> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access([], _key), do: nil
|
||||
@@ -51,20 +53,9 @@ defimpl Access, for: Atom do
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Function do
|
||||
@doc """
|
||||
The Access protocol for functions simply invokes
|
||||
the function passing the item as argument. This
|
||||
is useful because it allows a function to be
|
||||
passed as argument in places a dict would also fit.
|
||||
"""
|
||||
def access(function, item) do
|
||||
function.(item)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +48,6 @@ defmodule Application.Behaviour do
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Starts the given application and all of its dependencies that
|
||||
# have not been started yet recursively.
|
||||
#
|
||||
@@ -95,6 +94,7 @@ defmodule Application.Behaviour do
|
||||
quote location: :keep do
|
||||
@behavior :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -51,12 +51,22 @@ defimpl Binary.Chars, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
to_binary 'foo' #=> "foo"
|
||||
to_binary ["foo", 'bar'] #=> "foobar"
|
||||
iex> to_binary('foo')
|
||||
"foo"
|
||||
iex> to_binary(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_binary(thing) do
|
||||
iolist_to_binary(thing)
|
||||
try do
|
||||
iolist_to_binary(thing)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: __MODULE__,
|
||||
structure: thing,
|
||||
extra: "Only iolists are supported"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,11 +74,19 @@ 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
|
||||
list_to_binary integer_to_list(thing)
|
||||
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
|
||||
list_to_binary float_to_list(thing)
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,8 +11,6 @@ defprotocol Binary.Inspect do
|
||||
printing.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Tuple, Atom, Number, Function, PID, Port, Reference]
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -34,15 +32,15 @@ defmodule Binary.Inspect.Utils do
|
||||
end
|
||||
|
||||
defp do_container_join([h], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts)
|
||||
Kernel.inspect(h, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, counter) when is_list(t) do
|
||||
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
|
||||
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([], _opts, _counter) do
|
||||
@@ -54,47 +52,52 @@ defmodule Binary.Inspect.Utils do
|
||||
|
||||
## escape
|
||||
|
||||
# It is considerably faster to loop the binary
|
||||
# and convert it to a list as we go compared
|
||||
# to looping the binary and creating a binary
|
||||
# as we go.
|
||||
def escape(other, char) do
|
||||
list_to_binary [char|do_escape(other, char)]
|
||||
b = do_escape(other, char, <<>>)
|
||||
<< char, b :: binary, char >>
|
||||
end
|
||||
|
||||
defp do_escape(<<char, t :: binary>>, char) do
|
||||
[?\\, char | do_escape(t, char)]
|
||||
@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(<<h, t :: binary>>, char) when
|
||||
h == ?# or h == ?\a or
|
||||
h == ?\b or h == ?\d or
|
||||
h == ?\e or h == ?\f or
|
||||
h == ?\n or h == ?\r or
|
||||
h == ?\\ or h == ?\t or
|
||||
h == ?\v do
|
||||
[?\\, escape_map(h) | do_escape(t, char)]
|
||||
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, char) do
|
||||
[h | do_escape(t,char)]
|
||||
defp do_escape(<<?\a, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, char) do
|
||||
[char]
|
||||
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
|
||||
|
||||
defp escape_map(?#), do: ?#
|
||||
defp escape_map(?\a), do: ?a
|
||||
defp escape_map(?\b), do: ?b
|
||||
defp escape_map(?\d), do: ?d
|
||||
defp escape_map(?\e), do: ?e
|
||||
defp escape_map(?\f), do: ?f
|
||||
defp escape_map(?\n), do: ?n
|
||||
defp escape_map(?\r), do: ?r
|
||||
defp escape_map(?\\), do: ?\\
|
||||
defp escape_map(?\t), do: ?t
|
||||
defp escape_map(?\v), do: ?v
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
@@ -111,9 +114,12 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(:foo) #=> ":foo"
|
||||
inspect(nil) #=> "nil"
|
||||
inspect(Foo.Bar) #=> "Foo.Bar"
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
|
||||
@@ -138,7 +144,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
@@ -146,7 +152,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
@@ -186,8 +192,10 @@ defimpl Binary.Inspect, for: BitString do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect("bar") #=> "bar"
|
||||
inspect("f\"oo") #=> "f\"oo"
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
"""
|
||||
|
||||
@@ -251,9 +259,12 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect('bar') #=> 'bar'
|
||||
inspect([0|'bar']) #=> "[0,98,97,114]"
|
||||
inspect([:foo,:bar]) #=> "[:foo, :bar]"
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
iex> inspect([0|'bar'])
|
||||
"[0,98,97,114]"
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo,:bar]"
|
||||
|
||||
"""
|
||||
|
||||
@@ -261,9 +272,9 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
def inspect(thing, opts) do
|
||||
cond do
|
||||
printable?(thing) ->
|
||||
escape(list_to_binary(thing), ?')
|
||||
Keyword.keyword?(thing) ->
|
||||
:io_lib.printable_list(thing) ->
|
||||
escape(:unicode.characters_to_binary(thing), ?')
|
||||
keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
@@ -272,7 +283,7 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
|
||||
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
|
||||
end, ", ")
|
||||
end
|
||||
|
||||
@@ -283,18 +294,15 @@ defimpl Binary.Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
## printable?
|
||||
|
||||
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
|
||||
printable?(cs)
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e, ?\a] do
|
||||
printable?(cs)
|
||||
end
|
||||
|
||||
defp printable?([]), do: true
|
||||
defp printable?(_), do: false
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
@@ -306,8 +314,10 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect({1,2,3}) #=> "{1,2,3}"
|
||||
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
|
||||
iex> inspect({1,2,3})
|
||||
"{1,2,3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
@@ -315,28 +325,12 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts[:raw] do
|
||||
record_protocol(tuple, opts)
|
||||
record_inspect(tuple, opts)
|
||||
end || container_join(tuple, "{", "}", opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_protocol(tuple, opts) do
|
||||
name = elem(tuple, 0)
|
||||
|
||||
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
|
||||
unless name in [BitString, List, Tuple, Atom, Number, Any] do
|
||||
try do
|
||||
target = Module.concat(Binary.Inspect, name)
|
||||
target.inspect(tuple, opts)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
record_inspect(tuple, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
@@ -365,12 +359,12 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
end
|
||||
|
||||
defp record_join([f], [v], opts) do
|
||||
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
|
||||
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
|
||||
end
|
||||
|
||||
defp record_join([fh|ft], [vh|vt], opts) do
|
||||
atom_to_binary(fh, :utf8) <> ": " <>
|
||||
Binary.Inspect.inspect(vh, opts) <> ", " <>
|
||||
Kernel.inspect(vh, opts) <> ", " <>
|
||||
record_join(ft, vt, opts)
|
||||
end
|
||||
|
||||
@@ -385,16 +379,24 @@ defimpl Binary.Inspect, for: Number do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(1) #=> "1"
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def inspect(thing, _) when is_integer(thing) do
|
||||
list_to_binary integer_to_list(thing)
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def inspect(thing, _) do
|
||||
list_to_binary :io_lib.format("~p", [thing])
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -404,16 +406,17 @@ defimpl Binary.Inspect, for: Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(%r/foo/m) #=> "%r\"foo\"m"
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
|
||||
def inspect(regex, _opts) when size(regex) == 5 do
|
||||
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
|
||||
Kernel.inspect other, Keyword.put(opts, :raw, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,15 +6,17 @@ defmodule Bitwise do
|
||||
The easiest way to use is to simply import them into
|
||||
your module:
|
||||
|
||||
use Bitwise
|
||||
|
||||
bnot 1 #=> -2
|
||||
1 &&& 1 #=> 1
|
||||
iex> use Bitwise
|
||||
iex> bnot 1
|
||||
-2
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
You can select to include only or skip operators by passing options:
|
||||
|
||||
use Bitwise, only_operators: true
|
||||
1 &&& 1 #=> 1
|
||||
iex> use Bitwise, only_operators: true
|
||||
...> 1 &&& 1
|
||||
1
|
||||
|
||||
"""
|
||||
|
||||
@@ -123,4 +125,4 @@ defmodule Bitwise do
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+47
-33
@@ -58,7 +58,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the
|
||||
Evaluates the contents given by string. The second argument is the
|
||||
binding (which should be a keyword) followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
@@ -84,47 +84,47 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 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:
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // [])
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
|
||||
def eval(string, binding, Macro.Env[] = env) do
|
||||
eval(string, binding, env.to_keywords)
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
eval_string(string, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval(string, binding, opts) do
|
||||
def eval_string(string, binding, opts) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
Evaluates the quoted contents.
|
||||
|
||||
This function accepts a list of environment options.
|
||||
Check `Code.eval` for more information.
|
||||
Check `Code.eval_string` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
contents = quote hygiene: false, do: a + b
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
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} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
@@ -140,8 +140,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns { :ok, ast }
|
||||
if it succeeds, { :error, { line, error, token } } otherwise.
|
||||
Converts the given string to AST. It returns `{ :ok, ast }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -153,6 +153,11 @@ defmodule Code do
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_binary/1
|
||||
|
||||
The opposite of converting a string to its AST is
|
||||
`Macro.to_binary`, which converts a AST to a binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_ast(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
@@ -185,10 +190,10 @@ defmodule Code do
|
||||
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@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 all the modules
|
||||
defined in the file.
|
||||
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
|
||||
module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
@@ -205,17 +210,17 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires 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 all the modules
|
||||
defined in the file.
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file`. If
|
||||
the file was already required/loaded, doesn't do anything and returns nil.
|
||||
|
||||
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, the given file will be loaded only once. Check `load_file`
|
||||
if you want a file to be loaded concurrently.
|
||||
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.
|
||||
|
||||
Check `load_file` 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)
|
||||
@@ -270,6 +275,15 @@ defmodule Code do
|
||||
:elixir_compiler.string :unicode.characters_to_list(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the quoted expression and returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted [quoted], file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is loaded. If the module is already
|
||||
loaded, it works as no-op. If the module was not loaded yet,
|
||||
|
||||
+252
-55
@@ -1,5 +1,5 @@
|
||||
defmodule Dict do
|
||||
@moduledoc """
|
||||
@moduledoc %B"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict, allowing
|
||||
@@ -13,33 +13,54 @@ defmodule Dict do
|
||||
|
||||
For simplicity's sake, in the examples below everytime
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like the two 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.
|
||||
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.
|
||||
|
||||
## Protocols
|
||||
|
||||
Besides implementing the functions in this module, all
|
||||
dictionaries are also required to implement the `Access`
|
||||
protocol:
|
||||
|
||||
iex> dict = HashDict.new
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end)
|
||||
|
||||
Keep in mind that all dicts are also required to
|
||||
implement both `Access` and `Enum.Iterator` protocols.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type keys :: [ key ]
|
||||
@type t :: tuple | list
|
||||
|
||||
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 get!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback merge(t, t) :: t
|
||||
defcallback merge(t, t, (key, value, value -> value)) :: t
|
||||
defcallback pop(t, key) :: {value, t}
|
||||
defcallback pop(t, key, value) :: {value, t}
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
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
|
||||
@@ -51,7 +72,7 @@ defmodule Dict do
|
||||
is_tuple(unquote(dict)) ->
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
List.Dict
|
||||
ListDict
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -63,8 +84,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.keys d #=> [:a,:b]
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
@@ -77,8 +99,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.values d #=> [1,2]
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
@@ -91,8 +114,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.size d #=> 2
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
2
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
@@ -105,9 +129,11 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.has_key?(d, :a) #=> true
|
||||
Dict.has_key?(d, :b) #=> false
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
@@ -121,31 +147,58 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> nil
|
||||
Dict.get d, :b, 3 #=> 3
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
nil
|
||||
iex> Dict.get(d, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default // nil) 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`.
|
||||
If `dict` does not contain `key`, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
iex> Dict.fetch(d, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(dict, key) do
|
||||
target(dict).fetch(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, it raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> raises KeyError[key: :b]
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec get!(t, key) :: value | no_return
|
||||
def get!(dict, key) do
|
||||
target(dict).get!(dict, key)
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(dict, key) do
|
||||
target(dict).fetch!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -154,9 +207,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.put d, :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
@@ -169,9 +223,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.put_new d, :a, 3
|
||||
#=> [a: 1, b: 2]
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
@@ -185,11 +240,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
d = new [b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
iex> d = HashDict.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
@@ -203,10 +261,11 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2
|
||||
#=> [a: 3, b: 2, d: 4]
|
||||
iex> d1 = HashDict.new([a: 1, b: 2])
|
||||
...> d2 = HashDict.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]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
@@ -221,12 +280,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a: 4, b: 2, d: 4]
|
||||
iex> d1 = HashDict.new([a: 1, b: 2])
|
||||
...> d2 = HashDict.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
@@ -234,15 +294,43 @@ defmodule Dict do
|
||||
target(dict).merge(dict, enum, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict` as
|
||||
well as the `dict` without `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :a
|
||||
...> {v, Enum.sort(d)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b
|
||||
...> {v, Enum.sort(d)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b, 3
|
||||
...> {v, Enum.sort(d)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default // nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a value in `dict` by calling `fun` on the value to get a new
|
||||
value. An exception is generated if `key` is not present in the dict.
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :a, fn val -> -val end
|
||||
#=> [a: -1, b: 2]
|
||||
iex> d = HashDict.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
|
||||
@@ -257,9 +345,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :c, 3, fn val -> -val end
|
||||
#=> [a: 1, b: 2, c: 3]
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
@@ -267,6 +356,80 @@ defmodule Dict do
|
||||
target(dict).update(dict, key, initial, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a tuple of two dicts, where the first dict contains only
|
||||
entries from `dict` with keys in `keys`, and the second dict
|
||||
contains only entries from `dict` with keys not in `keys`
|
||||
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [a: 1], [b: 2] }
|
||||
|
||||
iex> d = HashDict.new([])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
|
||||
iex> d = HashDict.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], [] }
|
||||
|
||||
"""
|
||||
@spec split(t, keys) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where the the given `keys` a removed from `dict`.
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.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])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec drop(t, keys) :: t
|
||||
def drop(dict, keys) do
|
||||
target(dict).drop(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are
|
||||
included. Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1]
|
||||
...>
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, keys) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
"""
|
||||
@@ -275,6 +438,40 @@ defmodule Dict do
|
||||
target(dict).empty(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal, if the dicts are of different types they're
|
||||
first converted to lists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = HashDict.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)
|
||||
...> b = []
|
||||
...> Dict.equal?(a, b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(a, b) do
|
||||
a_target = target(a)
|
||||
b_target = target(b)
|
||||
|
||||
cond do
|
||||
a_target == b_target ->
|
||||
a_target.equal?(a, b)
|
||||
|
||||
a_target.size(a) == b_target.size(b) ->
|
||||
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of key-value pairs stored in `dict`.
|
||||
No particular order is enforced.
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
defmodule Dict.Common do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior Dict
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
def new do
|
||||
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
|
||||
Exception.print_stacktrace
|
||||
empty(nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from a list of pairs.
|
||||
|
||||
## Examples
|
||||
|
||||
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
|
||||
#=> [a: 1, b: 2]
|
||||
|
||||
"""
|
||||
def new(pairs) do
|
||||
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
|
||||
Exception.print_stacktrace
|
||||
|
||||
Enum.reduce pairs, new, fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from a list of elements with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> ["a": "a", "b": "b"]
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
|
||||
Exception.print_stacktrace
|
||||
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule OrdDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary type that stores items
|
||||
as a list of tuples. It is a simple wrapper around
|
||||
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
|
||||
and exposed via the `Dict` module.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ OrdDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key key, data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get(dict(data), key, default // nil) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict(data), key) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store key, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put_new(dict(data), key, value) do
|
||||
dict(:orddict.update key, fn(x) -> x end, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase key, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update key, fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: OrdDict do
|
||||
def iterator({ OrdDict, data }), do: data
|
||||
def count({ OrdDict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Access, for: OrdDict do
|
||||
def access(dict, key), do: OrdDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defmodule Binary.Dict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary that forces the keys to be
|
||||
converted to binaries on insertion. Currently it is implemented
|
||||
using an `OrdDict`, but this may change in the future.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ Binary.Dict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key to_binary(key), data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get(dict(data), key, default // nil) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict(data), key) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store to_binary(key), value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put_new(dict(data), key, value) do
|
||||
dict(:orddict.update to_binary(key), fn(x) -> x end, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase to_binary(key), data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Binary.Dict do
|
||||
def iterator({ Binary.Dict, data }), do: data
|
||||
def count({ Binary.Dict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Access, for: Binary.Dict do
|
||||
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
|
||||
end
|
||||
+679
-816
File diff suppressed because it is too large
Load Diff
+123
-124
@@ -1,3 +1,99 @@
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
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}"
|
||||
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}"
|
||||
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}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
end
|
||||
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
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
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
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
|
||||
def message(exception) do
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
if exception.extra do
|
||||
msg <> ". " <> exception.extra
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Several convenience functions to work and pretty print
|
||||
@@ -14,47 +110,49 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
def normalize(:badarg) do
|
||||
ArgumentError.new
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
def normalize(:badarith) do
|
||||
ArithmeticError.new
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
def normalize(:system_limit) do
|
||||
SystemLimitError.new
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
def normalize({ :badarity, { fun, args } }) do
|
||||
BadArityError.new(function: fun, args: args)
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
def normalize({ :badfun, actual }) do
|
||||
BadFunctionError.new(actual: actual)
|
||||
BadFunctionError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize({ :badmatch, actual }) do
|
||||
MatchError.new(actual: actual)
|
||||
MatchError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize({ :case_clause, actual }) do
|
||||
CaseClauseError.new(actual: actual)
|
||||
CaseClauseError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
UndefinedFunctionError.new from_stacktrace(:erlang.get_stacktrace)
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize(:function_clause) do
|
||||
FunctionClauseError.new from_stacktrace(:erlang.get_stacktrace)
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize({ :badarg, payload }) do
|
||||
ArgumentError.new message: "argument error: #{inspect(payload)}"
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
def normalize(other) do
|
||||
ErlangError.new original: other
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -62,28 +160,28 @@ defmodule Exception do
|
||||
The current working directory may be given as argument, which
|
||||
is used to prettify the stacktrace.
|
||||
"""
|
||||
def format_entry(entry, cwd // nil)
|
||||
def format_stacktrace_entry(entry, cwd // nil)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler
|
||||
def format_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler
|
||||
def format_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}(file)"
|
||||
end
|
||||
|
||||
def format_entry({module, fun, arity, file_line}, cwd) do
|
||||
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
|
||||
end
|
||||
|
||||
def format_entry({fun, arity, file_line}, cwd) do
|
||||
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
|
||||
end
|
||||
|
||||
@@ -96,11 +194,6 @@ defmodule Exception do
|
||||
"""
|
||||
def format_stacktrace(trace // nil)
|
||||
|
||||
def format_stacktrace(trace) when is_tuple(trace) do
|
||||
IO.puts "Exception.format_stacktrace is deprecated, please use format_entry instead"
|
||||
print_stacktrace
|
||||
end
|
||||
|
||||
def format_stacktrace(trace) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
@@ -110,7 +203,7 @@ defmodule Exception do
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_entry(&1)) <> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -127,14 +220,6 @@ defmodule Exception do
|
||||
|
||||
## Helpers
|
||||
|
||||
# Check the given module is a valid exception record.
|
||||
@doc false
|
||||
def check!(module) do
|
||||
unless :erlang.function_exported(module, :message, 1) do
|
||||
raise "Expected #{inspect module} to implement message/1"
|
||||
end
|
||||
end
|
||||
|
||||
# Format fun and arity
|
||||
@doc false
|
||||
def format_fun_arity(fun, arity) do
|
||||
@@ -189,101 +274,15 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
{ module, function, length(args) }
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, arity, _ }|_]) do
|
||||
[module: module, function: function, arity: arity]
|
||||
{ module, function, arity }
|
||||
end
|
||||
|
||||
defp from_stacktrace(_), do: []
|
||||
end
|
||||
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
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}"
|
||||
defp from_stacktrace(_) do
|
||||
{ nil, nil, nil }
|
||||
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}"
|
||||
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}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
end
|
||||
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, to_arity(exception.arity)
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
|
||||
defp to_arity(arity) when is_integer(arity), do: arity
|
||||
defp to_arity(list) when is_list(list), do: length(list)
|
||||
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
|
||||
"no function clause matching: #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
|
||||
def message(exception) do
|
||||
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
+101
-182
@@ -62,7 +62,7 @@ defmodule File do
|
||||
|
||||
In order to write and read files, one must use the
|
||||
functions in the IO module. By default, a file is
|
||||
opened on binary mode which requires the functions
|
||||
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
|
||||
@@ -105,90 +105,6 @@ defmodule File do
|
||||
alias :filename, as: FN
|
||||
alias :filelib, as: FL
|
||||
|
||||
@doc false
|
||||
def expand_path(path) do
|
||||
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
|
||||
Exception.print_stacktrace
|
||||
Path.expand(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def expand_path(path, relative_to) do
|
||||
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
|
||||
Exception.print_stacktrace
|
||||
Path.expand(path, relative_to)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def basename(path) do
|
||||
IO.puts "File.basename is deprecated, please use Path.basename instead"
|
||||
Exception.print_stacktrace
|
||||
FN.basename(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def basename(path, extension) do
|
||||
IO.puts "File.basename is deprecated, please use Path.basename instead"
|
||||
Exception.print_stacktrace
|
||||
FN.basename(path, extension)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def dirname(path) do
|
||||
IO.puts "File.dirname is deprecated, please use Path.dirname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.dirname(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def extname(path) do
|
||||
IO.puts "File.extname is deprecated, please use Path.extname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.extension(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def rootname(path) do
|
||||
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.rootname(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def rootname(path, extension) do
|
||||
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.rootname(path, extension)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def join(paths) do
|
||||
IO.puts "File.join is deprecated, please use Path.join instead"
|
||||
Exception.print_stacktrace
|
||||
FN.join(paths)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def join(left, right) do
|
||||
IO.puts "File.join is deprecated, please use Path.join instead"
|
||||
Exception.print_stacktrace
|
||||
FN.join(left, right)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def split(path) do
|
||||
IO.puts "File.split is deprecated, please use Path.split instead"
|
||||
Exception.print_stacktrace
|
||||
FN.split(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def wildcard(glob) do
|
||||
IO.puts "File.wildcard is deprecated, please use Path.wildcard instead"
|
||||
Exception.print_stacktrace
|
||||
Path.wildcard(glob)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the path is a regular file.
|
||||
|
||||
@@ -253,7 +169,7 @@ defmodule File do
|
||||
case mkdir(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "make directory", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "make directory", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -278,7 +194,7 @@ defmodule File do
|
||||
case mkdir_p(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "make directory (with -p)", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "make directory (with -p)", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -311,7 +227,7 @@ defmodule File do
|
||||
{ :ok, binary } ->
|
||||
binary
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "read file", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "read file", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -346,7 +262,7 @@ defmodule File do
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "read file stats", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "read file stats", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -355,7 +271,7 @@ defmodule File do
|
||||
path. Returns `:ok` or `{ :error, reason }`.
|
||||
"""
|
||||
def write_stat(path, File.Stat[] = stat, opts // []) do
|
||||
F.write_file_info(path, setelem(stat, 0, :file_info), opts)
|
||||
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -366,7 +282,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: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "write file stats", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -389,7 +305,7 @@ defmodule File do
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "touch", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "touch", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -421,7 +337,7 @@ defmodule File do
|
||||
{ :ok, bytes_count } -> bytes_count
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy",
|
||||
source: to_binary(source), destination: to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -472,7 +388,7 @@ defmodule File do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: to_binary(source), destination: to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -546,7 +462,7 @@ defmodule File do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: to_binary(source), destination: to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -651,7 +567,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: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "write to file", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -688,7 +604,7 @@ defmodule File do
|
||||
case rm(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove file", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove file", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -716,7 +632,7 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove directory", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove directory", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -789,14 +705,20 @@ defmodule File do
|
||||
case rm_rf(path) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `path` according to the given list of modes.
|
||||
|
||||
By default, the file is opened in read mode, as a binary with utf8 encoding.
|
||||
In order to write and read files, one must use the functions
|
||||
in the IO module. By default, a file is opened in binary mode
|
||||
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.
|
||||
|
||||
The allowed modes:
|
||||
|
||||
@@ -873,7 +795,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.open!("file.txt", [:read, :write], fn(file) ->
|
||||
File.open("file.txt", [:read, :write], fn(file) ->
|
||||
IO.readline(file)
|
||||
end)
|
||||
|
||||
@@ -898,7 +820,7 @@ defmodule File do
|
||||
case open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "open", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -910,7 +832,7 @@ defmodule File do
|
||||
case open(path, modes, function) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "open", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -953,7 +875,7 @@ defmodule File do
|
||||
case F.set_cwd(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "set current working directory to", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "set current working directory to", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -975,6 +897,31 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns list of files in the given directory.
|
||||
|
||||
It returns `{ :ok, [files] }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map file_list, :unicode.characters_to_binary(&1) }
|
||||
{ :error, _ } = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `ls/1` but raises `File.Error`
|
||||
in case of an error.
|
||||
"""
|
||||
def ls!(dir // ".") do
|
||||
case ls(dir) do
|
||||
{ :ok, value } -> value
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "list directory", path: :unicode.characters_to_binary(dir)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Closes the file referenced by `io_device`. It mostly returns `:ok`, except
|
||||
for some severe errors such as out of memory.
|
||||
@@ -983,11 +930,6 @@ defmodule File do
|
||||
`close/1` might return an old write error and not even try to close the file.
|
||||
See `open/2`.
|
||||
"""
|
||||
def close(io_device) when is_function(io_device) do
|
||||
{ iterator, _ } = io_device.()
|
||||
iterator.(:close)
|
||||
end
|
||||
|
||||
def close(io_device) do
|
||||
F.close(io_device)
|
||||
end
|
||||
@@ -1006,7 +948,8 @@ defmodule File do
|
||||
quotes per single quotes and write each line to a target file
|
||||
is shown below:
|
||||
|
||||
source = File.iterator("README.md")
|
||||
{ :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, "'")
|
||||
@@ -1014,50 +957,27 @@ defmodule File do
|
||||
end
|
||||
|
||||
"""
|
||||
def iterator(device)
|
||||
|
||||
def iterator(file) when is_binary(file) or is_list(file) do
|
||||
iterator(file, [])
|
||||
end
|
||||
|
||||
def iterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_iterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def iterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, iterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `iterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
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
|
||||
open!(file, mode) |> iterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_iterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1065,50 +985,27 @@ defmodule File do
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
def biniterator(device)
|
||||
|
||||
def biniterator(file) when is_binary(file) or is_list(file) do
|
||||
biniterator(file, [])
|
||||
end
|
||||
|
||||
def biniterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :file.read_line(device) do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_biniterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def biniterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, biniterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `biniterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
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.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
open!(file, mode) |> biniterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -1127,4 +1024,26 @@ 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
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define 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
|
||||
until they are fetched:
|
||||
|
||||
defmodule MyEventHandler do
|
||||
use GenEvent.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_) do
|
||||
{ :ok, [] }
|
||||
end
|
||||
|
||||
def handle_event({:notification, x}, notifications) do
|
||||
{ :ok, [x|notifications] }
|
||||
end
|
||||
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> [1, 2]
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> []
|
||||
|
||||
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
|
||||
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
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{ :ok, :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defoverridable [init: 1,
|
||||
handle_event: 2,
|
||||
handle_call: 2, handle_info: 2,
|
||||
terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -88,26 +88,32 @@ defmodule GenServer.Behaviour do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
+193
-68
@@ -62,6 +62,7 @@ defmodule HashDict do
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
ordered()
|
||||
end
|
||||
@@ -72,9 +73,10 @@ defmodule HashDict do
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b,1},{:a,2}]
|
||||
#=> HashDict[a: 1, b: 2]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@spec new(list({key :: term, value :: term})) :: Dict.t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
@@ -88,9 +90,10 @@ defmodule HashDict do
|
||||
## Examples
|
||||
|
||||
HashDict.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> HashDict[{ "a", "a" }, { "b", "b" }]
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
@@ -123,7 +126,7 @@ defmodule HashDict do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
dict
|
||||
{ dict, 1 } ->
|
||||
{ _dict, 1 } ->
|
||||
raise KeyError, key: key
|
||||
end
|
||||
end
|
||||
@@ -149,16 +152,24 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value under key from the dict,
|
||||
raises KeyError if such key does not exist.
|
||||
Fetches the value under key from the dict
|
||||
and return it in a tagged tuple.
|
||||
"""
|
||||
def get!(dict, key) when is_tuple(dict) do
|
||||
def fetch(dict, key) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
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.
|
||||
"""
|
||||
@@ -167,35 +178,21 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a value from the dict.
|
||||
Returns the value under key from the dict as well as the dict without key.
|
||||
"""
|
||||
def delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, 0 } ->
|
||||
dict
|
||||
{ new_bucket, -1 } ->
|
||||
ordered(dict, size: size - 1, bucket: new_bucket)
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
{ dict, value, _ } -> { value, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, 0 } ->
|
||||
dict
|
||||
{ root, -1 } ->
|
||||
res = if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end
|
||||
end
|
||||
@doc """
|
||||
Deletes a value from the dict.
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -212,6 +209,16 @@ defmodule HashDict do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict1)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
@@ -220,7 +227,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
dict_fold(dict, [], [&1|&2])
|
||||
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -260,14 +267,69 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
|
||||
defp split([], including, excluding) do
|
||||
{ including, excluding }
|
||||
end
|
||||
|
||||
defp split([key|keys], including, excluding) do
|
||||
case dict_delete(excluding, key) do
|
||||
{ excluding, _, 0 } -> split(keys, including, excluding)
|
||||
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys.
|
||||
"""
|
||||
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
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
def reduce(trie() = dict, acc, fun) do
|
||||
dict_fold(dict, acc, fun)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_get(ordered(bucket: bucket), key) do
|
||||
:lists.keyfind(key, 1, bucket)
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp dict_get(trie(root: root, depth: depth), key) do
|
||||
:lists.keyfind(key, 1, node_bucket(root, depth, bucket_hash(key)))
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
@@ -301,28 +363,89 @@ defmodule HashDict do
|
||||
{ trie(dict, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ new_bucket, value, -1 } ->
|
||||
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end, value, -1 }
|
||||
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
|
||||
end
|
||||
|
||||
defp bucket_get([{key,_}=e|_bucket], key) do
|
||||
e
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
false
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([{k,_}=e|bucket], key, { :put, value }) when key < k do
|
||||
{ [{key,value},e|bucket], 1 }
|
||||
defp bucket_put([{k,_}|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [{key, value}|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}=e|bucket], key, { :update, initial, _fun }) when key < k do
|
||||
{ [{key,initial},e|bucket], 1 }
|
||||
defp bucket_put([{k,_}|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [{key, initial}|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}=e|bucket], key, value) when key > k do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,_}|bucket], key, { :put, value }) do
|
||||
defp bucket_put([{key,_}|bucket], key, { :put, value }) do
|
||||
{ [{key,value}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,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
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
@@ -335,27 +458,27 @@ defmodule HashDict do
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([{k,_}=e|bucket], key, value) when key < k, do: [{key,value},e|bucket]
|
||||
defp bucket_put!([{k,_}=e|bucket], key, value) when key > k, do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([{_,_}|bucket], key, value), do: [{key,value}|bucket]
|
||||
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 key < k do
|
||||
{ bucket, 0 }
|
||||
defp bucket_delete([{k,_}|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
|
||||
{ rest, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], count }
|
||||
defp bucket_delete([{key,value}|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{_,_}|bucket], _key) do
|
||||
{ bucket, -1 }
|
||||
defp bucket_delete([e|bucket], key) do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
{ [], 0 }
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Folds the bucket
|
||||
@@ -395,29 +518,29 @@ defmodule HashDict do
|
||||
defp node_put(node, 0, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), key, value)
|
||||
{ setelem(node, pos, new), count }
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
|
||||
{ setelem(node, pos, new), count }
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), key) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { setelem(node, pos, new), -1 }
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { setelem(node, pos, new), -1 }
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -471,14 +594,16 @@ defmodule HashDict do
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
setelem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: HashDict do
|
||||
def iterator(dict), do: HashDict.to_list(dict)
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
|
||||
def member?(_dict, _), do: false
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
@@ -489,6 +614,6 @@ defimpl Binary.Inspect, for: HashDict do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(dict, opts) do
|
||||
"#HashDict<" <> Binary.Inspect.inspect(HashDict.to_list(dict), opts) <> ">"
|
||||
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
+46
-13
@@ -23,6 +23,8 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device. It returns:
|
||||
|
||||
@@ -34,7 +36,7 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def read(device // :stdio, count) do
|
||||
def read(device // group_leader(), count) do
|
||||
:io.get_chars(map_dev(device), "", count)
|
||||
end
|
||||
|
||||
@@ -44,7 +46,7 @@ defmodule IO do
|
||||
|
||||
Check `read/2` for more information.
|
||||
"""
|
||||
def binread(device // :stdio, count) do
|
||||
def binread(device // group_leader(), count) do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
@@ -65,7 +67,7 @@ defmodule IO do
|
||||
This function does the same as `gets/2`,
|
||||
except the prompt is not required as argument.
|
||||
"""
|
||||
def readline(device // :stdio) do
|
||||
def readline(device // group_leader()) do
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@@ -75,7 +77,7 @@ defmodule IO do
|
||||
|
||||
Check `readline/1` for more information.
|
||||
"""
|
||||
def binreadline(device // :stdio) do
|
||||
def binreadline(device // group_leader()) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
@@ -99,7 +101,7 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
def write(device // :stdio, item) do
|
||||
def write(device // group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@@ -109,7 +111,7 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
def binwrite(device // :stdio, item) do
|
||||
def binwrite(device // group_leader(), item) do
|
||||
:file.write map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@@ -118,22 +120,37 @@ defmodule IO do
|
||||
but adds a new line at the end. The argument is expected
|
||||
to be a chardata.
|
||||
"""
|
||||
def puts(device // :stdio, item) do
|
||||
def puts(device // group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. Returns the item given.
|
||||
followed by a new line. A set of options can be given.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.inspect Process.list
|
||||
|
||||
"""
|
||||
def inspect(device // :stdio, item, opts // []) do
|
||||
puts device, Binary.Inspect.inspect(item, opts)
|
||||
def inspect(item, opts // []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
def inspect(device, item, opts) do
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets `count` bytes from the IO device. It returns:
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, count implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, the number of raw bytes. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
@@ -143,7 +160,23 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def getb(device // :stdio, prompt, count // 1) do
|
||||
def getn(prompt, count // 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
getn(group_leader, prompt, count)
|
||||
end
|
||||
|
||||
def getn(device, prompt) do
|
||||
getn(device, prompt, 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, count implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, the number of raw bytes.
|
||||
"""
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@@ -159,7 +192,7 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def gets(device // :stdio, prompt) do
|
||||
def gets(device // group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_iodata(prompt))
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
quote do
|
||||
name = unquote(name)
|
||||
code = unquote(code)
|
||||
|
||||
def name, [], [], do:
|
||||
quote do: "\e[#{unquote(code)}m"
|
||||
|
||||
args =
|
||||
quote do: [<< unquote(atom_to_binary(name)), rest :: binary >> ]
|
||||
|
||||
defp :escape_sequence, args, [], do:
|
||||
quote do: { "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
This module provides functionality to render ANSI escape sequences
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
"""
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
|
||||
@doc """
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
defsequence :reset, 0
|
||||
|
||||
@doc "Bright (increased intensity) or Bold"
|
||||
defsequence :bright, 1
|
||||
|
||||
@doc "Faint (decreased intensity), not widely supported"
|
||||
defsequence :faint, 2
|
||||
|
||||
@doc "Italic: on. Not widely supported. Sometimes treated as inverse."
|
||||
defsequence :italic, 3
|
||||
|
||||
@doc "Underline: Single"
|
||||
defsequence :underline, 4
|
||||
|
||||
@doc "Blink: Slow. Less than 150 per minute"
|
||||
defsequence :blink_slow, 5
|
||||
|
||||
@doc "Blink: Rapid. MS-DOS ANSI.SYS; 150 per minute or more; not widely supported"
|
||||
defsequence :blink_rapid, 6
|
||||
|
||||
@doc "Image: Negative. Swap foreground and background"
|
||||
defsequence :inverse, 7
|
||||
|
||||
@doc "Image: Negative. Swap foreground and background"
|
||||
defsequence :reverse, 7
|
||||
|
||||
@doc "Conceal. Not widely supported"
|
||||
defsequence :conceal, 8
|
||||
|
||||
@doc "Crossed-out. Characters legible, but marked for deletion. Not widely supported."
|
||||
defsequence :crossed_out, 9
|
||||
|
||||
@doc "Sets primary (default) font"
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
|
||||
@doc "Normal color or intensity"
|
||||
defsequence :normal, 22
|
||||
|
||||
@doc "Not italic"
|
||||
defsequence :not_italic, 23
|
||||
|
||||
@doc "Underline: None"
|
||||
defsequence :no_underline, 24
|
||||
|
||||
@doc "Blink: off"
|
||||
defsequence :blink_off, 25
|
||||
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@doc "Sets background color to #{color}"
|
||||
defsequence :"#{color}_background", code + 40
|
||||
end
|
||||
|
||||
@doc "Default text color"
|
||||
defsequence :default_color, 39
|
||||
|
||||
@doc "Default background color"
|
||||
defsequence :default_background, 49
|
||||
|
||||
@doc "Framed"
|
||||
defsequence :framed, 51
|
||||
|
||||
@doc "Encircled"
|
||||
defsequence :encircled, 52
|
||||
|
||||
@doc "Overlined"
|
||||
defsequence :overlined, 53
|
||||
|
||||
@doc "Not framed or encircled"
|
||||
defsequence :not_framed_encircled, 54
|
||||
|
||||
@doc "Not overlined"
|
||||
defsequence :not_overlined, 55
|
||||
|
||||
# Catch spaces between codes
|
||||
defp escape_sequence(<< ?\s, rest :: binary >>) do
|
||||
escape_sequence(rest)
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, %r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
It will also append a %{reset} to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/0` function)
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit // terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
end
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/0` function)
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye")
|
||||
"\e[0mbye"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit // terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
end
|
||||
|
||||
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, true, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) 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}
|
||||
end
|
||||
|
||||
defp do_escape_sequence(rest, emit, acc) do
|
||||
{code, rest} = escape_sequence(rest)
|
||||
if emit do
|
||||
acc = [code|acc]
|
||||
end
|
||||
do_escape(rest, true, emit, true, acc)
|
||||
end
|
||||
end
|
||||
+772
-551
File diff suppressed because it is too large
Load Diff
+147
-67
@@ -3,34 +3,27 @@ defmodule Kernel.CLI do
|
||||
Module responsible for controlling Elixir's CLI
|
||||
"""
|
||||
|
||||
defrecord Config, commands: [], output: ".",
|
||||
compile: [], halt: true, compiler_options: []
|
||||
defrecord Config, commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: []
|
||||
|
||||
# This is the API invoked by Elixir boot process.
|
||||
@doc false
|
||||
def main(argv) do
|
||||
argv = lc arg inlist argv, do: list_to_binary(arg)
|
||||
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
:elixir_code_server.cast({ :argv, argv })
|
||||
|
||||
run fn ->
|
||||
Enum.map Enum.reverse(config.commands), process_command(&1, config)
|
||||
:elixir_code_server.cast(:finished)
|
||||
end, config.halt
|
||||
end
|
||||
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
@doc """
|
||||
Wait until the CLI finishes processing options.
|
||||
"""
|
||||
def wait_until_finished do
|
||||
case :elixir_code_server.call({ :wait_until_finished, self }) do
|
||||
:wait ->
|
||||
receive do
|
||||
{ :elixir_code_server, :finished } -> :ok
|
||||
end
|
||||
:ok -> :ok
|
||||
end
|
||||
if errors != [] do
|
||||
Enum.each(errors, IO.puts(:stderr, &1))
|
||||
System.halt(1)
|
||||
end
|
||||
end, config.halt
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -53,7 +46,7 @@ defmodule Kernel.CLI do
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
System.halt(1)
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
@@ -66,12 +59,12 @@ defmodule Kernel.CLI do
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
## Private
|
||||
## Helpers
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
@@ -83,12 +76,12 @@ defmodule Kernel.CLI do
|
||||
exception ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
catch
|
||||
kind, reason ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -97,15 +90,11 @@ defmodule Kernel.CLI do
|
||||
unless hooks == [], do: at_exit(status)
|
||||
end
|
||||
|
||||
defp invalid_option(option) do
|
||||
IO.puts(:stderr, "Unknown option #{option}")
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|t], _ } when h == hd(list) ->
|
||||
invalid_option h
|
||||
{ [h|hs], _ } when h == hd(list) ->
|
||||
new_config = config.update_errors ["#{h} : Unknown option" | &1]
|
||||
callback.(hs, new_config)
|
||||
{ new_list, new_config } ->
|
||||
callback.(new_list, new_config)
|
||||
end
|
||||
@@ -113,8 +102,18 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Process shared options
|
||||
|
||||
defp process_shared(["-v"|t], config) do
|
||||
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
IO.puts "Elixir #{System.version}"
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -130,27 +129,15 @@ defmodule Kernel.CLI do
|
||||
process_shared t, config.update_commands [{:eval,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-r",h|t], config) do
|
||||
process_shared t, Enum.reduce(Path.wildcard(h), config, fn path, config ->
|
||||
config.update_commands [{:require,path}|&1]
|
||||
end)
|
||||
process_shared t, config.update_commands [{:require,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pr",h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--remsh", "--name"] do
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -164,18 +151,16 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_argv(["--compile"|t], config) do
|
||||
defp process_argv(["+compile"|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
defp process_argv(["+iex"|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S",h|t], config) do
|
||||
exec = System.find_executable(h)
|
||||
if exec do
|
||||
{ config.update_commands([{:require,exec}|&1]), t }
|
||||
else
|
||||
IO.puts(:stderr, "Could not find executable #{h}")
|
||||
System.halt(1)
|
||||
end
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -183,7 +168,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:require,h}|&1]), t }
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -218,7 +203,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile [pattern|&1]
|
||||
end
|
||||
end
|
||||
@@ -227,42 +212,137 @@ defmodule Kernel.CLI do
|
||||
{ config.update_commands([{:compile,config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
|
||||
defp process_iex(["--"|t], config) do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex",_|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S",h|t], config) do
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_iex([], config) do
|
||||
{ config, [] }
|
||||
end
|
||||
|
||||
# Process commands
|
||||
|
||||
defp process_command({:cookie,h}, _config) do
|
||||
if Node.alive? do
|
||||
Node.set_cookie(binary_to_atom(h))
|
||||
:ok
|
||||
else
|
||||
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:eval, expr}, _config) when is_binary(expr) do
|
||||
Code.eval(expr, [])
|
||||
Code.eval_string(expr, [])
|
||||
:ok
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.Behaviour.start(binary_to_atom(app)) do
|
||||
{ :error, reason } ->
|
||||
IO.puts(:stderr, "Could not start application #{app}: #{inspect reason}")
|
||||
System.halt(1)
|
||||
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
|
||||
:ok ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:require, file}, _config) when is_binary(file) do
|
||||
Code.require_file(file)
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = find_elixir_executable(file) do
|
||||
Code.require_file(exec)
|
||||
:ok
|
||||
else
|
||||
{ :error, "-S : Could not find executable #{file}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
Code.require_file(file)
|
||||
:ok
|
||||
else
|
||||
{ :error, "No file named #{file}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Enum.map files, Code.require_file(&1)
|
||||
:ok
|
||||
else
|
||||
{ :error, "-r : No files matched pattern #{pattern}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
Kernel.ParallelRequire.files(files)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Kernel.ParallelRequire.files(files)
|
||||
:ok
|
||||
else
|
||||
{ :error, "-pr : No files matched pattern #{pattern}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
File.mkdir_p(config.output)
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, Path.wildcard(&1)
|
||||
files = Enum.uniq(List.concat(files))
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
if files != [] do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
:ok
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp find_elixir_executable(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
# If we are on Windows, the executable is going to be
|
||||
# a .bat file that must be in the same directory as
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{ :win32, _ } ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
_ ->
|
||||
exec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule Kernel.ErrorHandler do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = Process.get(:elixir_compiler_pid)
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
parent <- { :waiting, self(), module }
|
||||
receive do
|
||||
{ :release, ^parent } -> :ok
|
||||
|
||||
@@ -32,7 +32,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
schedulers = :erlang.system_info(:schedulers_online)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
end
|
||||
|
||||
@@ -91,7 +91,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
# 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 } ->
|
||||
{ :module_available, _child, module, binary } ->
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [{module, binary}|result]
|
||||
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
|
||||
@@ -99,16 +99,34 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_waiting = OrdDict.store(child, on, waiting)
|
||||
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
extra = if match?({^child, module}, List.keyfind(waiting, child, 0)) do
|
||||
" (undefined module #{inspect module})"
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file}#{extra} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
|
||||
# Release waiting processes that are waiting for the given module
|
||||
defp release_waiting_processes(module, waiting) do
|
||||
Enum.filter waiting, fn { child, waiting_module } ->
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule Kernel.ParallelRequire do
|
||||
can be optionally given as argument.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
schedulers = :erlang.system_info(:schedulers_online)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_requires(files, [], callback, schedulers, [])
|
||||
end
|
||||
|
||||
|
||||
@@ -5,42 +5,50 @@
|
||||
defmodule Kernel.RecordRewriter do
|
||||
@moduledoc false
|
||||
|
||||
def optimize_clause(clause) do
|
||||
optimize_clause(clause, :orddict.new)
|
||||
def optimize_clause(module, clause) do
|
||||
optimize_clause(clause, module, :orddict.new)
|
||||
end
|
||||
|
||||
## Clause
|
||||
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, dict) do
|
||||
{ args, dict } = optimize_args(args, dict)
|
||||
{ body, dict, res } = optimize_body(body, dict, [])
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ body, dict, res } = optimize_body(body, module, dict, [])
|
||||
{ { :clause, line, args, guards, body }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_args(args, dict) do
|
||||
defp optimize_args(args, module, dict) do
|
||||
Enum.map_reduce args, dict, fn(arg, acc) ->
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
|
||||
{ new_arg, new_acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_body([], dict, _acc) do
|
||||
defp optimize_body([], _module, dict, _acc) do
|
||||
{ [], dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_body([h], dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, dict)
|
||||
defp optimize_body([h], module, dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
|
||||
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
|
||||
end
|
||||
|
||||
defp optimize_body([h|t], dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, dict)
|
||||
optimize_body(t, new_dict, [new_expr|acc])
|
||||
defp optimize_body([h|t], module, dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
|
||||
optimize_body(t, module, new_dict, [new_expr|acc])
|
||||
end
|
||||
|
||||
## Record helpers
|
||||
|
||||
defp record_fields(record) do
|
||||
defp record_fields(record, record) do
|
||||
fields = Module.get_attribute(record, :record_fields)
|
||||
optimizable = Module.get_attribute(record, :record_optimizable)
|
||||
unless nil?(fields) or nil?(optimizable) do
|
||||
{ (lc { k, _ } inlist fields, do: k), optimizable }
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(_module, record) do
|
||||
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
|
||||
try do
|
||||
fields = lc { k, _ } inlist record.__record__(:fields), do: k
|
||||
@@ -55,16 +63,16 @@ defmodule Kernel.RecordRewriter do
|
||||
defp record_field_info(function) do
|
||||
case atom_to_list(function) do
|
||||
'update' -> nil
|
||||
'update_' ++ field -> nil
|
||||
'update_' ++ _field -> nil
|
||||
_ -> { :accessor, function }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(line, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(record) do
|
||||
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if List.member?(optimizable, { function, length(args) + 1 }) do
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, field == &1) do
|
||||
@@ -81,7 +89,7 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(_line, _res, _left, _right, _args) do
|
||||
defp optimize_call(_line, _module, _res, _left, _right, _args) do
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -111,12 +119,12 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
{ args, dict } = optimize_args(args, dict)
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, 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)
|
||||
|
||||
case optimize_call(call_line, res, left, right, args) do
|
||||
case optimize_call(call_line, module, res, left, right, args) do
|
||||
{ call, call_res } ->
|
||||
{ call, dict, call_res }
|
||||
nil ->
|
||||
@@ -124,15 +132,15 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, line, expr, args }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
{ args, dict } = optimize_args(args, dict)
|
||||
defp optimize_expr({ :call, line, expr, args }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ { :call, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :match, line, left, right }, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ :match, line, left, right }, module, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, module, dict)
|
||||
|
||||
match = { :match, line, left, right }
|
||||
|
||||
@@ -147,81 +155,81 @@ defmodule Kernel.RecordRewriter do
|
||||
{ match, dict, right_res || left_res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, left, right }, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :op, line, op, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, expr }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
defp optimize_expr({ :op, line, op, expr }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :op, line, op, expr }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin, line, elements }, dict) do
|
||||
{ elements, dict } = optimize_args(elements, dict)
|
||||
defp optimize_expr({ :bin, line, elements }, module, dict) do
|
||||
{ elements, dict } = optimize_args(elements, module, dict)
|
||||
{ { :bin, line, elements }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :cons, line, left, right }, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ :cons, line, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :cons, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :block, line, args }, dict) do
|
||||
{ args, dict, res } = optimize_body(args, dict, [])
|
||||
defp optimize_expr({ :block, line, args }, module, dict) do
|
||||
{ args, dict, res } = optimize_body(args, module, dict, [])
|
||||
{ { :block, line, args }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :tuple, line, args }, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, dict)
|
||||
defp optimize_expr({ :tuple, line, args }, module, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
|
||||
args_res = if Enum.any?(args_res), do: args_res, else: nil
|
||||
|
||||
res =
|
||||
case args do
|
||||
[{ :atom, _, atom }|t] -> atom
|
||||
[{ :atom, _, atom }|_] -> atom
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{ { :tuple, line, args }, dict, { res, args_res } }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :var, _, name } = var, dict) do
|
||||
defp optimize_expr({ :var, _, name } = var, _module, dict) do
|
||||
case :orddict.find(name, dict) do
|
||||
{ :ok, res } -> { var, dict, res }
|
||||
:error -> { var, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :case, line, expr, clauses }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :case, line, expr, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses }, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :receive, line, clauses }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :receive, line, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, dict, [])
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
|
||||
|
||||
dict = join_dict(tuples, dict)
|
||||
res = join_result(tuples, res)
|
||||
@@ -229,58 +237,58 @@ defmodule Kernel.RecordRewriter do
|
||||
{ { :receive, line, clauses, after_key, after_value }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
|
||||
catches = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, dict)
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
|
||||
elses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ body, _, res } = optimize_body(body, dict, [])
|
||||
{ body, _, res } = optimize_body(body, module, dict, [])
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ try_after, _, _ } = optimize_body(try_after, dict, [])
|
||||
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
|
||||
{ { :try, line, body, elses, catches, try_after }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :function, module, name, arity } }, dict) do
|
||||
{ module, dict, _ } = optimize_expr(module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, dict)
|
||||
{ { :fun, line, { :function, module, name, arity } }, dict, nil }
|
||||
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
|
||||
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, module, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, module, dict)
|
||||
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, dict) do
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
|
||||
clauses = lc clause inlist clauses do
|
||||
{ clause, _, _ } = optimize_clause(clause, dict)
|
||||
{ clause, _, _ } = optimize_clause(clause, module, dict)
|
||||
clause
|
||||
end
|
||||
|
||||
{ { :fun, line, { :clauses, clauses } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ comprehension, line, expr, args }, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, new_dict)
|
||||
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, module, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
|
||||
{ { comprehension, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ generate, line, left, right }, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { generate, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr(other, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
{ other, dict, nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp optimize_tuple_args(args, dict) do
|
||||
defp optimize_tuple_args(args, module, dict) do
|
||||
{ final_args, { final_dict, final_acc } } =
|
||||
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
|
||||
{ new_arg, { new_acc, [res|acc_res] } }
|
||||
end
|
||||
|
||||
@@ -342,6 +350,10 @@ defmodule Kernel.RecordRewriter do
|
||||
vars
|
||||
end
|
||||
|
||||
defp join_dict([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t]) do
|
||||
join_dict(t, dict)
|
||||
end
|
||||
@@ -355,6 +367,8 @@ defmodule Kernel.RecordRewriter do
|
||||
{ :ok, _ } -> :orddict.store(key, nil, acc)
|
||||
:error -> :orddict.erase(key, acc)
|
||||
end
|
||||
{ key, nil }, acc ->
|
||||
:orddict.store(key, nil, acc)
|
||||
end
|
||||
|
||||
join_dict(t, other)
|
||||
@@ -364,6 +378,10 @@ defmodule Kernel.RecordRewriter do
|
||||
other
|
||||
end
|
||||
|
||||
defp join_result([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t]) do
|
||||
join_result(t, res)
|
||||
end
|
||||
@@ -383,4 +401,4 @@ defmodule Kernel.RecordRewriter do
|
||||
defp join_result([], res) do
|
||||
res
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:{}.(1,2,3)
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
|
||||
"""
|
||||
@@ -35,7 +35,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:[].(1,2,3)
|
||||
iex> [ 1, 2, 3 ]
|
||||
[ 1, 2, 3 ]
|
||||
|
||||
"""
|
||||
@@ -46,7 +46,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:<<>>.(1,2,3)
|
||||
iex> << 1, 2, 3 >>
|
||||
<< 1, 2, 3 >>
|
||||
|
||||
## Bitstring types
|
||||
@@ -55,16 +55,16 @@ defmodule Kernel.SpecialForms do
|
||||
specific types. Most of the time, Elixir will figure out
|
||||
the part's type and won't require any work from you:
|
||||
|
||||
<<102, "oo">>
|
||||
#=> "foo"
|
||||
iex> <<102, "oo">>
|
||||
"foo"
|
||||
|
||||
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:
|
||||
|
||||
rest = "oo"
|
||||
<<102, rest>>
|
||||
#=> ** (ArgumentError) argument error
|
||||
iex> rest = "oo"
|
||||
...> <<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
|
||||
@@ -94,8 +94,8 @@ defmodule Kernel.SpecialForms do
|
||||
signedness only matters for matching. If unspecified, it
|
||||
defaults to unsigned. Example:
|
||||
|
||||
<<-100 :: signed, rest :: binary>> = <<-100, "foo">>
|
||||
#=> <<156,102,111,111>>
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156,102,111,111>>
|
||||
|
||||
This match would have failed if we did not specify that the
|
||||
value -100 is signed. If we're matching into a variable instead
|
||||
@@ -103,9 +103,9 @@ defmodule Kernel.SpecialForms do
|
||||
will simply be interpreted as having the given (or implied)
|
||||
signedness, e.g.:
|
||||
|
||||
<<val, rest :: binary>> = <<-100, "foo">>
|
||||
val
|
||||
#=> 156
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
...> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
|
||||
@@ -130,13 +130,13 @@ defmodule Kernel.SpecialForms do
|
||||
unit is multiplied by the size to give the effective size of
|
||||
the part:
|
||||
|
||||
<<102, rest :: [size(2), unit(8)]>> = "foo"
|
||||
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
|
||||
"foo"
|
||||
|
||||
<<102, rest :: size(16)>> = "foo"
|
||||
iex> <<102, _rest :: size(16)>> = "foo"
|
||||
"foo"
|
||||
|
||||
<<102, rest :: size(32)>> = "foo"
|
||||
iex> <<102, _rest :: size(32)>> = "foo"
|
||||
** (MatchError) no match of right hand side value: "foo"
|
||||
|
||||
In the example above, the first two expressions matches
|
||||
@@ -158,7 +158,8 @@ defmodule Kernel.SpecialForms do
|
||||
writing `size(8)`, one can write just `8` and it will be interpreted
|
||||
as `size(8)`
|
||||
|
||||
<< 1 :: 3 >> == << 1 :: size(3) >> #=> true
|
||||
iex> << 1 :: 3 >> == << 1 :: size(3) >>
|
||||
true
|
||||
|
||||
The default unit for integers, floats, and bitstrings is 1. For
|
||||
binaries, it is 8.
|
||||
@@ -245,8 +246,9 @@ defmodule Kernel.SpecialForms do
|
||||
import those functions and reference them as local functions,
|
||||
for example:
|
||||
|
||||
import List
|
||||
flatten([1,[2],3]) #=> [1,2,3]
|
||||
iex> import List
|
||||
...> flatten([1,[2],3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
|
||||
@@ -302,6 +304,26 @@ defmodule Kernel.SpecialForms do
|
||||
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
|
||||
functions or macros from this module, Elixir is going to issue
|
||||
a warning implying the import is not being used.
|
||||
|
||||
In case the import is generated automatically by a macro,
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
|
||||
If two modules `A` and `B` are imported and they both contain
|
||||
a `foo` function with an arity of `1`, an error is only emitted
|
||||
if an ambiguous call to `foo/1` is actually made; that is, the
|
||||
errors are emitted lazily, not eagerly.
|
||||
"""
|
||||
defmacro import(module, opts)
|
||||
|
||||
@@ -326,44 +348,46 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __FILE__
|
||||
|
||||
@doc """
|
||||
Returns the current directory as a binary.
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Allows you to get the representation of any expression.
|
||||
|
||||
## Examples
|
||||
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> { :sum, [], [1, 2, 3] }
|
||||
iex> quote do: sum(1, 2, 3)
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
|
||||
## Explanation
|
||||
|
||||
Any Elixir code can be represented using Elixir data structures.
|
||||
The building block of Elixir homoiconicity is a tuple with three
|
||||
elements, for example:
|
||||
The building block of Elixir macros is a tuple with three elements,
|
||||
for example:
|
||||
|
||||
{ :sum, 1, [1, 2, 3] }
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
|
||||
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
|
||||
another tuple in the same representation;
|
||||
* The second element of the tuple is always an integer
|
||||
representing the line number;
|
||||
* The second element of the tuple represents metadata;
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call);
|
||||
|
||||
## Options
|
||||
|
||||
* `:hygiene` - When false, disables hygiene for variables, aliases and imports;
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control which 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;
|
||||
* `:expand_aliases` - When false, do not expand aliases;
|
||||
* `:expand_imports` - When false, do not expand imports;
|
||||
* `:var_context` - The context for quoted variables. Defaults to the current module;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the context resolution happens at;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -377,19 +401,37 @@ defmodule Kernel.SpecialForms do
|
||||
"binaries" #=> Binaries
|
||||
{key, value} #=> Tuple with two elements
|
||||
|
||||
## Hygiene
|
||||
## Hygiene and context
|
||||
|
||||
Elixir macros are hygienic. This means aliases and imports
|
||||
defined inside the quoted often refer to the context that
|
||||
defined the macro.
|
||||
Elixir macros are hygienic via means of deferred resolution.
|
||||
This means variables, aliases and imports defined inside the
|
||||
quoted refer to the context that defined the macro and not
|
||||
the context where the macro is expanded.
|
||||
|
||||
Furthermore, variables inside quote are also hygienic. That
|
||||
said, a variable defined in a macro cannot affect the scope
|
||||
where the macro is included.
|
||||
For this mechanism to work, every quoted code is attached
|
||||
to a context. Consider the following example:
|
||||
|
||||
The option `hygiene` can turn off all the hygiene mechanisms
|
||||
defined below. However, they can also be changed in a one by
|
||||
one basis.
|
||||
defmodule ContextSample do
|
||||
def hello do
|
||||
quote do: world
|
||||
end
|
||||
end
|
||||
|
||||
ContextSample.hello
|
||||
#=> {:world,[],ContextSample}
|
||||
|
||||
Notice how the third element of the returned tuple is the
|
||||
module name. This means that the variable is associated to the
|
||||
ContextSample module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
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
|
||||
context and we are going to explore each of them in the following
|
||||
subsections.
|
||||
|
||||
### Hygiene in variables
|
||||
|
||||
@@ -451,14 +493,13 @@ defmodule Kernel.SpecialForms do
|
||||
# Access the variable a from Hygiene1
|
||||
quote do: var!(a, Hygiene1) = 2
|
||||
|
||||
Another option is to set the `var_context` option, affecting
|
||||
all variables in the block:
|
||||
Hygiene for variables can be disabled overall as:
|
||||
|
||||
quote var_context: Hygiene1, do: a = 2
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
### Hygiene in aliases
|
||||
|
||||
Aliases inside quote are expanded by default.
|
||||
Aliases inside quote are hygienic by default.
|
||||
Consider the following example:
|
||||
|
||||
defmodule Hygiene do
|
||||
@@ -470,24 +511,43 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference #=> HashDict[]
|
||||
Hygiene.no_interference #=> #HashDict<[]>
|
||||
|
||||
Notice that, even though the alias `D` is not available
|
||||
in the context the macro is expanded, the code above works
|
||||
because `D` was expanded when the quote was generated.
|
||||
because `D` still expands to `HashDict`.
|
||||
|
||||
There are two ways to disable this behaviour. By giving
|
||||
the `expand_aliases` equals to false to quote or by using
|
||||
the `alias!` macro inside the quote:
|
||||
In some particular cases you may want to access an alias
|
||||
or a module defined in the caller. In such scenarios, you
|
||||
can access it by disabling hygiene with `hygiene: [aliases: false]`
|
||||
or by using the `alias!` macro inside the quote:
|
||||
|
||||
defmodule NoHygiene do
|
||||
defmodule Hygiene do
|
||||
# This will expand to Elixir.Nested.hello
|
||||
defmacro no_interference do
|
||||
quote do: Nested.hello
|
||||
end
|
||||
|
||||
# This will expand to Nested.hello for
|
||||
# whatever is Nested in the caller
|
||||
defmacro interference do
|
||||
quote do: alias!(D).new
|
||||
quote do: alias!(Nested).hello
|
||||
end
|
||||
end
|
||||
|
||||
require NoHygiene
|
||||
NoHygiene.interference #=> UndefinedFunctionError
|
||||
defmodule Parent do
|
||||
defmodule Nested do
|
||||
def hello, do: "world"
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference
|
||||
#=> ** (UndefinedFunctionError) ...
|
||||
|
||||
Hygiene.interference
|
||||
#=> "world"
|
||||
end
|
||||
|
||||
|
||||
## Hygiene in imports
|
||||
|
||||
@@ -536,7 +596,7 @@ defmodule Kernel.SpecialForms do
|
||||
Lazy.return_size #=> 2
|
||||
|
||||
As in aliases, imports expansion can be explicitly disabled
|
||||
via the `expand_imports` option.
|
||||
via the `hygiene: [imports: false]` option.
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
@@ -570,7 +630,7 @@ defmodule Kernel.SpecialForms do
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
"""
|
||||
defmacro quote(opts, do: contents)
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should
|
||||
@@ -617,7 +677,8 @@ defmodule Kernel.SpecialForms do
|
||||
#=> { :sum, [], [1, 13, 3] }
|
||||
|
||||
"""
|
||||
defmacro unquote(expr)
|
||||
name = :unquote
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
Unquotes the given list expanding its arguments. Similar
|
||||
@@ -630,46 +691,47 @@ defmodule Kernel.SpecialForms do
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
"""
|
||||
defmacro unquote_splicing(expr)
|
||||
name = :unquote_splicing
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
lc n inlist [1,2,3,4], do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
are defined using both `inlist` and `inbits` operators, allowing you
|
||||
to loop lists and bitstrings:
|
||||
|
||||
# A list generator:
|
||||
lc n inlist [1,2,3,4], do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
list = [1,2,3,4]
|
||||
lc n inlist list, do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> list = [1,2,3,4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A comprehension with two generators
|
||||
lc x inlist [1,2], y inlist [2,3], do: x*y
|
||||
#=> [2,3,4,6]
|
||||
iex> lc x inlist [1,2], y inlist [2,3], do: x*y
|
||||
[2,3,4,6]
|
||||
|
||||
Filters can also be given:
|
||||
|
||||
# A comprehension with a generator and a filter
|
||||
lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
|
||||
#=> [2,4,6]
|
||||
iex> lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
|
||||
[2,4,6]
|
||||
|
||||
Bit string generators are quite useful when you need to
|
||||
organize bit string streams:
|
||||
|
||||
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
|
||||
iex> lc <<r:8,g:8,b:8>> inbits pixels, do: {r,g,b}
|
||||
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}]
|
||||
|
||||
"""
|
||||
@@ -681,7 +743,7 @@ defmodule Kernel.SpecialForms do
|
||||
be a bitstring. For example, here is how to remove all
|
||||
spaces from a string:
|
||||
|
||||
bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
"helloworld"
|
||||
|
||||
"""
|
||||
@@ -692,8 +754,8 @@ defmodule Kernel.SpecialForms do
|
||||
of expressions in Elixir. This special form is private
|
||||
and should not be invoked directly:
|
||||
|
||||
quote do: (1; 2; 3)
|
||||
#=> { :__block__, [], [1,2,3] }
|
||||
iex> quote do: (1; 2; 3)
|
||||
{ :__block__, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
@@ -705,7 +767,7 @@ defmodule Kernel.SpecialForms do
|
||||
it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
|
||||
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
"""
|
||||
@@ -715,13 +777,13 @@ defmodule Kernel.SpecialForms do
|
||||
This is the special form used to hold aliases information.
|
||||
It is usually compiled to an atom:
|
||||
|
||||
quote do: Foo.Bar
|
||||
quote do: Foo.Bar #=>
|
||||
{ :__aliases__, [], [:Foo,:Bar] }
|
||||
|
||||
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
|
||||
unambiguously identified by the operator `:.`. For example:
|
||||
|
||||
quote do: Foo.bar
|
||||
quote do: Foo.bar #=>
|
||||
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
|
||||
|
||||
Whenever an expression iterator sees a `:.` as the tuple key,
|
||||
@@ -748,4 +810,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
@doc """
|
||||
Calls the overriden function when overriding it with `defoverridable`.
|
||||
See `Kernel.defoverridable` for more information and documentation.
|
||||
"""
|
||||
defmacro super(args)
|
||||
end
|
||||
|
||||
@@ -46,9 +46,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
|
||||
### Functions
|
||||
### Anonymous functions
|
||||
|
||||
Any function:
|
||||
Any anonymous function:
|
||||
|
||||
(fun(...) -> any)
|
||||
or
|
||||
@@ -56,13 +56,13 @@ defmodule Kernel.Typespec do
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Function with arity of zero:
|
||||
Anonymous function with arity of zero:
|
||||
|
||||
(fun() -> type)
|
||||
or
|
||||
(() -> type)
|
||||
|
||||
Function with some arity:
|
||||
Anonymous function with some arity:
|
||||
|
||||
(fun(type, type) -> type)
|
||||
or
|
||||
@@ -75,7 +75,7 @@ defmodule Kernel.Typespec do
|
||||
Elixir discourages the use of type `string()` as it might be confused
|
||||
with binaries which are referred to as "strings" in Elixir (as opposed to
|
||||
character lists). In order to use the type that is called `string()` in Erlang,
|
||||
one has to use the `char_list()` type which is a synonym to `string()`. If yu
|
||||
one has to use the `char_list()` type which is a synonym for `string()`. If you
|
||||
use `string()`, you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated by functions in the
|
||||
@@ -279,7 +279,7 @@ defmodule Kernel.Typespec do
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
List.member?(exported_types, { name, length(args) }) -> { :type, type }
|
||||
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
end
|
||||
end
|
||||
@@ -325,7 +325,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
[]
|
||||
@@ -333,9 +333,9 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
case :code.get_object_code(module) do
|
||||
{ ^module, beam, _filename } -> beam
|
||||
:error -> module
|
||||
end
|
||||
end
|
||||
|
||||
@@ -350,10 +350,16 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, type, caller) do
|
||||
def deftype(kind, {name, _meta, args} = type, caller)
|
||||
when is_atom(name) and not is_list(args) do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, _caller) do
|
||||
type_spec = Macro.to_binary(other)
|
||||
raise ArgumentError, message: "invalid type specification #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
@@ -390,6 +396,11 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, _caller) do
|
||||
spec = Macro.to_binary(other)
|
||||
raise ArgumentError, message: "invalid function type specification #{spec}"
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
|
||||
line = line(meta)
|
||||
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
|
||||
@@ -411,6 +422,17 @@ defmodule Kernel.Typespec do
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
|
||||
case unpack_typespec_kw(arg, []) do
|
||||
{ :ok, ast } -> ast
|
||||
:error -> [typespec_to_ast(arg)]
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, args }) do
|
||||
lc arg inlist args, do: typespec_to_ast(arg)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
@@ -462,11 +484,15 @@ defmodule Kernel.Typespec do
|
||||
{ var, line, nil }
|
||||
end
|
||||
|
||||
# special shortcut(s)
|
||||
# Special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
@@ -508,7 +534,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
|
||||
exprs = :lists.reverse(collect_union(exprs))
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
@@ -566,7 +592,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle access macro
|
||||
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
|
||||
access = {{:., meta, [Kernel, :access]}, meta1,
|
||||
[target, args ++ [_: (quote hygiene: false, do: any)]]}
|
||||
[target, args ++ [_: { :any, [], [] }]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
@@ -591,9 +617,14 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
end
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if List.member?(vars, name) do
|
||||
if :lists.member(name, vars) do
|
||||
{ :var, line(meta), name }
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
@@ -612,6 +643,10 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), name, arguments }
|
||||
@@ -638,8 +673,11 @@ defmodule Kernel.Typespec do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(l, _, _) when is_list(l) do
|
||||
raise ArgumentError, message: "Unexpected list #{inspect l}"
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l)] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
@@ -657,6 +695,11 @@ 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"
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
case [fn_args(meta, args, vars, caller), typespec(return, vars, caller)] do
|
||||
[{:type,_,:any},{:type,_,:any,[]}] -> []
|
||||
@@ -676,4 +719,19 @@ defmodule Kernel.Typespec do
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :union, [
|
||||
next,
|
||||
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
|
||||
] }, acc) do
|
||||
unpack_typespec_kw(next, [{atom,typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
|
||||
{ :ok, [{atom,typespec_to_ast(type)}|acc] }
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
+95
-49
@@ -2,7 +2,7 @@ defmodule Keyword do
|
||||
@moduledoc """
|
||||
A keyword is a list of tuples where the first element
|
||||
of the tuple is an atom and the second element can be
|
||||
any value.
|
||||
any value.
|
||||
|
||||
A keyword may have duplicated keys, so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
@@ -36,10 +36,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir-' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
keyword?(rest)
|
||||
end
|
||||
|
||||
def keyword?([]), do: true
|
||||
@@ -59,8 +56,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.new [{:b,1},{:a,2}]
|
||||
#=> [a: 2, b: 1]
|
||||
iex> Keyword.new([{:b,1},{:a,2}])
|
||||
[a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: t
|
||||
@@ -77,8 +74,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.new [:a, :b], fn x -> {x,x} end
|
||||
#=> [a: :a, b: :b]
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x,x} end) |> Enum.sort
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
|
||||
@@ -100,9 +97,12 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get [a: 1], :a #=> 1
|
||||
Keyword.get [a: 1], :b #=> nil
|
||||
Keyword.get [a: 1], :b, 3 #=> 3
|
||||
iex> Keyword.get([a: 1], :a)
|
||||
1
|
||||
iex> Keyword.get([a: 1], :b)
|
||||
nil
|
||||
iex> Keyword.get([a: 1], :b, 3)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@@ -115,30 +115,54 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for specific key. If key does not exist,
|
||||
Fetchs the value for specific key and return it in a tuple.
|
||||
If the key does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.fetch([a: 1], :a)
|
||||
{ :ok, 1 }
|
||||
|
||||
iex> Keyword.fetch([a: 1], :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(keywords, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific key. If key does not exist,
|
||||
an error is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get! [a: 1], :a #=> 1
|
||||
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
|
||||
iex> Keyword.fetch!([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec get!(t, key) :: value | no_return
|
||||
def get!(keywords, key) when is_atom(key) do
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(keywords, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get_values [a: 1, a: 2], :a
|
||||
#=> [1,2]
|
||||
iex> Keyword.get_values([a: 1, a: 2], :a)
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
@@ -152,7 +176,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
|
||||
iex> Keyword.keys([a: 1, b: 2])
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
@@ -165,7 +190,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.values [a: 1, b: 2] #=> [1,2]
|
||||
iex> Keyword.values([a: 1, b: 2])
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
@@ -181,8 +207,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
|
||||
Keyword.delete [b: 2], :a #=> [b: 2]
|
||||
iex> Keyword.delete([a: 1, b: 2], :a)
|
||||
[b: 2]
|
||||
iex> Keyword.delete([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
@@ -190,6 +218,23 @@ defmodule Keyword do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
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.
|
||||
|
||||
## 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]
|
||||
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_atom(key) do
|
||||
:lists.keydelete(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
@@ -198,8 +243,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.put [a: 1, b: 2], :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
iex> Keyword.put([a: 1, b: 2], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
@@ -213,8 +258,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.put_new [a: 1, b: 2], :a, 3
|
||||
#=> [a: 1, b: 2]
|
||||
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
@@ -231,8 +276,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
#=> true
|
||||
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
|
||||
true
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
@@ -246,8 +291,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
#=> [a:3, b:2, d: 4]
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4]) |> Enum.sort
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
@@ -261,10 +306,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a:4, b:2, d: 4]
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
iex> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
@@ -285,10 +330,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.has_key?([a: 1], :a)
|
||||
#=> true
|
||||
Keyword.has_key?([a: 1], :b)
|
||||
#=> false
|
||||
iex> Keyword.has_key?([a: 1], :a)
|
||||
true
|
||||
iex> Keyword.has_key?([a: 1], :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
@@ -302,10 +347,11 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, &1 * 2)
|
||||
#=> KeyError
|
||||
iex> Keyword.update([a: 1], :a, &1 * 2)
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, &1 * 2)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec update(t, key, (value -> value)) :: t | no_return
|
||||
@@ -327,13 +373,13 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
#=> [a: 1, b: 11]
|
||||
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
[a: 2]
|
||||
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
[a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
def update([{key, value}|keywords], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
+100
-78
@@ -15,8 +15,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.concat [[1,[2],3], [4], [5,6]]
|
||||
#=> [1,[2],3,4,5,6]
|
||||
iex> List.concat([[1,[2],3], [4], [5,6]])
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
@@ -32,8 +32,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.concat [1,2,3], [4,5,6]
|
||||
#=> [1,2,3,4,5,6]
|
||||
iex> List.concat([1,2,3], [4,5,6])
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat(list, elements) when is_list(list) and is_list(elements) do
|
||||
@@ -47,8 +47,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.delete([1,2,3], 1)
|
||||
#=> [2,3]
|
||||
iex> List.delete([1,2,3], 1)
|
||||
[2,3]
|
||||
|
||||
"""
|
||||
def delete(list, item) do
|
||||
@@ -60,11 +60,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.duplicate "hello", 3
|
||||
#=> ["hello","hello","hello"]
|
||||
iex> List.duplicate("hello", 3)
|
||||
["hello","hello","hello"]
|
||||
|
||||
List.duplicate [1,2], 2
|
||||
#=> [[1,2],[1,2]]
|
||||
iex> List.duplicate([1,2], 2)
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
:lists.duplicate(n, elem)
|
||||
@@ -77,11 +77,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.flatten [1,[[2],3]]
|
||||
#=> [1,2,3]
|
||||
iex> List.flatten([1,[[2],3]])
|
||||
[1,2,3]
|
||||
|
||||
List.flatten [1,[[2],3]], [4,5]
|
||||
#=> [1,2,3,4,5]
|
||||
iex> List.flatten([1,[[2],3]], [4,5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
def flatten(list) do
|
||||
@@ -98,11 +98,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.foldl [5,5], 10, fn x, acc -> x + acc end
|
||||
#=> 20
|
||||
iex> List.foldl([5,5], 10, fn (x, acc) -> x + acc end)
|
||||
20
|
||||
|
||||
List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
#=> 2
|
||||
iex> List.foldl([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
2
|
||||
|
||||
"""
|
||||
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
||||
@@ -115,8 +115,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
#=> -2
|
||||
iex> List.foldr([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
-2
|
||||
|
||||
"""
|
||||
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
||||
@@ -128,12 +128,12 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.last []
|
||||
#=> nil
|
||||
List.last [1]
|
||||
#=> 1
|
||||
List.last [1, 2, 3]
|
||||
#=> 3
|
||||
iex> List.last([])
|
||||
nil
|
||||
iex> List.last([1])
|
||||
1
|
||||
iex> List.last([1, 2, 3])
|
||||
3
|
||||
|
||||
"""
|
||||
def last([]), do: nil
|
||||
@@ -142,39 +142,21 @@ defmodule List do
|
||||
:lists.last(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `term` is included in the list.
|
||||
This function simply delegates to `lists:member`
|
||||
which is implemented in C for performance.
|
||||
|
||||
## Examples
|
||||
|
||||
List.member? [1,2,3], 1
|
||||
#=> true
|
||||
|
||||
List.member? [1,2,3], 0
|
||||
#=> false
|
||||
|
||||
"""
|
||||
def member?(list, term) do
|
||||
:lists.member(term, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
where the item at position `posistion` matches with the
|
||||
where the item at position `position` matches with the
|
||||
given `item`.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyfind([a: 1, b: 2], :a, 0)
|
||||
#=> { :a, 1 }
|
||||
iex> List.keyfind([a: 1, b: 2], :a, 0)
|
||||
{ :a, 1 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], 2, 1)
|
||||
#=> { :b, 2 }
|
||||
iex> List.keyfind([a: 1, b: 2], 2, 1)
|
||||
{ :b, 2 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], :c, 0)
|
||||
#=> nil
|
||||
iex> List.keyfind([a: 1, b: 2], :c, 0)
|
||||
nil
|
||||
|
||||
"""
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
@@ -183,19 +165,19 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns true if there is
|
||||
a tuple where the item at position `posistion` matches
|
||||
a tuple where the item at position `position` matches
|
||||
with the given `item`.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keymember?([a: 1, b: 2], :a, 0)
|
||||
#=> true
|
||||
iex> List.keymember?([a: 1, b: 2], :a, 0)
|
||||
true
|
||||
|
||||
List.keymember?([a: 1, b: 2], 2, 1)
|
||||
#=> true
|
||||
iex> List.keymember?([a: 1, b: 2], 2, 1)
|
||||
true
|
||||
|
||||
List.keymember?([a: 1, b: 2], :c, 0)
|
||||
#=> false
|
||||
iex> List.keymember?([a: 1, b: 2], :c, 0)
|
||||
false
|
||||
|
||||
"""
|
||||
def keymember?(list, key, position) do
|
||||
@@ -208,8 +190,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keyreplace(list, key, position, new_tuple) do
|
||||
@@ -223,8 +205,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
@@ -233,19 +215,19 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and deletes the first tuple
|
||||
where the item at position `posistion` matches with the
|
||||
where the item at position `position` matches with the
|
||||
given `item`. Returns the new tuple.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keydelete([a: 1, b: 2], :a, 0)
|
||||
#=> [{ :b, 2 }]
|
||||
iex> List.keydelete([a: 1, b: 2], :a, 0)
|
||||
[{ :b, 2 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], 2, 1)
|
||||
#=> [{ :a, 1 }]
|
||||
iex> List.keydelete([a: 1, b: 2], 2, 1)
|
||||
[{ :a, 1 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], :c, 0)
|
||||
#=> [{ :a, 1 }, { :b, 2 }]
|
||||
iex> List.keydelete([a: 1, b: 2], :c, 0)
|
||||
[{ :a, 1 }, { :b, 2 }]
|
||||
|
||||
"""
|
||||
def keydelete(list, key, position) do
|
||||
@@ -259,7 +241,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.wrap [1,2,3] #=> [1,2,3]
|
||||
iex> List.wrap([1,2,3])
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
def wrap(list) when is_list(list) do
|
||||
@@ -279,11 +262,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.zip [[1, 2], [3, 4], [5, 6]]
|
||||
#=> [{1, 3, 5}, {2, 4, 6}]
|
||||
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
||||
[{1, 3, 5}, {2, 4, 6}]
|
||||
|
||||
List.zip [[1, 2], [3], [5, 6]]
|
||||
#=> [{1, 3, 5}]
|
||||
iex> List.zip([[1, 2], [3], [5, 6]])
|
||||
[{1, 3, 5}]
|
||||
|
||||
"""
|
||||
def zip([]), do: []
|
||||
@@ -297,19 +280,58 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.unzip [{1, 2}, {3, 4}]
|
||||
#=> [[1, 3], [2, 4]]
|
||||
iex> List.unzip([{1, 2}, {3, 4}])
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
|
||||
#=> [[1, 2, 3], [:a, :b, :c]]
|
||||
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map tuple_to_list(&1), zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with an inserted value at specified index. Note that the index
|
||||
is capped at the list length and that negative indicies wraps around at the
|
||||
end of the list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.insert_at([1, 2, 3, 4], 2, 0)
|
||||
[1, 2, 0, 3, 4]
|
||||
|
||||
iex> List.insert_at([1, 2, 3], 10, 0)
|
||||
[1, 2, 3, 0]
|
||||
|
||||
iex> List.insert_at([1, 2, 3], -1, 0)
|
||||
[1, 2, 0, 3]
|
||||
|
||||
"""
|
||||
def insert_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_insert_at(list, length(list) + index, value)
|
||||
else
|
||||
do_insert_at(list, index, value)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# insert_at
|
||||
|
||||
defp do_insert_at([], _index, value) do
|
||||
[ value ]
|
||||
end
|
||||
|
||||
defp do_insert_at(list, index, value) when index <= 0 do
|
||||
[ value | list ]
|
||||
end
|
||||
|
||||
defp do_insert_at([h|t], index, value) do
|
||||
[ h | do_insert_at(t, index - 1, value) ]
|
||||
end
|
||||
|
||||
# zip
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
|
||||
@@ -27,6 +27,18 @@ defimpl List.Chars, for: List do
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Number do
|
||||
def to_char_list(integer) when is_integer(integer), do: integer_to_list(integer)
|
||||
def to_char_list(float) when is_float(float), do: float_to_list(float)
|
||||
end
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when is_integer(thing) do
|
||||
integer_to_list(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule List.Dict do
|
||||
defmodule ListDict do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on lists of two-items tuples.
|
||||
|
||||
@@ -7,19 +7,19 @@ defmodule List.Dict do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns a new `List.Dict`, i.e. an empty list.
|
||||
Returns a new `ListDict`, i.e. an empty list.
|
||||
"""
|
||||
def new, do: []
|
||||
|
||||
@doc """
|
||||
Creates a new `List.Dict` from the given pairs.
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.map pairs, fn({ k, v }) -> { k, v } end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new `List.Dict` from the given pairs
|
||||
Creates a new `ListDict` from the given pairs
|
||||
via the given transformation function.
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
@@ -65,17 +65,36 @@ defmodule List.Dict 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 }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
raises `KeyError` if the key does not exist.
|
||||
"""
|
||||
def get!(dict, key) do
|
||||
def fetch!(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
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.
|
||||
"""
|
||||
@@ -112,6 +131,39 @@ defmodule List.Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
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
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, keys) 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.
|
||||
"""
|
||||
@@ -144,10 +196,17 @@ defmodule List.Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty `List.Dict`.
|
||||
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.
|
||||
"""
|
||||
+68
-55
@@ -42,7 +42,6 @@ defmodule Macro do
|
||||
|
||||
extract_args(quote do: foo) == { :foo, [] }
|
||||
extract_args(quote do: foo()) == { :foo, [] }
|
||||
extract_args(quote do: :foo.()) == { :foo, [] }
|
||||
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
|
||||
extract_args(quote do: 1.(1,2,3)) == :error
|
||||
|
||||
@@ -52,32 +51,27 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recursively escapes the given value so it can be inserted
|
||||
into a syntax tree. Structures that are valid syntax nodes
|
||||
(like atoms, integers, binaries) are represented by themselves.
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.escape(:foo)
|
||||
#=> :foo
|
||||
iex> Macro.escape(:foo)
|
||||
:foo
|
||||
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
|
||||
Macro.escape({ :a, :b, :c })
|
||||
#=> { :{}, [], [:a, :b, :c] }
|
||||
"""
|
||||
def escape({ left, right }) do
|
||||
{ escape(left), escape(right) }
|
||||
def escape(expr) do
|
||||
:elixir_quote.escape(expr, false) |> elem(0)
|
||||
end
|
||||
|
||||
def escape(tuple) when is_tuple(tuple) do
|
||||
{ :{}, [], escape(tuple_to_list(tuple)) }
|
||||
@doc false
|
||||
def escape_quoted(expr) do
|
||||
:elixir_quote.escape(expr, true) |> elem(0)
|
||||
end
|
||||
|
||||
def escape(list) when is_list(list) do
|
||||
lc item inlist list, do: escape(item)
|
||||
end
|
||||
|
||||
def escape(other), do: other
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
@@ -93,8 +87,8 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.unescape_binary "example\\n"
|
||||
#=> "example\n"
|
||||
iex> Macro.unescape_binary("example\\n")
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
@@ -105,8 +99,8 @@ defmodule Macro do
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars according to the map given.
|
||||
Check `unescape/1` if you want to use the same map as
|
||||
Elixir single- and double-quoted strings.
|
||||
Check `unescape_binary/1` if you want to use the same map
|
||||
as Elixir single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
|
||||
@@ -152,11 +146,13 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
Check `unescape/1` and `unescape/2` for more information
|
||||
about unescaping. Only tokens that are binaries are
|
||||
unescaped, all others are ignored. This function is useful
|
||||
when implementing your own sigils. Check the implementation
|
||||
of `Kernel.__b__` for examples.
|
||||
Check `unescape_binary/1` and `unescape_binary/2` for more
|
||||
information about unescaping.
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
ignored. This function is useful when implementing your own
|
||||
sigils. Check the implementation of `Kernel.__b__`
|
||||
for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
:elixir_interpolation.unescape_tokens(tokens)
|
||||
@@ -164,7 +160,7 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
Check `unescape_tokens/1` and `unescaped/2` for more information.
|
||||
Check `unescape_tokens/1` and `unescape_binary/2` for more information.
|
||||
"""
|
||||
def unescape_tokens(tokens, map) do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
@@ -175,8 +171,8 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
#=> "foo.bar(1, 2, 3)"
|
||||
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
"foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_binary(tree)
|
||||
@@ -275,7 +271,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
|
||||
def to_binary(other), do: inspect(other, raw: true)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@@ -285,7 +281,7 @@ defmodule Macro do
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
|
||||
defp module_to_binary(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_binary(other), do: call_to_binary(other)
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
@@ -366,9 +362,12 @@ defmodule Macro do
|
||||
In case the expression cannot be expanded, it returns the expression itself.
|
||||
|
||||
Notice that `Macro.expand` is not recursive and it does not
|
||||
expand child expressions. For example, `!some_macro` will expand as:
|
||||
expand child expressions. In this example
|
||||
|
||||
Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
|
||||
`!some_macro` will expand to something like:
|
||||
|
||||
iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
case some_macro do
|
||||
false -> true
|
||||
nil -> true
|
||||
@@ -440,13 +439,15 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand(aliases, env)
|
||||
def expand(aliases, env) do
|
||||
expand(aliases, env, nil)
|
||||
end
|
||||
|
||||
def expand({ :__aliases__, _, _ } = original, env) do
|
||||
case :elixir_aliases.expand(original, env.aliases) do
|
||||
defp expand({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
atom when is_atom(atom) -> atom
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env)
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
@@ -456,7 +457,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> Module.get_attribute(module, name)
|
||||
false -> original
|
||||
@@ -464,12 +465,13 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
|
||||
def expand({ :__FILE__, _, atom }, env) when is_atom(atom), do: env.file
|
||||
def expand({ :__ENV__, _, atom }, env) when is_atom(atom), do: env
|
||||
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
|
||||
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
|
||||
defp expand({ :__DIR__, _, atom }, env, _cache) when is_atom(atom), do: :filename.dirname(env.file)
|
||||
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
|
||||
|
||||
# Expand possible macro import invocation
|
||||
def expand({ atom, line, args } = original, env) when is_atom(atom) do
|
||||
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
@@ -478,8 +480,16 @@ defmodule Macro do
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
module = env.module
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, env.function, env.requires, env.macros, env)
|
||||
env.module, extra, to_erl_env(env, cache))
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
@@ -488,23 +498,26 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
|
||||
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
receiver = expand(left, env)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, env.function, env.requires, env)
|
||||
args, env.module, to_erl_env(env, cache))
|
||||
case expand do
|
||||
{ :ok, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _receiver, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
def expand(other, _env), do: other
|
||||
defp expand(other, _env, _cache), do: other
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -522,16 +535,16 @@ defmodule Macro do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
defp do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
def do_safe_term(_), do: nil
|
||||
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
@@ -19,23 +19,27 @@ defmodule Macro.Env do
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `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
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{ module, module }]
|
||||
@type context :: :assign | :guard | nil
|
||||
@type context :: :match | :guard | nil
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
|
||||
fields = [:module, :file, :line, :function,
|
||||
:aliases, :context, :requires, :functions, :macros]
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros]
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
@@ -58,7 +62,7 @@ defmodule Macro.Env do
|
||||
Returns wether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :assign
|
||||
def in_match?(record), do: context(record) == :match
|
||||
|
||||
@doc """
|
||||
Returns the environment stacktrace.
|
||||
|
||||
+336
-51
@@ -7,16 +7,296 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
@moduledoc %B'''
|
||||
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.
|
||||
|
||||
After the module is compiled, using many of the functions in
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their purpose
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
|
||||
## Module attributes
|
||||
|
||||
Each module can be decorated with one or more attributes. The following ones
|
||||
are currently defined by Elixir:
|
||||
|
||||
* `@after_compile`
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take two arguments: the module environment and its bytecode.
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__after_compile__/2`.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@after_compile __MODULE__
|
||||
|
||||
def __after_compile__(env, _bytecode) do
|
||||
IO.inspect env
|
||||
end
|
||||
end
|
||||
|
||||
* `@before_compile`
|
||||
|
||||
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
|
||||
macro, its returned value will be injected at the end of the module definition
|
||||
before the compilation starts.
|
||||
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@before_compile __MODULE__
|
||||
|
||||
defmacro __before_compile__(_env) do
|
||||
quote do
|
||||
def hello, do: "world"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@behaviour gen_event
|
||||
|
||||
# ...
|
||||
end
|
||||
|
||||
* `@compile`
|
||||
|
||||
Define options for module compilation that are passed to the Erlang
|
||||
compiler.
|
||||
|
||||
Accepts an atom, a tuple, or a list of atoms and tuples.
|
||||
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
|
||||
def myfun(arg) do
|
||||
to_binary(arg)
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
|
||||
Provide documentation for the function or macro that follows the
|
||||
attribute.
|
||||
|
||||
Accepts a string (often a heredoc) or `false` where `@doc false` will
|
||||
make the function/macro invisible to the documentation extraction tools
|
||||
like ExDoc.
|
||||
|
||||
Can be invoked more than once.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sum.
|
||||
"""
|
||||
def sum(a, b) do
|
||||
a + b
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
|
||||
Change the filename used in stacktraces for the function or macro that
|
||||
follows the attribute.
|
||||
|
||||
Accepts a string. Can be used more than once.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@file "hello.ex"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
|
||||
Provide documentation for the current module.
|
||||
|
||||
Accepts a string (which is often a heredoc) or `false` where
|
||||
`@moduledoc false` will make the module invisible to the
|
||||
documentation extraction tools like ExDoc.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
A very useful module
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
* `@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.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take 6 arguments:
|
||||
|
||||
- the module environment
|
||||
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
|
||||
- function/macro name
|
||||
- list of quoted arguments
|
||||
- list of quoted guards
|
||||
- quoted function body
|
||||
|
||||
If the function/macro being defined has multiple clauses, the hook will
|
||||
be called for each clause.
|
||||
|
||||
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.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
IO.puts "Defining #{kind} named #{name} with args:"
|
||||
IO.inspect args
|
||||
IO.puts "and guards"
|
||||
IO.inspect guards
|
||||
IO.puts "and body"
|
||||
IO.puts Macro.to_binary(body)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition { H, :on_def }
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_binary(arg)
|
||||
end
|
||||
|
||||
def hello(_) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
|
||||
A hook that will be invoked whenever the module is loaded.
|
||||
|
||||
Accepts a function atom of a function in the current module. The function
|
||||
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
|
||||
loading of the module will be aborted.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
|
||||
def load_check do
|
||||
if some_condition() do
|
||||
:ok
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def some_condition do
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behavior callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
`@` and followed by a valid Elixir value:
|
||||
|
||||
defmodule M do
|
||||
@custom_attr [some: "stuff"]
|
||||
end
|
||||
|
||||
For more advanced options available when defining custom attributes, see
|
||||
`register_attribute/3`.
|
||||
|
||||
## Runtime information about a module
|
||||
|
||||
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 """
|
||||
Provides runtime information about functions and macros defined by the
|
||||
module, enables docstring extraction, etc.
|
||||
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
|
||||
more information.
|
||||
"""
|
||||
def __info__(kind)
|
||||
|
||||
@doc """
|
||||
Check if a module is open, i.e. it is currently being defined
|
||||
@@ -28,10 +308,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
Check `Code.eval` for more information.
|
||||
See `Code.eval_string` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -116,9 +396,12 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.concat [Foo, Bar] #=> Foo.Bar
|
||||
Module.concat [Foo, "Bar"] #=> Foo.Bar
|
||||
Module.concat [Foo, 'Bar'] #=> Foo.Bar
|
||||
iex> Module.concat([Foo, Bar])
|
||||
Foo.Bar
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
iex> Module.concat([Foo, 'Bar'])
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
@@ -131,9 +414,12 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.concat Foo, Bar #=> Foo.Bar
|
||||
Module.concat Foo, "Bar" #=> Foo.Bar
|
||||
Module.concat Foo, 'Bar' #=> Foo.Bar
|
||||
iex> Module.concat(Foo, Bar)
|
||||
Foo.Bar
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
iex> Module.concat(Foo, 'Bar')
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
def concat(left, right) do
|
||||
@@ -148,11 +434,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.safe_concat [Unknown, Module]
|
||||
#=> ArgumentError
|
||||
iex> Module.safe_concat([Unknown, Module])
|
||||
** (ArgumentError) argument error
|
||||
|
||||
Module.safe_concat [List, Chars]
|
||||
#=> List.Chars
|
||||
iex> Module.safe_concat([List, Chars])
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
def safe_concat(list) when is_list(list) do
|
||||
@@ -167,11 +453,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.safe_concat Unknown, Module
|
||||
#=> ArgumentError
|
||||
iex> Module.safe_concat(Unknown, Module)
|
||||
** (ArgumentError) argument error
|
||||
|
||||
Module.safe_concat List, Chars
|
||||
#=> List.Chars
|
||||
iex> Module.safe_concat(List, Chars)
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
def safe_concat(left, right) do
|
||||
@@ -210,17 +496,15 @@ defmodule Module do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] ->
|
||||
ETS.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
doc || old_doc
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
_ ->
|
||||
{ :error, :existing_doc }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -314,7 +598,7 @@ defmodule Module do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case ETS.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _, _ }] -> true
|
||||
[{ _, ^kind, _, _, _, _, _ }] -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
@@ -333,7 +617,7 @@ defmodule Module do
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
lc { tuple, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -352,30 +636,33 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, stored_kind, _, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it.
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
more information and documentation.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
table = function_table_for(module)
|
||||
|
||||
lc tuple inlist tuples do
|
||||
case ETS.lookup(table, tuple) do
|
||||
[clause] ->
|
||||
ETS.delete(table, tuple)
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, clause, false } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
_ ->
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{ name, arity } = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
neighbours = Module.DispatchTracker.yank(module, tuple)
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
{ count + 1, clause, neighbours, false }
|
||||
end, { 1, clause, neighbours, false }, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -406,7 +693,7 @@ defmodule Module do
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
new =
|
||||
if List.member?(acc, key) do
|
||||
if :lists.member(key, acc) do
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
[] -> [value]
|
||||
@@ -443,7 +730,7 @@ defmodule Module do
|
||||
[{^key,old}] -> old
|
||||
[] ->
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
if List.member?(acc, key), do: [], else: nil
|
||||
if :lists.member(key, acc), do: [], else: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -474,7 +761,8 @@ defmodule Module do
|
||||
When registering an attribute, two options can be given:
|
||||
|
||||
* `:accumulate` - Several calls to the same attribute will
|
||||
accumulate instead of override the previous one;
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
@@ -521,19 +809,18 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "-"))
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a module name to binary without the Elixir prefix.
|
||||
"""
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
end
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
def to_binary(module) do
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
@@ -550,8 +837,6 @@ defmodule Module do
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
{ :error, :existing_doc } ->
|
||||
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -607,11 +892,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
list_to_atom :lists.concat([:f, module])
|
||||
:elixir_def.table(module)
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
list_to_atom :lists.concat([:o, module])
|
||||
:elixir_module.docs_table(module)
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# calls in order to extract Elixir modules' behaviour
|
||||
# during compilation time.
|
||||
#
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with three main
|
||||
# vertices:
|
||||
#
|
||||
# * `:local` - points to local functions
|
||||
# * `:import` - points to imported modules
|
||||
# * `:warn` - points to imported modules that should be warned
|
||||
# * `:remote` - points to remote modules
|
||||
#
|
||||
# Besides those, we have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{ :remote, name, arity }` - an remotely invoked function/arity
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `:import`, `:remote`, `:warn`,
|
||||
# `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `:warn`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# * `{ :remote, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Module.DispatchTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@type name_arity :: { name, arity }
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type remote :: { :remote, name, arity }
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Receives a dispatch or a module and returns all dispatches
|
||||
that calls it.
|
||||
|
||||
In case the argument is a module, the response will be
|
||||
made by import and remote dispatches.
|
||||
|
||||
In case the argument is another dispatch, the response
|
||||
will be made by local dispatches.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, A does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_to(ref, module) :: [import | remote]
|
||||
@spec dispatches_to(ref, local | import | remote) :: [local]
|
||||
def dispatches_to(ref, dispatch) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.in_neighbours(d, dispatch) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a local and returns all dispatches from that local.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, C does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_from(ref, local) :: [local | import | remote]
|
||||
def dispatches_from(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were imported.
|
||||
All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec imports(ref) :: [module]
|
||||
def imports(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :import)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
"""
|
||||
@spec imports_with_dispatch(ref, name_arity) :: [module]
|
||||
def imports_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched
|
||||
to. All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec remotes(ref) :: [module]
|
||||
def remotes(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :remote)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
"""
|
||||
@spec remotes_with_dispatch(ref, name_arity) :: [module]
|
||||
def remotes_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :remote, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all locals that are reachable.
|
||||
|
||||
By default, all public functions are reachable.
|
||||
A private function is only reachable if it has
|
||||
a public function that it invokes directly.
|
||||
"""
|
||||
@spec reachable(ref) :: [local]
|
||||
def reachable(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
reduce_reachable(d, :local, [])
|
||||
end
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
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 only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
# Adds a definition into the tracker. A public
|
||||
# definition is connected with the :local node
|
||||
# while a private one is left unreachable until
|
||||
# a call is made to.
|
||||
@doc false
|
||||
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_definition, kind, tuple })
|
||||
end
|
||||
|
||||
# Adds and tracks defaults for a definition into the tracker.
|
||||
@doc false
|
||||
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
|
||||
end
|
||||
|
||||
# Adds a local dispatch to the given target.
|
||||
def add_local(pid, to) when is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, :local, to })
|
||||
end
|
||||
|
||||
# Adds a local dispatch from-to the given target.
|
||||
@doc false
|
||||
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
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 })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
end
|
||||
|
||||
# Associates a module with a warn. This adds the given
|
||||
# module and associates it with the `:import` vertex
|
||||
# permanently, even if warn is false.
|
||||
@doc false
|
||||
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
|
||||
:gen_server.cast(pid, { :add_warnable, module, warn, line })
|
||||
end
|
||||
|
||||
# Collect all unused imports where warn has been set to true.
|
||||
def collect_unused_imports(pid) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
warnable = :digraph.out_neighbours(d, :warn)
|
||||
|
||||
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
|
||||
{ mod, line }
|
||||
end
|
||||
end
|
||||
|
||||
defp get_warn_line(d, mod) do
|
||||
[edge] = :digraph.out_edges(d, mod)
|
||||
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
|
||||
line
|
||||
end
|
||||
|
||||
defp has_imports?(d, mod) do
|
||||
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@doc false
|
||||
def yank(pid, local) do
|
||||
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
|
||||
end
|
||||
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, { :reattach, tuple, neighbours })
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@doc false
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
end
|
||||
end
|
||||
|
||||
# Collect all unused definitions based on the private
|
||||
# given also accounting the expected amount of default
|
||||
# clauses a private function have.
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
|
||||
{ name, arity } = tuple
|
||||
min = arity - default
|
||||
max = arity
|
||||
|
||||
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
else
|
||||
case :lists.min(invoked) - min do
|
||||
0 -> acc
|
||||
^default -> [{ :unused_args, tuple }|acc]
|
||||
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :import)
|
||||
:digraph.add_vertex(d, :remote)
|
||||
:digraph.add_vertex(d, :warn)
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, d) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, d }
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, d) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :remote, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :import, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_warnable, module, warn, line }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :import, module)
|
||||
|
||||
if warn do
|
||||
:digraph.add_edge(d, :warn, module, line)
|
||||
:digraph.add_edge(d, module, :warn, line)
|
||||
else
|
||||
:digraph.del_path(d, :warn, module)
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, kind, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
if function != nil do
|
||||
replace_edge!(d, function, tuple)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_add_local(d, from, to) do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, from, to)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, :local, tuple)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
|
||||
defp replace_edge(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
:digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
end
|
||||
+32
-1
@@ -74,11 +74,23 @@ defmodule Node do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
|
||||
"""
|
||||
@spec disconnect(t) :: boolean | :ginored
|
||||
@spec disconnect(t) :: boolean | :ignored
|
||||
def disconnect(node) do
|
||||
:erlang.disconnect_node(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
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.
|
||||
"""
|
||||
@spec connect(t) :: boolean | :ignored
|
||||
def connect(node) do
|
||||
:net_kernel.connect_node(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
@@ -152,4 +164,23 @@ defmodule Node do
|
||||
def spawn_link(node, module, fun, args) do
|
||||
:erlang.spawn_link(node, module, fun, args)
|
||||
end
|
||||
|
||||
@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
|
||||
sets the cookie of all other unknown nodes to `cookie`.
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the magic cookie of the local node, if the node is alive;
|
||||
otherwise `:nocookie`.
|
||||
"""
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
end
|
||||
end
|
||||
@@ -5,45 +5,53 @@ defmodule OptionParser do
|
||||
|
||||
## Examples
|
||||
|
||||
OptionParser.parse(["--debug"])
|
||||
#=> { [debug: true], [] }
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{ [debug: true], [] }
|
||||
|
||||
OptionParser.parse(["--source", "lib"])
|
||||
#=> { [source: "lib"], [] }
|
||||
iex> OptionParser.parse(["--source", "lib"])
|
||||
{ [source: "lib"], [] }
|
||||
|
||||
OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
#=> { [source: "lib", verbose: true], ["test/enum_test.exs"] }
|
||||
iex> OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib", verbose: true], ["test/enum_test.exs"] }
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as second argument:
|
||||
|
||||
OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
#=> { [debug: true], [] }
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [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, list, etc. The following
|
||||
types are supported:
|
||||
Extra information about switches can be given as argument too.
|
||||
This is useful in order to say a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
|
||||
* `:boolean` - They never consume the next value unless it is true/false;
|
||||
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;
|
||||
|
||||
The following extra options are supported:
|
||||
|
||||
* `:keep` - Keep duplicated items in the list instead of overriding;
|
||||
|
||||
Examples:
|
||||
|
||||
OptionParser.parse(["--unlock path/to/file"], switches: [unlock: :boolean])
|
||||
#=> { [unlock: true], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: true], ["path/to/file"] }
|
||||
|
||||
OptionParser.parse(["--unlock false path/to/file"], switches: [unlock: :boolean])
|
||||
#=> { [unlock: false], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--unlock", "false", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: false], ["path/to/file"] }
|
||||
|
||||
## Negation switches
|
||||
|
||||
Any switches starting with `--no-` are always considered to be
|
||||
booleans and never parse the next value:
|
||||
|
||||
OptionParser.parse(["--no-op path/to/file"])
|
||||
#=> { [no_op: true], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{ [no_op: true], ["path/to/file"] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
@@ -58,8 +66,8 @@ defmodule OptionParser do
|
||||
|
||||
## Example
|
||||
|
||||
OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
#=> { [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
@@ -69,11 +77,6 @@ defmodule OptionParser do
|
||||
## Helpers
|
||||
|
||||
defp parse(argv, opts, bool) do
|
||||
if flags = opts[:flags] do
|
||||
IO.write "OptionParser option :flags is deprecated, please use :switches instead\n#{Exception.format_stacktrace}"
|
||||
opts = Keyword.put(opts, :switches, lc(k inlist flags, do: { k, :boolean }))
|
||||
end
|
||||
|
||||
aliases = opts[:aliases] || []
|
||||
switches = opts[:switches] || []
|
||||
parse(argv, aliases, switches, bool)
|
||||
@@ -85,16 +88,18 @@ defmodule OptionParser do
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
|
||||
{ option, value } = normalize_option(option, aliases)
|
||||
kind = switches[option]
|
||||
|
||||
if value == nil do
|
||||
{ value, t } =
|
||||
case switch_type(switches, option) do
|
||||
:boolean -> boolean_from_tail(t)
|
||||
_ -> value_from_tail(t)
|
||||
if is_switch_a? :boolean, kind do
|
||||
boolean_from_tail(t)
|
||||
else
|
||||
value_from_tail(t)
|
||||
end
|
||||
end
|
||||
|
||||
dict = store_option dict, option, value
|
||||
dict = store_option dict, option, value, kind
|
||||
parse(t, aliases, switches, dict, args, all)
|
||||
end
|
||||
|
||||
@@ -117,16 +122,22 @@ defmodule OptionParser do
|
||||
defp value_from_tail([h|t]), do: { h, t }
|
||||
defp value_from_tail([]), do: { true, [] }
|
||||
|
||||
defp store_option(dict, option, value) when value in ["false", "true"] do
|
||||
store_option(dict, option, binary_to_atom(value))
|
||||
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, option, value) do
|
||||
[{ option, value }|dict]
|
||||
defp store_option(dict, option, value, kind) do
|
||||
if is_switch_a? :keep, kind do
|
||||
[{ option, value }|dict]
|
||||
else
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
defp reverse_dict(dict, switches) do
|
||||
switches = lc { k, :boolean } inlist switches, not Keyword.has_key?(dict, k), do: { k, false }
|
||||
switches = lc { k, v } inlist switches,
|
||||
is_switch_a?(:boolean, v),
|
||||
not Keyword.has_key?(dict, k), do: { k, false }
|
||||
Enum.reverse switches ++ dict
|
||||
end
|
||||
|
||||
@@ -155,7 +166,7 @@ defmodule OptionParser do
|
||||
defp is_no?("no-" <> _), do: true
|
||||
defp is_no?(_), do: false
|
||||
|
||||
defp switch_type(switches, option) do
|
||||
switches[option] || :default
|
||||
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
|
||||
end
|
||||
|
||||
+120
-102
@@ -1,23 +1,25 @@
|
||||
defmodule Path do
|
||||
@moduledoc """
|
||||
This module provides conveniences for manipulating or
|
||||
retrieving filesystem paths.
|
||||
retrieving file system paths.
|
||||
|
||||
The functions on this module may receive a char list or
|
||||
a binary as argument and will return the given type.
|
||||
The functions in this module may receive a char list or
|
||||
a binary as 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, unless some few functions
|
||||
that needs to query the filesystem to retrieve paths
|
||||
(like `Path.wildcard` and `Path.expand`).
|
||||
interact with the file system, except for a few functions
|
||||
that require it (like `Path.wildcard` and `Path.expand`).
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
|
||||
@type t :: char_list | atom | binary
|
||||
@type r :: char_list | binary
|
||||
|
||||
@doc """
|
||||
Converts the given filename and returns an absolute name.
|
||||
Differently from `Path.expand/1`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
Converts the given path to an absolute one. Differently from
|
||||
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
|
||||
|
||||
## Unix examples
|
||||
|
||||
@@ -40,20 +42,19 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given filename and returns an absolute name
|
||||
relative to the given location. If the path is already
|
||||
an absolute path, the relative path is ignored.
|
||||
Builds a path from `relative_to` to `path`. If `path` is already
|
||||
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
|
||||
|
||||
Differently from `Path.expand/2`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.absname("foo", "bar")
|
||||
#=> "bar/foo"
|
||||
iex> Path.absname("foo", "bar")
|
||||
"bar/foo"
|
||||
|
||||
Path.absname("../x", "bar")
|
||||
#=> "bar/../x"
|
||||
iex> Path.absname("../x", "bar")
|
||||
"bar/../x"
|
||||
|
||||
"""
|
||||
def absname(path, relative_to) do
|
||||
@@ -61,31 +62,43 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Expands the path by returning its absolute name and expanding
|
||||
any `.` and `..` characters.
|
||||
Converts the path to an absolute one and expands
|
||||
any `.` and `..` characters and a leading `~`.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.expand("/foo/bar/../bar") == "/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def expand(path) do
|
||||
normalize FN.absname(path, get_cwd(path))
|
||||
normalize FN.absname(expand_home(path), get_cwd(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Expands the path to the relative location and expanding
|
||||
any `.` and `..` characters. If the path is already an
|
||||
absolute path, the relative location is ignored.
|
||||
Expands the path relative to the path given as the second argument
|
||||
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
|
||||
an absolute one.
|
||||
|
||||
The second argument is first expanded to an absolute path.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.expand("foo/bar/../bar", "/baz") == "/baz/foo/bar"
|
||||
Path.expand("/foo/bar/../bar", "/baz") == "/foo/bar"
|
||||
# Assuming that the absolute path to baz is /quux/baz
|
||||
Path.expand("foo/bar/../bar", "baz")
|
||||
#=> "/quux/baz/foo/bar"
|
||||
|
||||
iex> Path.expand("foo/bar/../bar", "/baz")
|
||||
"/baz/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar", "/baz")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def expand(path, relative_to) do
|
||||
normalize FN.absname(FN.absname(path, relative_to), get_cwd(path))
|
||||
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -96,6 +109,7 @@ defmodule Path do
|
||||
Path.type("/usr/local/bin") #=> :absolute
|
||||
Path.type("usr/local/bin") #=> :relative
|
||||
Path.type("../usr/local/bin") #=> :relative
|
||||
Path.type("~/file") #=> :relative
|
||||
|
||||
## Windows examples
|
||||
|
||||
@@ -179,8 +193,9 @@ defmodule Path do
|
||||
|
||||
@doc """
|
||||
Returns the given `path` relative to the given `from` path.
|
||||
In other words, it tries to strip the `from` prefix from `path`.
|
||||
|
||||
This function does not query the filesystem, so it assumes
|
||||
This function does not query the file system, so it assumes
|
||||
no symlinks in between the paths.
|
||||
|
||||
In case a direct relative path cannot be found, it returns
|
||||
@@ -188,9 +203,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
|
||||
Path.relative_to("/usr/local/foo", "/") #=> "foo"
|
||||
Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
|
||||
iex> Path.relative_to("/usr/local/foo", "/usr/local")
|
||||
"foo"
|
||||
iex> Path.relative_to("/usr/local/foo", "/")
|
||||
"usr/local/foo"
|
||||
iex> Path.relative_to("/usr/local/foo", "/etc")
|
||||
"/usr/local/foo"
|
||||
|
||||
"""
|
||||
def relative_to(path, from) when is_list(path) and is_binary(from) do
|
||||
@@ -224,14 +242,14 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.basename("foo")
|
||||
#=> "foo"
|
||||
iex> Path.basename("foo")
|
||||
"foo"
|
||||
|
||||
Path.basename("foo/bar")
|
||||
#=> "bar"
|
||||
iex> Path.basename("foo/bar")
|
||||
"bar"
|
||||
|
||||
Path.basename("/")
|
||||
#=> ""
|
||||
iex> Path.basename("/")
|
||||
""
|
||||
|
||||
"""
|
||||
def basename(path) do
|
||||
@@ -245,12 +263,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.basename("~/foo/bar.ex", ".ex")
|
||||
#=> "bar"
|
||||
Path.basename("~/foo/bar.exs", ".ex")
|
||||
#=> "bar.exs"
|
||||
Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
#=> "bar.old"
|
||||
iex> Path.basename("~/foo/bar.ex", ".ex")
|
||||
"bar"
|
||||
iex> Path.basename("~/foo/bar.exs", ".ex")
|
||||
"bar.exs"
|
||||
iex> Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
"bar.old"
|
||||
|
||||
"""
|
||||
def basename(path, extension) do
|
||||
@@ -258,12 +276,14 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the `directory` component of `path`.
|
||||
Returns the directory component of `path`.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.dirname("/foo/bar.ex")
|
||||
#=> "foo"
|
||||
#=> "/foo"
|
||||
Path.dirname("/foo/bar/baz.ex")
|
||||
#=> "/foo/bar"
|
||||
|
||||
"""
|
||||
def dirname(path) do
|
||||
@@ -271,14 +291,14 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the `extension` of the last component of `path`.
|
||||
Returns the extension of the last component of `path`.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.extname("foo.erl")
|
||||
#=> ".erl"
|
||||
Path.extname("~/foo/bar")
|
||||
#=> ""
|
||||
iex> Path.extname("foo.erl")
|
||||
".erl"
|
||||
iex> Path.extname("~/foo/bar")
|
||||
""
|
||||
|
||||
"""
|
||||
def extname(path) do
|
||||
@@ -290,10 +310,10 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.rootname("/foo/bar")
|
||||
#=> "/foo/bar"
|
||||
Path.rootname("/foo/bar.ex")
|
||||
#=> "/foo/bar"
|
||||
iex> Path.rootname("/foo/bar")
|
||||
"/foo/bar"
|
||||
iex> Path.rootname("/foo/bar.ex")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def rootname(path) do
|
||||
@@ -306,10 +326,10 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.rootname("/foo/bar.erl", ".erl")
|
||||
#=> "/foo/bar"
|
||||
Path.rootname("/foo/bar.erl", ".ex")
|
||||
#=> "/foo/bar.erl"
|
||||
iex> Path.rootname("/foo/bar.erl", ".erl")
|
||||
"/foo/bar"
|
||||
iex> Path.rootname("/foo/bar.erl", ".ex")
|
||||
"/foo/bar.erl"
|
||||
|
||||
"""
|
||||
def rootname(path, extension) do
|
||||
@@ -317,17 +337,17 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string with one or more paths components joint by the path separator.
|
||||
This function should be used to convert a list of strings in a path.
|
||||
Returns a string with one or more path components joined by the path separator.
|
||||
This function should be used to convert a list of strings to a path.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.join(["~", "foo"])
|
||||
#=> "~/foo"
|
||||
Path.join(["foo"])
|
||||
#=> "foo"
|
||||
Path.join(["/", "foo", "bar"])
|
||||
#=> "/foo/bar"
|
||||
iex> Path.join(["~", "foo"])
|
||||
"~/foo"
|
||||
iex> Path.join(["foo"])
|
||||
"foo"
|
||||
iex> Path.join(["/", "foo", "bar"])
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def join([name1, name2|rest]), do:
|
||||
@@ -342,8 +362,8 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.join("foo", "bar")
|
||||
#=> "foo/bar"
|
||||
iex> Path.join("foo", "bar")
|
||||
"foo/bar"
|
||||
|
||||
"""
|
||||
def join(left, right) when is_binary(left) and is_binary(right), do:
|
||||
@@ -365,10 +385,7 @@ defmodule Path do
|
||||
join(left, atom_to_binary(right))
|
||||
|
||||
defp major_os_type do
|
||||
case :os.type do
|
||||
{ maj, _ } -> maj
|
||||
maj -> maj
|
||||
end
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
@@ -400,19 +417,22 @@ defmodule Path do
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Returns a list with the path splitted by the path separator.
|
||||
If an empty string is given, then it returns the root path.
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.split("")
|
||||
#=> ["/"]
|
||||
Path.split("foo")
|
||||
#=> ["foo"]
|
||||
Path.split("/foo/bar")
|
||||
#=> ["/", "foo", "bar"]
|
||||
iex> Path.split("")
|
||||
[]
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
"""
|
||||
# Work around a bug in Erlang on UNIX
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(path)
|
||||
end
|
||||
@@ -440,8 +460,8 @@ defmodule Path do
|
||||
## Examples
|
||||
|
||||
Imagine you have a directory called `projects` with three Elixir projects
|
||||
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
|
||||
inside their ebin directories all projects as follows:
|
||||
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
|
||||
inside the ebin directory of each project as follows:
|
||||
|
||||
Path.wildcard("projects/*/ebin/**/*.beam")
|
||||
|
||||
@@ -451,17 +471,17 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = :elixir_glob.wildcard :unicode.characters_to_list(glob)
|
||||
paths = :filelib.wildcard :unicode.characters_to_list(glob)
|
||||
Enum.map paths, :unicode.characters_to_binary(&1)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
Enum.map wildcard(list_to_binary(glob)), binary_to_list(&1)
|
||||
:filelib.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_list
|
||||
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
|
||||
defp get_cwd(_), do: System.cwd!
|
||||
|
||||
defp binary_to_filename_string(binary) do
|
||||
@@ -484,33 +504,31 @@ defmodule Path do
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
|
||||
defp normalize(path), do: do_normalize(FN.split(path))
|
||||
|
||||
defp do_normalize(["~"|t]) do
|
||||
do_normalize t, [System.user_home!]
|
||||
defp expand_home(<<?~, rest :: binary>>) do
|
||||
System.user_home! <> rest
|
||||
end
|
||||
|
||||
defp do_normalize(['~'|t]) do
|
||||
do_normalize t, [System.user_home! |> binary_to_list]
|
||||
defp expand_home('~' ++ rest) do
|
||||
(System.user_home! |> binary_to_filename_string) ++ rest
|
||||
end
|
||||
|
||||
defp do_normalize(t) do
|
||||
do_normalize t, []
|
||||
defp expand_home(other), do: other
|
||||
|
||||
defp normalize(path), do: normalize(FN.split(path), [])
|
||||
|
||||
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp do_normalize([top|t], [_|acc]) when top in ["..", '..'] do
|
||||
do_normalize t, acc
|
||||
defp normalize([top|t], acc) when top in [".", '.'] do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp do_normalize([top|t], acc) when top in [".", '.'] do
|
||||
do_normalize t, acc
|
||||
defp normalize([h|t], acc) do
|
||||
normalize t, [h|acc]
|
||||
end
|
||||
|
||||
defp do_normalize([h|t], acc) do
|
||||
do_normalize t, [h|acc]
|
||||
end
|
||||
|
||||
defp do_normalize([], acc) do
|
||||
defp normalize([], acc) do
|
||||
join Enum.reverse(acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,14 +19,6 @@ defmodule Process do
|
||||
:erlang.is_process_alive(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current process.
|
||||
"""
|
||||
@spec self :: pid
|
||||
def self do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all key-values in the dictionary.
|
||||
"""
|
||||
@@ -368,6 +360,7 @@ defmodule Process do
|
||||
:erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@compile { :inline, nillify: 1 }
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
@@ -267,7 +267,7 @@ defmodule Protocol do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
@@ -287,7 +287,7 @@ defmodule Protocol do
|
||||
# Generate all others protocols.
|
||||
defp each_impl_for({ kind, fun, _ }, _, _) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when unquote(fun).(arg) do
|
||||
defp __raw_impl__(arg) when unquote(fun)(arg) do
|
||||
__MODULE__.unquote(kind)
|
||||
end
|
||||
end
|
||||
@@ -404,7 +404,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name).(unquote_splicing(args))
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
|
||||
Kernel.def unquote(name), args, [], do: body
|
||||
|
||||
+28
-20
@@ -5,14 +5,7 @@ defrecord Range, [:first, :last] do
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
How to iterate the range, receives the first
|
||||
and range as arguments. It needs to return a
|
||||
function that receives an item and returns
|
||||
a tuple with two elements: the given item
|
||||
and the next item in the iteration.
|
||||
"""
|
||||
def iterator(first, range)
|
||||
def reduce(first, range, acc, fun)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
@@ -20,10 +13,12 @@ defprotocol Range.Iterator do
|
||||
def count(first, range)
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Range do
|
||||
def iterator(Range[first: first] = range) do
|
||||
iterator = Range.Iterator.iterator(first, range)
|
||||
{ iterator, iterator.(first) }
|
||||
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
|
||||
|
||||
def count(Range[first: first] = range) do
|
||||
@@ -32,16 +27,29 @@ defimpl Enum.Iterator, for: Range do
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
fn(current) ->
|
||||
if current > last, do: :stop, else: { current, current + 1 }
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
else
|
||||
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
|
||||
end
|
||||
Enumerable.Function.reduce(reducer, acc, fun)
|
||||
end
|
||||
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
fn(current) ->
|
||||
if current < last, do: :stop, else: { current, current - 1 }
|
||||
end
|
||||
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_up(counter, last, acc, fun) do
|
||||
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, fun) do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
@@ -57,6 +65,6 @@ defimpl Binary.Inspect, for: Range do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Binary.Inspect.inspect(first, opts) <> ".." <> Binary.Inspect.inspect(last, opts)
|
||||
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
|
||||
end
|
||||
end
|
||||
|
||||
+233
-40
@@ -1,6 +1,155 @@
|
||||
defmodule Record do
|
||||
@moduledoc """
|
||||
@moduledoc %B"""
|
||||
Functions to define Elixir 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`.
|
||||
|
||||
## Examples
|
||||
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
|
||||
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:
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
|
||||
# Update multiple attributes at once:
|
||||
file_info.update(atime: now(), accesses: 1)
|
||||
|
||||
# Obtain the keywords representation of a record:
|
||||
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
|
||||
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
|
||||
## Runtime introspection
|
||||
|
||||
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.
|
||||
For example, assuming the `Config` record defined above, it could be used
|
||||
in typespecs as follow:
|
||||
|
||||
@spec handle_config(Config.t) :: boolean()
|
||||
|
||||
Inside the record definition, a developer can define his own types too:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
When defining a type, all the fields not mentioned in the type are
|
||||
assumed to have type `term`.
|
||||
|
||||
## 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
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -24,22 +173,51 @@ defmodule Record do
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
opts = Keyword.delete(opts, :do)
|
||||
|
||||
quote do
|
||||
unquoted_values = unquote(values)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Record.DSL
|
||||
import Elixir.Record.DSL
|
||||
|
||||
values = unquote(values)
|
||||
opts = unquote(opts)
|
||||
Record.deffunctions(values, __ENV__)
|
||||
unquote(block)
|
||||
Record.deftypes(values, @record_type, __ENV__)
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
# Reassign values to inner scope to
|
||||
# avoid conflicts in nested records
|
||||
values = unquoted_values
|
||||
|
||||
Elixir.Record.deffunctions(values, __ENV__)
|
||||
value = unquote(block)
|
||||
Elixir.Record.deftypes(values, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import public record definition as a set of private macros (as defined by defrecordp/2)
|
||||
|
||||
## Usage
|
||||
|
||||
Record.import Record.Module, as: macro_name
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
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))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for private records definition. It defines
|
||||
a set of macros with the given `name` and the fields specified
|
||||
@@ -80,7 +258,7 @@ defmodule Record do
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if env == Macro.Env do
|
||||
@@ -124,31 +302,31 @@ defmodule Record do
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env) do
|
||||
def defmacros(name, values, env, tag // nil) do
|
||||
escaped = lc value inlist values do
|
||||
{ key, value } = convert_value(value)
|
||||
{ key, Macro.escape(value) }
|
||||
end
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name).() do
|
||||
Record.access(__MODULE__, unquote(escaped), [], __CALLER__)
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record) when is_tuple(record) do
|
||||
Record.to_keywords(__MODULE__, unquote(escaped), record)
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(args) do
|
||||
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
|
||||
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(__MODULE__, unquote(escaped), record, key)
|
||||
defmacrop unquote(name)(record, key) when is_atom(key) do
|
||||
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, args) do
|
||||
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -216,7 +394,8 @@ defmodule Record do
|
||||
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] -> { :{}, [line: caller.line], [atom|match] }
|
||||
[] ->
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
@@ -390,6 +569,7 @@ defmodule Record 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
|
||||
@@ -432,28 +612,28 @@ defmodule Record do
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(record) do
|
||||
# def atime(record) do
|
||||
# elem(record, 1)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# def mtime(record) do
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
# def :atime.(value, record) do
|
||||
# setelem(record, 1, value)
|
||||
# def atime(value, record) do
|
||||
# set_elem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# setelem(record, 2, value)
|
||||
# def mtime(record) do
|
||||
# set_elem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
# def :atime.(callback, record) do
|
||||
# setelem(record, 1, callback.(elem(record, 1)))
|
||||
# def atime(callback, record) do
|
||||
# set_elem(record, 1, callback.(elem(record, 1)))
|
||||
# end
|
||||
#
|
||||
# def :mtime.(callback, record) do
|
||||
# setelem(record, 2, callback.(elem(record, 2)))
|
||||
# def mtime(callback, record) do
|
||||
# set_elem(record, 2, callback.(elem(record, 2)))
|
||||
# end
|
||||
#
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
@@ -465,17 +645,17 @@ defmodule Record do
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key).(record) do
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key).(value, record) do
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update).(function, record) do
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
@@ -499,7 +679,7 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
contents = { :{}, [], [(quote do: __MODULE__)|fields] }
|
||||
contents = quote do: { __MODULE__, unquote_splicing(fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -529,21 +709,34 @@ defmodule Record do
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = if values == [], do: [], else: [options_specs(values)]
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: { __MODULE__, unquote_splicing(types) }
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options)
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(Keyword.t | tuple) :: t
|
||||
@spec to_keywords(t) :: Keyword.t
|
||||
@spec update(Keyword.t, t) :: t
|
||||
@spec new(options | tuple) :: 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
|
||||
end
|
||||
end
|
||||
|
||||
defp options_specs([{ k, _, v }|t]) do
|
||||
:lists.foldl fn { k, _, v }, acc ->
|
||||
{ :|, [], [{ k, v }, acc] }
|
||||
end, { k, v }, t
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
@@ -607,7 +800,7 @@ defmodule Record.DSL do
|
||||
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
|
||||
|
||||
quote do
|
||||
@record_type Keyword.merge(@record_type || [], unquote(escaped))
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+72
-37
@@ -54,8 +54,7 @@ defmodule Regex do
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def compile(source, options // "") do
|
||||
source = to_binary(source)
|
||||
def compile(source, options // "") when is_binary(source) do
|
||||
options = to_binary(options)
|
||||
opts = translate_options(options)
|
||||
re_opts = opts -- [:groups]
|
||||
@@ -80,18 +79,12 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string
|
||||
and returns the index (zero indexes) where the first
|
||||
match occurs, nil otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.index %r/c(d)/, "abcd" #=> 3
|
||||
Regex.index %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
IO.puts "Regex.index(re, string) is deprecated. Please use " <>
|
||||
"Regex.run(re, string, return: :index) instead."
|
||||
Exception.print_stacktrace
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
@@ -103,8 +96,10 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.match? %r/foo/, "foo" #=> true
|
||||
Regex.match? %r/foo/, "bar" #=> false
|
||||
iex> Regex.match?(%r/foo/, "foo")
|
||||
true
|
||||
iex> Regex.match?(%r/foo/, "bar")
|
||||
false
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
@@ -113,12 +108,17 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string.
|
||||
It returns a list with all matches or nil if no match ocurred.
|
||||
It returns a list with all matches, nil if no match ocurred, or []
|
||||
if it matched, /g was specified, but nothing was captured.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.run %r/c(d)/, "abcd" #=> ["cd", "d"]
|
||||
Regex.run %r/e/, "abcd" #=> nil
|
||||
iex> Regex.run(%r/c(d)/, "abcd")
|
||||
["cd", "d"]
|
||||
iex> Regex.run(%r/e/, "abcd")
|
||||
nil
|
||||
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
def run(regex, string, options // [])
|
||||
@@ -128,22 +128,25 @@ defmodule Regex do
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise "regex was not compiled with g"
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a list of tuples.
|
||||
Requires the regex to be compiled with the groups option.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
|
||||
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
|
||||
"""
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
@@ -151,7 +154,7 @@ defmodule Regex do
|
||||
captures = if groups do
|
||||
Enum.sort(groups)
|
||||
else
|
||||
raise "Regex was not compiled with g"
|
||||
raise ArgumentError, message: "regex was not compiled with g"
|
||||
end
|
||||
options = Keyword.put(options, :capture, captures)
|
||||
end
|
||||
@@ -160,7 +163,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the underlying re_pattern in the regular expression.
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
def re_pattern(regex(re_pattern: compiled)) do
|
||||
compiled
|
||||
@@ -171,7 +174,8 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.source %r(foo) #=> "foo"
|
||||
iex> Regex.source(%r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
def source(regex(source: source)) do
|
||||
@@ -179,11 +183,12 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the regex options as a list.
|
||||
Returns the regex options as a string.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.opts %r(foo)m #=> 'm'
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
def opts(regex(options: options)) do
|
||||
@@ -195,7 +200,8 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
|
||||
iex> Regex.groups(%r/(?<foo>foo)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
def groups(regex(groups: groups)) do
|
||||
@@ -210,9 +216,12 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.scan %r/c(d|e)/, "abcd abce" #=> [["d"], ["e"]]
|
||||
Regex.scan %r/c(?:d|e)/, "abcd abce" #=> ["cd", "ce"]
|
||||
Regex.scan %r/e/, "abcd" #=> []
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
[["d"], ["e"]]
|
||||
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
|
||||
["cd", "ce"]
|
||||
iex> Regex.scan(%r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options // [])
|
||||
@@ -257,11 +266,16 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.replace(%r/d/, "abc", "d") #=> "abc"
|
||||
Regex.replace(%r/b/, "abc", "d") #=> "adc"
|
||||
Regex.replace(%r/b/, "abc", "[&]") #=> "a[b]c"
|
||||
Regex.replace(%r/b/, "abc", "[\\&]") #=> "a[&]c"
|
||||
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
|
||||
iex> Regex.replace(%r/d/, "abc", "d")
|
||||
"abc"
|
||||
iex> Regex.replace(%r/b/, "abc", "d")
|
||||
"adc"
|
||||
iex> Regex.replace(%r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(%r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
|
||||
@@ -271,6 +285,25 @@ defmodule Regex do
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string to be literally matched in a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.escape(".")
|
||||
"\\."
|
||||
iex> Regex.escape("\\what if")
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t | char_list) :: String.t | char_list
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
@@ -302,15 +335,17 @@ defmodule Regex do
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[h|t] -> t
|
||||
[_|t] -> t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
case :re.run(source, pattern, options) do
|
||||
case :re.run(source, @groups_pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
|
||||
+424
-97
@@ -1,17 +1,38 @@
|
||||
defmodule String do
|
||||
@moduledoc %B"""
|
||||
A string in Elixir is a UTF-8 encoded binary.
|
||||
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. A codepoint is a
|
||||
Unicode Character, which may be represented by one
|
||||
or more bytes. For example, the character "é" is
|
||||
represented with two bytes:
|
||||
Unicode Standard, version 6.2.0. For example,
|
||||
`titlecase`, `downcase`, `strip` are provided by this
|
||||
module.
|
||||
|
||||
string = "é"
|
||||
#=> "é"
|
||||
size(string)
|
||||
#=> 2
|
||||
Besides this module, Elixir provides more low-level
|
||||
operations that works 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`
|
||||
|
||||
Finally, [the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
provides a couple other functions that works on the byte level.
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> string = "é"
|
||||
...> byte_size(string)
|
||||
2
|
||||
|
||||
Furthermore, this module also presents the concept of
|
||||
graphemes, which are multiple characters that may be
|
||||
@@ -19,10 +40,9 @@ defmodule String do
|
||||
the same "é" character written above could be represented
|
||||
by the letter "e" followed by the accent ́:
|
||||
|
||||
string = "\x{0065}\x{0301}"
|
||||
#=> "é"
|
||||
size(string)
|
||||
#=> 3
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
3
|
||||
|
||||
Although the example above is made of two characters, it is
|
||||
perceived by users as one.
|
||||
@@ -40,26 +60,30 @@ defmodule String do
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as binaries. For example:
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
String.codepoints "josé" #=> ["j", "o", "s", "é"]
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
codepoint. One may use the `?` special macro:
|
||||
|
||||
?j #=> 106
|
||||
?é #=> 233
|
||||
iex> ?j
|
||||
106
|
||||
iex> ?é
|
||||
233
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
<< eacute :: utf8 >> = "é"
|
||||
eacute #=> 233
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
...> eacute
|
||||
233
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
a string by their hexadecimal code:
|
||||
|
||||
string = "jos\x{0065}\x{0301}"
|
||||
#=> "josé"
|
||||
"jos\x{0065}\x{0301}" #=>
|
||||
"josé"
|
||||
|
||||
## Self-synchronization
|
||||
|
||||
@@ -72,8 +96,10 @@ defmodule String do
|
||||
characters. For example, `String.length` is going to return
|
||||
a correct result even if an invalid codepoint is fed into it.
|
||||
|
||||
In the future, bang version of such functions may be
|
||||
provided which will rather raise on such invalid data.
|
||||
In other words, this module expects invalid data to be detected
|
||||
when retrieving data from the external source. For example, a
|
||||
driver that reads strings from a database will be the one
|
||||
responsible to check the validity of the encoding.
|
||||
"""
|
||||
|
||||
@type t :: binary
|
||||
@@ -86,7 +112,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.printable?("abc") #=> true
|
||||
iex> String.printable?("abc")
|
||||
true
|
||||
|
||||
"""
|
||||
@spec printable?(t) :: boolean
|
||||
@@ -112,36 +139,51 @@ defmodule String do
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc """
|
||||
Divides a string into sub string based on a pattern,
|
||||
Splits a string on sub strings at each Unicode whitespace
|
||||
occurrence with leading and trailing whitespace ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split("foo" <> <<194,133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Divides a string into sub strings based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
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.
|
||||
If a pattern is not specified, the string is split on
|
||||
whitespace occurrences.
|
||||
|
||||
It returns a list with the original string if the pattern
|
||||
can't be matched.
|
||||
|
||||
## Examples
|
||||
|
||||
String.split("a,b,c", ",") #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", ",", global: false) #=> ["a", "b,c"]
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
["a", "b,c"]
|
||||
|
||||
String.split("foo bar") #=> ["foo", "bar"]
|
||||
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
|
||||
iex> String.split("1,2 3,4", [" ", ","])
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
String.split("a,b,c", %r{,}) #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", %r{,}, global: false) #=> ["a", "b,c"]
|
||||
String.split("a,b", %r{\.}) #=> ["a,b"]
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split("a,b", %r{\\.})
|
||||
["a,b"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
|
||||
def split(binary, pattern // " ", options // [])
|
||||
def split(binary, pattern, options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, global: options[:global])
|
||||
@@ -157,9 +199,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.upcase("abcd") #=> "ABCD"
|
||||
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
|
||||
String.upcase("josé") #=> "JOSÉ"
|
||||
iex> String.upcase("abcd")
|
||||
"ABCD"
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
|
||||
"""
|
||||
@spec upcase(t) :: t
|
||||
@@ -170,9 +215,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.downcase("ABCD") #=> "abcd"
|
||||
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
|
||||
String.downcase("JOSÉ") #=> "josé"
|
||||
iex> String.downcase("ABCD")
|
||||
"abcd"
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
|
||||
"""
|
||||
@spec downcase(t) :: t
|
||||
@@ -189,9 +237,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.capitalize("abcd") #=> "Abcd"
|
||||
String.capitalize("fin") #=> "Fin"
|
||||
String.capitalize("josé") #=> "José"
|
||||
iex> String.capitalize("abcd")
|
||||
"Abcd"
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
@@ -201,12 +252,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing whitespace characters
|
||||
and new line have been removed.
|
||||
Returns a string where trailing Unicode whitespace
|
||||
has been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc ") #=> " abc"
|
||||
iex> String.rstrip(" abc ")
|
||||
" abc"
|
||||
|
||||
"""
|
||||
@spec rstrip(t) :: t
|
||||
@@ -217,7 +269,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc _", ?_) #=> " abc "
|
||||
iex> String.rstrip(" abc _", ?_)
|
||||
" abc "
|
||||
|
||||
"""
|
||||
@spec rstrip(t, char) :: t
|
||||
@@ -248,12 +301,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading whitespace characters
|
||||
have been removed.
|
||||
Returns a string where leading Unicode whitespace
|
||||
has been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip(" abc ") #=> "abc "
|
||||
iex> String.lstrip(" abc ")
|
||||
"abc "
|
||||
|
||||
"""
|
||||
defdelegate lstrip(binary), to: String.Unicode
|
||||
@@ -263,7 +317,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip("_ abc _", ?_) #=> " abc _"
|
||||
iex> String.lstrip("_ abc _", ?_)
|
||||
" abc _"
|
||||
|
||||
"""
|
||||
|
||||
@@ -278,12 +333,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading/trailing whitespace
|
||||
and new line characters have been removed.
|
||||
Returns a string where leading/trailing Unicode whitespace
|
||||
has been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip(" abc ") #=> "abc"
|
||||
iex> String.strip(" abc ")
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec strip(t) :: t
|
||||
@@ -298,7 +354,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip("a abc a", ?a) #=> " abc "
|
||||
iex> String.strip("a abc a", ?a)
|
||||
" abc "
|
||||
|
||||
"""
|
||||
@spec strip(t, char) :: t
|
||||
@@ -318,11 +375,16 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.replace("a,b,c", ",", "-") #=> "a-b-c"
|
||||
String.replace("a,b,c", ",", "-", global: false) #=> "a-b,c"
|
||||
String.replace("a,b,c", "b", "[]", insert_replaced: 1) #=> "a,[b],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: 2) #=> "a[],b[],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
|
||||
iex> String.replace("a,b,c", ",", "-")
|
||||
"a-b-c"
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1,1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@spec replace(t, t, t) :: t
|
||||
@@ -348,8 +410,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.duplicate("abc", 1) #=> "abc"
|
||||
String.duplicate("abc", 2) #=> "abcabc"
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
iex> String.duplicate("abc", 2)
|
||||
"abcabc"
|
||||
|
||||
"""
|
||||
@spec duplicate(t, pos_integer) :: t
|
||||
@@ -362,9 +426,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.codepoints("josé") #=> ["j", "o", "s", "é"]
|
||||
String.codepoints("оптими зации") #=> ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
iex> String.codepoints("ἅἪῼ")
|
||||
["ἅ","Ἢ","ῼ"]
|
||||
|
||||
"""
|
||||
@spec codepoints(t) :: [codepoint]
|
||||
@@ -384,12 +451,68 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.next_codepoint("josé") #=> { "j", "osé" }
|
||||
iex> String.next_codepoint("josé")
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` contains only valid characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid?("a")
|
||||
true
|
||||
iex> String.valid?("ø")
|
||||
true
|
||||
iex> String.valid?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid?("asd" <> <<0xffff :: 16>>)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid?(t) :: boolean
|
||||
|
||||
noncharacters = Enum.to_list(?\x{FDD0}..?\x{FDEF}) ++
|
||||
[ ?\x{0FFFE}, ?\x{0FFFF}, ?\x{1FFFE}, ?\x{1FFFF}, ?\x{2FFFE}, ?\x{2FFFF},
|
||||
?\x{3FFFE}, ?\x{3FFFF}, ?\x{4FFFE}, ?\x{4FFFF}, ?\x{5FFFE}, ?\x{5FFFF},
|
||||
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
|
||||
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
|
||||
|
||||
lc noncharacter inlist noncharacters do
|
||||
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
|
||||
end
|
||||
|
||||
def valid?(<<_ :: utf8, t :: binary>>), do: valid?(t)
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` is a valid character.
|
||||
|
||||
All characters are codepoints, but some codepoints
|
||||
are not valid characters. They may be reserved, private,
|
||||
or other.
|
||||
|
||||
More info at: http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Noncharacters
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid_character?("a")
|
||||
true
|
||||
iex> String.valid_character?("ø")
|
||||
true
|
||||
iex> String.valid_character?("\x{ffff}")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid_character?(t) :: boolean
|
||||
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` is a valid codepoint.
|
||||
|
||||
@@ -398,10 +521,14 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.valid_codepoint?("a") #=> true
|
||||
String.valid_codepoint?("ø") #=> true
|
||||
String.valid_codepoint?("\xffff") #=> false
|
||||
String.valid_codepoint?("asdf") #=> false
|
||||
iex> String.valid_codepoint?("a")
|
||||
true
|
||||
iex> String.valid_codepoint?("ø")
|
||||
true
|
||||
iex> String.valid_codepoint?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid_codepoint?("asdf")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid_codepoint?(codepoint) :: boolean
|
||||
@@ -413,7 +540,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
|
||||
iex> String.graphemes("Ā̀stute")
|
||||
["Ā̀","s","t","u","t","e"]
|
||||
|
||||
"""
|
||||
@spec graphemes(t) :: [grapheme]
|
||||
@@ -428,19 +556,23 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.next_grapheme("josé") #=> { "j", "osé" }
|
||||
iex> String.next_grapheme("josé")
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_grapheme(t) :: grapheme | :no_grapheme
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
defdelegate next_grapheme(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns the first grapheme from an utf8 string.
|
||||
Returns the first grapheme from an utf8 string,
|
||||
nil if the string is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
String.first("elixir") #=> "e"
|
||||
String.first("եոգլի") #=> "ե"
|
||||
iex> String.first("elixir")
|
||||
"e"
|
||||
iex> String.first("եոգլի")
|
||||
"ե"
|
||||
|
||||
"""
|
||||
@spec first(t) :: grapheme | nil
|
||||
@@ -452,12 +584,15 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last grapheme from an utf8 string.
|
||||
Returns the last grapheme from an utf8 string,
|
||||
nil if the string is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
String.last("elixir") #=> "r"
|
||||
String.last("եոգլի") #=> "ի"
|
||||
iex> String.last("elixir")
|
||||
"r"
|
||||
iex> String.last("եոգլի")
|
||||
"ի"
|
||||
|
||||
"""
|
||||
@spec last(t) :: grapheme | nil
|
||||
@@ -476,8 +611,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.length("elixir") #=> 6
|
||||
String.length("եոգլի") #=> 5
|
||||
iex> String.length("elixir")
|
||||
6
|
||||
iex> String.length("եոգլի")
|
||||
5
|
||||
|
||||
"""
|
||||
@spec length(t) :: non_neg_integer
|
||||
@@ -497,11 +634,16 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.at("elixir", 0) #=> "e"
|
||||
String.at("elixir", 1) #=> "l"
|
||||
String.at("elixir", 10) #=> nil
|
||||
String.at("elixir", -1) #=> "r"
|
||||
String.at("elixir", -10) #=> nil
|
||||
iex> String.at("elixir", 0)
|
||||
"e"
|
||||
iex> String.at("elixir", 1)
|
||||
"l"
|
||||
iex> String.at("elixir", 10)
|
||||
nil
|
||||
iex> String.at("elixir", -1)
|
||||
"r"
|
||||
iex> String.at("elixir", -10)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec at(t, integer) :: grapheme | nil
|
||||
@@ -535,11 +677,22 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.slice("elixir", 1, 3) #=> "lix"
|
||||
String.slice("elixir", 1, 10) #=> "lixir"
|
||||
String.slice("elixir", 10, 3) #=> nil
|
||||
String.slice("elixir", -4, 4) #=> "ixi"
|
||||
String.slice("elixir", -10, 3) #=> nil
|
||||
iex> String.slice("elixir", 1, 3)
|
||||
"lix"
|
||||
iex> String.slice("elixir", 1, 10)
|
||||
"lixir"
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
iex> String.slice("a", 1, 1500)
|
||||
""
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
@@ -572,10 +725,184 @@ defmodule String do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an integer. If successful, returns a
|
||||
tuple of form {integer, remainder of string}. If unsuccessful,
|
||||
returns :error.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("34")
|
||||
{34,""}
|
||||
iex> String.to_integer("34.5")
|
||||
{34,".5"}
|
||||
iex> String.to_integer("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(binary_to_list(string))
|
||||
case result do
|
||||
:error -> :error
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to a float. If successful, returns a
|
||||
tuple of form {float, remainder of string}. If unsuccessful,
|
||||
returns :error. If given an integer value, will return
|
||||
same as to_integer/1.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_float("34")
|
||||
{34.0,""}
|
||||
iex> String.to_float("34.25")
|
||||
{34.25,""}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_float(t) :: {integer, t} | :error
|
||||
|
||||
def to_float(string) do
|
||||
charlist = binary_to_list(string)
|
||||
{result, remainder} = :string.to_float(charlist)
|
||||
case result do
|
||||
:error ->
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
_ -> {float(int_result), list_to_binary(int_remainder)}
|
||||
end
|
||||
_ -> {result, list_to_binary(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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec starts_with?(t, t | [t]) :: boolean
|
||||
|
||||
def starts_with?(string, prefixes) when is_list(prefixes) do
|
||||
Enum.any?(prefixes, do_starts_with(string, &1))
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) do
|
||||
do_starts_with(string, prefix)
|
||||
end
|
||||
|
||||
defp do_starts_with(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0,_}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
raise ArgumentError
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec ends_with?(t, t | [t]) :: boolean
|
||||
|
||||
def ends_with?(string, suffixes) when is_list(suffixes) do
|
||||
Enum.any?(suffixes, do_ends_with(string, &1))
|
||||
end
|
||||
|
||||
def ends_with?(string, suffix) do
|
||||
do_ends_with(string, suffix)
|
||||
end
|
||||
|
||||
defp do_ends_with(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
string_size = size(string)
|
||||
suffix_size = size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
|
||||
defp do_ends_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` contains match, otherwise false.
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.contains? "elixir of life", "of"
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["life", "death"]
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["death", "mercury"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_contains(string, match) when is_binary(match) do
|
||||
:nomatch != :binary.match(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
+52
-31
@@ -14,20 +14,40 @@ defmodule System do
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
Regex.replace %r/^\s+|\s+$/, File.read!("VERSION"), ""
|
||||
defp strip_re(iodata, pattern) do
|
||||
:re.replace(iodata, pattern, "", [return: :binary])
|
||||
end
|
||||
|
||||
# Tries to run `git describe --always --tags`. In case of success
|
||||
# returns the most recent tag, otherwise returns an empty string.
|
||||
defp read_stripped(path) do
|
||||
case :file.read_file(path) do
|
||||
{ :ok, binary } ->
|
||||
strip_re(binary, "^\s+|\s+$")
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped("VERSION") do
|
||||
"" -> raise CompileError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to run `git describe --always --tags`. In the case of success returns
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
dotgit = Path.join(File.cwd!, ".git")
|
||||
if :os.find_executable('git') && File.exists?(dotgit) do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
Regex.replace %r/\n/, to_binary(data), ""
|
||||
else
|
||||
""
|
||||
dirpath = ".git"
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
strip_re(data, "\n")
|
||||
else
|
||||
read_stripped(:filename.join(".git", "HEAD"))
|
||||
end
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,15 +153,22 @@ defmodule System do
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
case System.get_env(env) do
|
||||
case get_env(env) do
|
||||
nil -> nil
|
||||
tmp -> write_tmp_dir tmp
|
||||
end
|
||||
end
|
||||
|
||||
defp write_tmp_dir(dir) do
|
||||
case File.stat(dir) do
|
||||
{ :ok, File.Stat[type: :directory, access: access] } when access in [:read_write, :write] -> dir
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__index__ :type
|
||||
access_index = File.Stat.__index__ :access
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
dir
|
||||
_ -> nil
|
||||
end
|
||||
{ :error, _ } -> nil
|
||||
end
|
||||
end
|
||||
@@ -177,21 +204,23 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
This functions looks up an executable program given
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows.
|
||||
and Windows. It also considers the proper executable
|
||||
extension for each OS, so for Windows it will try to
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
If `program` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec find_executable(char_list) :: char_list | nil
|
||||
@spec find_executable(String.t) :: String.t | nil
|
||||
def find_executable(command) when is_list(command) do
|
||||
:os.find_executable(command) || nil
|
||||
def find_executable(program) when is_list(program) do
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(command) do
|
||||
case :os.find_executable(to_char_list(command)) do
|
||||
def find_executable(program) do
|
||||
case :os.find_executable(to_char_list(program)) do
|
||||
false -> nil
|
||||
other -> list_to_binary(other)
|
||||
end
|
||||
@@ -294,19 +323,11 @@ defmodule System do
|
||||
def halt(status // 0, options // [])
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
do_halt(status, options)
|
||||
:erlang.halt(status, options)
|
||||
end
|
||||
|
||||
def halt(status, options) do
|
||||
do_halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
# Support R15B
|
||||
if List.member?(:erlang.module_info(:exports), { :halt, 2 }) do
|
||||
defp do_halt(status, options), do: :erlang.halt(status, options)
|
||||
else
|
||||
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
|
||||
defp do_halt(status, _options), do: :erlang.halt(status)
|
||||
:erlang.halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
+46
-11
@@ -37,14 +37,16 @@ defmodule URI do
|
||||
the query string in steps.
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
fn -> { do_decoder(&1), q } end
|
||||
fn(acc, fun) ->
|
||||
do_decoder(q, acc, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
:stop
|
||||
defp do_decoder("", acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decoder(q, acc, fun) do
|
||||
next =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
@@ -57,7 +59,7 @@ defmodule URI do
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
do_decoder(next, fun.(current, acc), fun)
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -131,8 +133,16 @@ defmodule URI do
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
{ userinfo, host, port } = split_authority(authority)
|
||||
|
||||
if authority do
|
||||
authority = ""
|
||||
|
||||
if userinfo, do: authority = authority <> userinfo <> "@"
|
||||
if host, do: authority = authority <> host
|
||||
if port, do: authority = authority <> ":" <> integer_to_binary(port)
|
||||
end
|
||||
|
||||
info = URI.Info[
|
||||
scheme: scheme, path: path, query: query,
|
||||
scheme: scheme && String.downcase(scheme), path: path, query: query,
|
||||
fragment: fragment, authority: authority,
|
||||
userinfo: userinfo, host: host, port: port
|
||||
]
|
||||
@@ -140,7 +150,8 @@ defmodule URI do
|
||||
scheme_specific(scheme, info)
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
@doc false
|
||||
def scheme_module(scheme) do
|
||||
if scheme do
|
||||
module =
|
||||
try do
|
||||
@@ -150,10 +161,14 @@ defmodule URI do
|
||||
end
|
||||
|
||||
if module && Code.ensure_loaded?(module) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
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
|
||||
@@ -169,7 +184,7 @@ defmodule URI do
|
||||
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
{ userinfo, host, port }
|
||||
{ userinfo, host && String.downcase(host), port }
|
||||
end
|
||||
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
@@ -192,3 +207,23 @@ defmodule URI do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: URI.Info do
|
||||
def to_binary(uri) do
|
||||
result = ""
|
||||
|
||||
if module = URI.scheme_module(uri.scheme) do
|
||||
if module.default_port == uri.port, do: uri = uri.port(nil)
|
||||
end
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
+77
-45
@@ -16,7 +16,7 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
data_path = Path.expand("../UnicodeData.txt", __FILE__)
|
||||
data_path = Path.join(__DIR__, "UnicodeData.txt")
|
||||
|
||||
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
[ codepoint, _name, _category,
|
||||
@@ -37,7 +37,7 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
special_path = Path.expand("../SpecialCasing.txt", __FILE__)
|
||||
special_path = Path.join(__DIR__, "SpecialCasing.txt")
|
||||
|
||||
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
|
||||
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
|
||||
@@ -45,7 +45,7 @@ defmodule String.Unicode do
|
||||
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
|
||||
end
|
||||
|
||||
seqs_path = Path.expand("../NamedSequences.txt", __FILE__)
|
||||
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
|
||||
|
||||
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
|
||||
[ _name, codepoints ] = :binary.split(line, ";", [:global])
|
||||
@@ -57,88 +57,120 @@ defmodule String.Unicode do
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(string), do: do_downcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: unquote(lower) <> downcase(t)
|
||||
def :downcase, args, [], do: code
|
||||
defp do_downcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(lower)) ++ downcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def downcase(<< h, t :: binary >>) do
|
||||
<< h >> <> downcase(t)
|
||||
defp do_downcase(<< char, rest :: binary >>) do
|
||||
[char|do_downcase(rest)]
|
||||
end
|
||||
|
||||
def downcase(<< >>) do
|
||||
<< >>
|
||||
end
|
||||
defp do_downcase(""), do: []
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(string), do: do_upcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: unquote(upper) <> upcase(t)
|
||||
def :upcase, args, [], do: code
|
||||
defp do_upcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(upper)) ++ do_upcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def upcase(<< h, t :: binary >>) do
|
||||
<< h >> <> upcase(t)
|
||||
defp do_upcase(<< char, rest :: binary >>) do
|
||||
[char|do_upcase(rest)]
|
||||
end
|
||||
|
||||
def upcase(<< >>) do
|
||||
<< >>
|
||||
end
|
||||
defp do_upcase(""), do: []
|
||||
|
||||
# Titlecase once
|
||||
|
||||
def titlecase_once(""), do: { "", "" }
|
||||
|
||||
lc { codepoint, _upper, _lower, title } inlist codes, title && title != codepoint do
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: { unquote(title), t }
|
||||
def :titlecase_once, args, [], do: code
|
||||
def titlecase_once(unquote(codepoint) <> rest) do
|
||||
{ unquote(title), rest }
|
||||
end
|
||||
end
|
||||
|
||||
def titlecase_once(<< h, t :: binary >>) do
|
||||
{ <<h>>, t }
|
||||
end
|
||||
|
||||
def titlecase_once(<< >>) do
|
||||
{ <<>>, <<>> }
|
||||
def titlecase_once(<< char, rest :: binary >>) do
|
||||
{ << char >>, rest }
|
||||
end
|
||||
|
||||
# Strip
|
||||
|
||||
def lstrip(""), do: ""
|
||||
|
||||
lc char inlist whitespace do
|
||||
args = quote do: [unquote(char) <> rest]
|
||||
exprs = quote do: lstrip(rest)
|
||||
def :lstrip, args, [], do: exprs
|
||||
lc codepoint inlist whitespace do
|
||||
def lstrip(unquote(codepoint) <> rest) do
|
||||
lstrip(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def lstrip(other) when is_binary(other), do: other
|
||||
|
||||
def rstrip(""), do: ""
|
||||
|
||||
def rstrip(string) when is_binary(string) do
|
||||
do_rstrip(string, "")
|
||||
do_rstrip(string, [], [])
|
||||
end
|
||||
|
||||
lc char inlist whitespace do
|
||||
args = quote do: [unquote(char) <> rest, buffer]
|
||||
exprs = quote do: do_rstrip(rest, unquote(char) <> buffer)
|
||||
defp :do_rstrip, args, [], do: exprs
|
||||
lc codepoint inlist whitespace do
|
||||
c = binary_to_list(codepoint) |> :lists.reverse
|
||||
|
||||
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
|
||||
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, string :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(string, "") :: binary >>
|
||||
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
|
||||
do_rstrip(rest, nil, [char|acc2])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _), do: <<>>
|
||||
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
|
||||
do_rstrip(rest, nil, [char|acc1])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
|
||||
|
||||
# Split
|
||||
|
||||
def split(""), do: ""
|
||||
|
||||
def split(string) when is_binary(string) do
|
||||
:lists.reverse do_split(string, "", [])
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split(<< char, rest :: binary >>, buffer, acc) do
|
||||
do_split(rest, << buffer :: binary, char >>, acc)
|
||||
end
|
||||
|
||||
defp do_split(<<>>, buffer, acc) do
|
||||
if buffer != "" do
|
||||
[buffer | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
# Graphemes
|
||||
|
||||
lc codepoints inlist seqs do
|
||||
seq_args = quote do: [<< unquote_splicing(codepoints), t :: binary >>]
|
||||
seq_code = quote do: {<< unquote_splicing(codepoints) >>, t}
|
||||
def :next_grapheme, seq_args, [], do: seq_code
|
||||
def next_grapheme(<< unquote_splicing(codepoints), t :: binary >>) do
|
||||
{ << unquote_splicing(codepoints) >>, t }
|
||||
end
|
||||
end
|
||||
|
||||
def next_grapheme(<<>>) do
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"R15"}.
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
|
||||
+36
-25
@@ -1,15 +1,16 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, eval/2, eval/3, eval/4,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0]).
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
-type char_list() :: string().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
% OTP APPLICATION API
|
||||
|
||||
@@ -19,8 +20,8 @@ start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [{encoding,unicode}]),
|
||||
io:setopts(standard_error, [{encoding,unicode}])
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
end),
|
||||
elixir_sup:start_link([]).
|
||||
|
||||
@@ -36,7 +37,7 @@ main(Args) ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(Args).
|
||||
|
||||
% Boot and process given options. Invoked by Elixir's script.
|
||||
%% Boot and process given options. Invoked by Elixir's script.
|
||||
|
||||
start_cli() ->
|
||||
application:start(?MODULE),
|
||||
@@ -45,37 +46,47 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> File = to_binary(RawFile);
|
||||
false -> File = <<"nofile">>
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> to_binary(RawFile);
|
||||
false -> Scope#elixir_scope.file
|
||||
end,
|
||||
|
||||
case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, Local } -> Local;
|
||||
false -> Local = nil
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
end,
|
||||
|
||||
case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, Aliases } -> Aliases;
|
||||
false -> Aliases = []
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
end,
|
||||
|
||||
case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> Requires = ordsets:from_list(List);
|
||||
false -> Requires = elixir_dispatch:default_requires()
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
end,
|
||||
|
||||
case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, Functions } -> Functions;
|
||||
false -> Functions = elixir_dispatch:default_functions()
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
end,
|
||||
|
||||
case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, Macros } -> Macros;
|
||||
false -> Macros = elixir_dispatch:default_macros()
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
end,
|
||||
|
||||
#elixir_scope{
|
||||
Scope#elixir_scope{
|
||||
file=File, local=Local,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
|
||||
@@ -1,75 +1,134 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting/2, last/1, concat/1, safe_concat/1, lookup/2,
|
||||
format_error/1, ensure_loaded/3, expand/2]).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Expand an alias returning an atom if possible.
|
||||
%% Otherwise returns the list of aliases args.
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, S) -> S;
|
||||
store(Meta, New, Old, S) ->
|
||||
SA = S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
},
|
||||
|
||||
expand({ '__aliases__', _Line, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
expand_alias(H, Aliases);
|
||||
case lists:keymember(context, 1, Meta) of
|
||||
true ->
|
||||
SA#elixir_scope{
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
|
||||
};
|
||||
false ->
|
||||
SA
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Line, [H|T] }, Aliases) when is_atom(H) ->
|
||||
concat(case H of
|
||||
'Elixir' -> [H|T];
|
||||
_ -> [expand_alias(H, Aliases)|T]
|
||||
end);
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', _Line, List }, _Aliases) ->
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
expand(Alias, MacroAliases);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
case expand(Alias, MacroAliases) of
|
||||
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
|
||||
OtherAliases when is_list(OtherAliases) -> Atom
|
||||
end;
|
||||
false ->
|
||||
expand(Alias, Aliases)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases) of
|
||||
false -> [H];
|
||||
Atom -> Atom
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
|
||||
case H of
|
||||
'Elixir' ->
|
||||
concat(T);
|
||||
_ ->
|
||||
case expand_one(H, Aliases) of
|
||||
false -> [H|T];
|
||||
Atom -> concat([Atom|T])
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_alias(H, Aliases) ->
|
||||
lookup(list_to_atom("Elixir-" ++ atom_to_list(H)), Aliases).
|
||||
expand_one(H, Aliases) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
|
||||
|
||||
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
|
||||
ok;
|
||||
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
ensure_loaded(Line, File, Ref, FileModules) ->
|
||||
try
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
|
||||
Kind = case lists:member(Ref, FileModules) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last alias.
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
|
||||
last(Atom) ->
|
||||
Last = last(lists:reverse(atom_to_list(Atom)), []),
|
||||
list_to_atom("Elixir-" ++ Last).
|
||||
list_to_atom("Elixir." ++ Last).
|
||||
|
||||
last([$-|_], Acc) -> Acc;
|
||||
last([$.|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
%% Returns the nesting between two aliases.
|
||||
%% Gets two modules names and return an alias
|
||||
%% which can be passed down to the alias directive
|
||||
%% and it will create a proper shortcut representing
|
||||
%% the given nesting.
|
||||
%%
|
||||
%% Examples:
|
||||
%%
|
||||
%% nesting_alias('Elixir.Foo.Bar', 'Elixir.Foo.Bar.Baz.Bat')
|
||||
%% { 'Elixir.Baz', 'Elixir.Foo.Bar.Baz' }
|
||||
%%
|
||||
%% When passed to alias, the example above will generate an
|
||||
%% alias like:
|
||||
%%
|
||||
%% 'Elixir.Baz' => 'Elixir.Foo.Bar.Baz'
|
||||
%%
|
||||
nesting_alias(nil, _Full) -> false;
|
||||
|
||||
nesting(nil, _Full) -> false;
|
||||
|
||||
nesting(Prefix, Full) ->
|
||||
nesting_alias(Prefix, Full) ->
|
||||
PrefixList = list_nesting(Prefix),
|
||||
FullList = list_nesting(Full),
|
||||
(PrefixList /= []) andalso do_nesting(PrefixList, FullList, []).
|
||||
|
||||
(PrefixList /= []) andalso
|
||||
(PrefixList /= FullList) andalso
|
||||
lists:prefix(PrefixList, FullList) andalso
|
||||
binary_to_atom(<<"Elixir-", (hd(FullList -- PrefixList))/binary>>, utf8).
|
||||
do_nesting([X|PreTail], [X|Tail], Acc) ->
|
||||
do_nesting(PreTail, Tail, [X|Acc]);
|
||||
do_nesting([], [H|_], Acc) ->
|
||||
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case binary:split(atom_to_binary(Atom, utf8), <<$->>, [global]) of
|
||||
[<<"Elixir">>|T] -> T;
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
%% Receives a list of atoms representing modules
|
||||
%% and concatenate them.
|
||||
%% Receives a list of atoms, binaries or lists
|
||||
%% representing modules and concatenates them.
|
||||
|
||||
concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
@@ -78,22 +137,16 @@ raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat(Args) -> do_concat(Args).
|
||||
|
||||
do_concat(Args) ->
|
||||
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
|
||||
Aliases = [to_partial(Arg) || Arg <- Args],
|
||||
"Elixir" ++ lists:concat(Aliases).
|
||||
|
||||
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
|
||||
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
|
||||
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
|
||||
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
|
||||
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
|
||||
to_partial("Elixir." ++ Arg) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
|
||||
dot_to_dash(List) ->
|
||||
[case X of
|
||||
$. -> $-;
|
||||
_ -> X
|
||||
end || X <- List].
|
||||
|
||||
%% Lookup an alias in the current scope
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
lookup(Else, Dict) ->
|
||||
case orddict:find(Else, Dict) of
|
||||
@@ -104,8 +157,8 @@ lookup(Else, Dict) ->
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~s is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~s is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
@@ -1,10 +1,9 @@
|
||||
%% Handle code related to rocket args, guard and -> matching
|
||||
%% for case, fn, receive and friends. try is handled in elixir_try.
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
@@ -19,15 +18,15 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
% Function for translating assigns.
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=orddict:new()}),
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match, temp_vars=orddict:new()}),
|
||||
{ Result, NewS#elixir_scope{context=Context} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
@@ -105,66 +104,61 @@ do_match(Meta, DecoupledClauses, S) ->
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(Meta, X, Acc),
|
||||
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(RawCV),
|
||||
CV = lists:reverse(ReverseCV),
|
||||
AllVars = lists:foldl(fun(KV, Acc) ->
|
||||
elixir_scope:merge_clause_vars(Acc, KV)
|
||||
end, orddict:new(), CV),
|
||||
|
||||
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
|
||||
SharedVars = ordsets:intersection(CV),
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
|
||||
normalize_vars(Key, Ref, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
case AllVars of
|
||||
[] -> { TClauses, TS };
|
||||
_ ->
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
|
||||
% Defines a tuple that will be used as left side of the match operator
|
||||
Line = ?line(Meta),
|
||||
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
case generate_match_vars(FinalVars, ClauseVars, [], []) of
|
||||
{ [], [] } ->
|
||||
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
|
||||
{ Left, Right } ->
|
||||
MatchExpr = generate_match(Line, Left, Right),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
expand_clauses(Meta, TClauses, CV, LeftVars, FinalVars, [], FS)
|
||||
end.
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
false ->
|
||||
{ [Final,MatchExpr|RawClauseExprs], S }
|
||||
end,
|
||||
|
||||
expand_clauses(Meta, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
|
||||
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
|
||||
{ Var, Kind, _, OldValue } <- FinalVars],
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
|
||||
end;
|
||||
|
||||
AssignExpr = generate_match(Meta, LeftVars, RightVars),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
Line = ?line(Meta),
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
false ->
|
||||
{ [Final,AssignExpr|RawClauseExprs], S }
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Meta, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
|
||||
|
||||
expand_clauses(_Meta, [], [], _LeftVars, _FinalVars, Acc, S) ->
|
||||
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
{ lists:reverse(Acc), S }.
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
@@ -181,10 +175,10 @@ each_clause(Meta, { 'after', [Condition], Expr }, S) ->
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause(Meta, { Key, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s", [Key]).
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -210,24 +204,14 @@ has_match_tuple(H) when is_list(H) ->
|
||||
|
||||
has_match_tuple(_) -> false.
|
||||
|
||||
% Normalize the given var checking its existence in the scope var dictionary.
|
||||
% Normalize the given var in between clauses
|
||||
% by picking one value as reference and retriving
|
||||
% its previous value.
|
||||
|
||||
normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
{ NewValue, S1 } =
|
||||
case orddict:find(Key, Shared) of
|
||||
{ ok, SharedValue } ->
|
||||
{ SharedValue, S };
|
||||
error ->
|
||||
{ { _, _, ErlValue }, ErlS } = case (Kind /= nil) or (S#elixir_scope.noname) of
|
||||
true -> elixir_scope:build_erl_var(0, S);
|
||||
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
|
||||
end,
|
||||
{ ErlValue, ErlS }
|
||||
end,
|
||||
|
||||
S2 = S1#elixir_scope{
|
||||
vars=orddict:store(Key, NewValue, Vars),
|
||||
clause_vars=orddict:store(Key, NewValue, ClauseVars)
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
FS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
clause_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
@@ -235,27 +219,40 @@ normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Var, Kind, NewValue, Expr }, S2 }.
|
||||
{ { Key, Value, Expr }, FS }.
|
||||
|
||||
% Normalize a var by checking if it was defined in the clause.
|
||||
% If so, use it, otherwise use from main scope.
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
|
||||
case orddict:find({ Var, Kind }, ClauseVars) of
|
||||
{ ok, ClauseValue } -> { var, 0, ClauseValue };
|
||||
error -> OldValue
|
||||
end.
|
||||
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, NewValue } ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, ClauseValue } ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
|
||||
end;
|
||||
|
||||
%% generate_match
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
{ Left, Right }.
|
||||
|
||||
generate_match(Meta, [Left], [Right]) ->
|
||||
{ match, ?line(Meta), Left, Right };
|
||||
generate_match(Line, [Left], [Right]) ->
|
||||
{ match, Line, Left, Right };
|
||||
|
||||
generate_match(Meta, LeftVars, RightVars) ->
|
||||
Line = ?line(Meta),
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
%% Listify
|
||||
|
||||
listify(Expr) when not is_list(Expr) -> [Expr];
|
||||
listify(Expr) -> Expr.
|
||||
listify(Expr) -> Expr.
|
||||
|
||||
%% We don't use umergec because imports, aliases and
|
||||
%% what not are not passed from one clause to the other.
|
||||
merge_clauses_scope(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
|
||||
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
|
||||
}.
|
||||
@@ -30,13 +30,6 @@ start_link() ->
|
||||
init(_args) ->
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
handle_call({ wait_until_finished, Pid }, _, Config) ->
|
||||
Waiting = Config#elixir_code_server.waiting,
|
||||
case is_list(Waiting) of
|
||||
true -> { reply, wait, Config#elixir_code_server{waiting=[Pid|Waiting]} };
|
||||
false -> { reply, ok, Config }
|
||||
end;
|
||||
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
@@ -87,11 +80,6 @@ handle_cast({ compiler_options, Options }, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_cast(finished, Config) ->
|
||||
Waiting = Config#elixir_code_server.waiting,
|
||||
[Pid ! { elixir_code_server, finished } || Pid <- lists:reverse(Waiting)],
|
||||
{ noreply, Config#elixir_code_server{waiting=done} };
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4]).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Public API
|
||||
|
||||
@@ -22,11 +21,14 @@ get_opts() ->
|
||||
%% Compiles the given string.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
Previous = get(elixir_compiled),
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -89,11 +91,17 @@ module(Forms, S, Callback) ->
|
||||
end,
|
||||
module(Forms, S#elixir_scope.file, Options, false, Callback).
|
||||
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(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),
|
||||
case compile:forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
format_warnings(Bootstrap, File, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
@@ -112,6 +120,38 @@ core() ->
|
||||
|
||||
%% HELPERS
|
||||
|
||||
compile_opts(Forms, Options) ->
|
||||
EnvOptions = env_default_opts(),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
env_default_opts() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
@@ -157,7 +197,7 @@ core_file(File) ->
|
||||
try
|
||||
Lists = file(list_to_binary(File)),
|
||||
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||
io:format("Compiled ~s~n", [File])
|
||||
io:format("Compiled ~ts~n", [File])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
|
||||
@@ -172,7 +212,6 @@ core_main() ->
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/record/extractor.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/exception.ex",
|
||||
@@ -181,23 +220,24 @@ core_main() ->
|
||||
"lib/elixir/lib/enum.ex",
|
||||
"lib/elixir/lib/binary/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/list/chars.ex",
|
||||
"lib/elixir/lib/io.ex",
|
||||
"lib/elixir/lib/file.ex",
|
||||
"lib/elixir/lib/path.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/regex.ex",
|
||||
"lib/elixir/lib/system.ex",
|
||||
"lib/elixir/lib/kernel/cli.ex",
|
||||
"lib/elixir/lib/kernel/error_handler.ex",
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex",
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex"
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex",
|
||||
"lib/elixir/lib/module/dispatch_tracker.ex"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
|
||||
format_errors(_File, []) ->
|
||||
exit({nocompile, "compilation failed but no error was raised"});
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
format_errors(File, Errors) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
@@ -207,4 +247,4 @@ format_errors(File, Errors) ->
|
||||
format_warnings(Bootstrap, File, Warnings) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
end, Warnings).
|
||||
end, Warnings).
|
||||
|
||||
+193
-120
@@ -1,34 +1,60 @@
|
||||
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1,
|
||||
build_table/1,
|
||||
delete_table/1,
|
||||
clauses_table/1,
|
||||
setup/1,
|
||||
cleanup/1,
|
||||
reset_last/1,
|
||||
wrap_definition/7,
|
||||
store_definition/8,
|
||||
store_each/7,
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
wrap_definition/6,
|
||||
wrap_definition/8,
|
||||
store_definition/7,
|
||||
store_definition/9,
|
||||
store_each/8,
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-define(attr, '__def_table').
|
||||
-define(clauses_attr, '__clauses_table').
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
table(Module) -> ?atom_concat([f, Module]).
|
||||
table(Module) ->
|
||||
ets:lookup_element(Module, ?attr, 2).
|
||||
|
||||
build_table(Module) ->
|
||||
FunctionTable = table(Module),
|
||||
ets:new(FunctionTable, [set, named_table, public]),
|
||||
clauses_table(Module) ->
|
||||
ets:lookup_element(Module, ?clauses_attr, 2).
|
||||
|
||||
setup(Module) ->
|
||||
ets:insert(Module, { ?attr, ets:new(Module, [set, public]) }),
|
||||
ets:insert(Module, { ?clauses_attr, ets:new(Module, [bag, public]) }),
|
||||
reset_last(Module),
|
||||
FunctionTable.
|
||||
ok.
|
||||
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
cleanup(Module) ->
|
||||
ets:delete(table(Module)),
|
||||
ets:delete(clauses_table(Module)).
|
||||
|
||||
%% Reset the last item. Useful when evaling code.
|
||||
reset_last(Module) ->
|
||||
ets:insert(table(Module), { last, [] }).
|
||||
|
||||
%% Looks up a definition from the database.
|
||||
lookup_definition(Module, Tuple) ->
|
||||
case ets:lookup(table(Module), Tuple) of
|
||||
[Result] ->
|
||||
CTable = clauses_table(Module),
|
||||
{ Result, [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)] };
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete_definition(Module, Tuple) ->
|
||||
ets:delete(table(Module), Tuple),
|
||||
ets:delete(clauses_table(Module), Tuple).
|
||||
|
||||
%% Wraps the function into a call to store_definition once the function
|
||||
%% definition is read. The function is compiled into a meta tree to ensure
|
||||
%% we will receive the full function.
|
||||
@@ -44,63 +70,78 @@ reset_last(Module) ->
|
||||
%%
|
||||
%% If we just analyzed the compiled structure (i.e. the function availables
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
|
||||
Line = ?line(Meta),
|
||||
MetaS = elixir_scope:serialize(S),
|
||||
|
||||
Invoke = [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{var, Line, '_@MODULE'},
|
||||
Name,
|
||||
Args,
|
||||
Guards,
|
||||
Expr,
|
||||
MetaS
|
||||
],
|
||||
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Call, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Name, Args, Guards, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
|
||||
Line = ?line(Meta),
|
||||
Invoke =
|
||||
[{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'}] ++ Extra,
|
||||
|
||||
?wrap_call(Line, ?MODULE, store_definition, Invoke).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~s", [Kind]);
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
|
||||
Tuple ->
|
||||
Tuple
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
Arity = length(Args),
|
||||
DS = elixir_scope:deserialize(RawS),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Expr = def_body(Line, Body),
|
||||
Tuple = { Name, Arity },
|
||||
S = DS#elixir_scope{module=Module, function=Tuple},
|
||||
elixir_tracker:record_definition(Tuple, Kind, Module),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Line, Module, S, CO),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
%% Store function
|
||||
if
|
||||
(Body == nil) -> [];
|
||||
true ->
|
||||
compile_super(Module, TS),
|
||||
CheckClauses = S#elixir_scope.check_clauses,
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, length(Defaults), Function)
|
||||
end,
|
||||
compile_super(Module, TS),
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
|
||||
[store_each(false, Kind, File, Location, Table, 0,
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
{ Name, Arity };
|
||||
|
||||
def_body(_Line, nil) -> nil;
|
||||
def_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
def_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
|
||||
%% @on_definition
|
||||
|
||||
@@ -110,8 +151,8 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
elixir_module:eval_callbacks(Line, Module, on_definition,
|
||||
[Env, Kind, Name, Args, Guards, Expr], S)
|
||||
end.
|
||||
|
||||
%% Retrieve @file or fallback to default
|
||||
@@ -138,22 +179,34 @@ compile_super(_Module, _S) -> ok.
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_integer(Line) ->
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_integer(Line) ->
|
||||
Args = elixir_quote:linify(Line, RawArgs),
|
||||
Guards = elixir_quote:linify(Line, RawGuards),
|
||||
Expr = elixir_quote:linify(Line, RawExpr),
|
||||
Body = elixir_quote:linify(Line, RawBody),
|
||||
Arity = length(Args),
|
||||
IsMacro = is_macro(Kind),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
ExtendedArgs = case IsMacro of
|
||||
ExtendedArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
end,
|
||||
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
|
||||
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
|
||||
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
|
||||
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]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
@@ -167,7 +220,7 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_i
|
||||
end,
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case IsMacro andalso TS#elixir_scope.caller of
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = { 'match', Line,
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
@@ -177,8 +230,10 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_i
|
||||
false -> NClause
|
||||
end,
|
||||
|
||||
Function = { function, Line, Name, Arity, [FClause] },
|
||||
{ Function, Defaults, TS }.
|
||||
{ [FClause], TS }.
|
||||
|
||||
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -189,58 +244,59 @@ is_macro(_) -> false.
|
||||
% and the body of all functions.
|
||||
unwrap_stored_definitions(File, Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_stored_definition(ets:tab2list(Table), File, [], [], [], [], [], {[],[]}).
|
||||
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
|
||||
Tuple = element(1, Fun),
|
||||
unwrap_stored_definition(
|
||||
T, File, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
|
||||
function_for_stored_definition(Fun, File, Functions)
|
||||
);
|
||||
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
|
||||
{ Name, Arity } = Tuple = element(1, Fun),
|
||||
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
|
||||
case Clauses of
|
||||
[] -> { false, Exports, Private, Def, Defmacro };
|
||||
_ -> unwrap_stored_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
false -> { Functions, Tail };
|
||||
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
end,
|
||||
|
||||
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
|
||||
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
|
||||
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
|
||||
|
||||
unwrap_stored_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = { ?elixir_macro(Name), Arity + 1 },
|
||||
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
|
||||
|
||||
unwrap_stored_definition(
|
||||
T, File, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
|
||||
function_for_stored_definition(setelement(1, Fun, Macro), File, Functions)
|
||||
);
|
||||
unwrap_stored_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ Fun, Exports, [Info|Private], Def, Defmacro };
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
|
||||
unwrap_stored_definition(
|
||||
T, File, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
|
||||
function_for_stored_definition(Fun, File, Functions)
|
||||
);
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
|
||||
Tuple = element(1, Fun),
|
||||
unwrap_stored_definition(
|
||||
T, File, Exports, [Tuple|Private], Def, Defmacro,
|
||||
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
|
||||
);
|
||||
|
||||
unwrap_stored_definition([], _File, Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
|
||||
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
|
||||
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
|
||||
unwrap_stored_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ false, Exports, [Info|Private], Def, Defmacro }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, _, { File, _ }, _, Clauses}, File, {Functions,Tail}) ->
|
||||
{
|
||||
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
|
||||
Tail
|
||||
};
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, _File, {Functions,Tail}) ->
|
||||
{
|
||||
Functions,
|
||||
[
|
||||
{ function, Line, Name, Arity, lists:reverse(Clauses) },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
]
|
||||
}.
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
] }.
|
||||
|
||||
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
@@ -253,22 +309,24 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, File, Location, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
case ets:lookup(Table, {Name, Arity}) of
|
||||
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
|
||||
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 }] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = Defaults + StoredDefaults,
|
||||
FinalClauses = Clauses ++ StoredClauses,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults,
|
||||
FinalClauses = Clauses,
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } })
|
||||
FinalDefaults = Defaults
|
||||
end,
|
||||
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } }),
|
||||
ets:insert(CTable, [{ Tuple, Clause } || Clause <- Clauses ]),
|
||||
ets:insert(Table, { Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults }).
|
||||
|
||||
%% Validations
|
||||
|
||||
@@ -277,26 +335,41 @@ 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, Table) ->
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
{ElseName, ElseArity} ->
|
||||
_ ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
|
||||
{ override_function, { Kind, Name, Arity } } })
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } } }).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [Atom]);
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({clauses_with_docs,{Name,Arity}}) ->
|
||||
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
|
||||
format_error({no_module,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
|
||||
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
|
||||
format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("~ts ~ts/~B has default values and multiple clauses, use a separate clause for declaring 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({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~s ~s/~B already defined as ~s", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
@@ -1,19 +1,14 @@
|
||||
%% Module responsible for local invocation of macros and functions.
|
||||
-module(elixir_def_local).
|
||||
-export([
|
||||
macro_for/3,
|
||||
function_for/3,
|
||||
format_error/1,
|
||||
check_unused_local_macros/3
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Used by elixir_dispatch, returns false if no macro is found
|
||||
macro_for(_Tuple, _All, nil) -> false;
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, Module) ->
|
||||
try ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
|
||||
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);
|
||||
_ ->
|
||||
false
|
||||
@@ -21,26 +16,25 @@ macro_for(Tuple, All, Module) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% Used on runtime by rewritten clauses, raises an error if function is not found
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
%% Helpers
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
%% TODO: Consider caching functions in a table for performance.
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
@@ -65,14 +59,4 @@ rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% Error handling
|
||||
|
||||
check_unused_local_macros(File, Recorded, Defmacrop) ->
|
||||
[elixir_errors:handle_file_warning(File,
|
||||
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
|
||||
Check, not lists:member(Fun, Recorded)].
|
||||
|
||||
format_error({unused_macro,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
@@ -16,13 +15,13 @@ overridable(Module, Value) ->
|
||||
is_defined(Module, Tuple) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, _, _ } } -> true;
|
||||
{ ok, { _, _, _, _ } } -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
case is_defined(Module, Tuple) of
|
||||
true -> [];
|
||||
true -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
@@ -54,7 +53,7 @@ name(Module, Function) ->
|
||||
name(Module, Function, overridable(Module)).
|
||||
|
||||
name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
{ Count, _, _ } = orddict:fetch(Function, Overridable),
|
||||
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
|
||||
?atom_concat([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
@@ -62,33 +61,35 @@ name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
store(Module, Function, GenerateName) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:fetch(Function, Overridable) of
|
||||
{ _Count, _Clause, true } -> ok;
|
||||
{ Count, Clause, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
|
||||
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
|
||||
{ _Count, _Clause, _Neighbours, true } -> ok;
|
||||
{ Count, Clause, Neighbours, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
|
||||
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
true -> { defp, name(Module, Function, Overridable) };
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), Defaults, Def)
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
end.
|
||||
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
store_pending(Module) ->
|
||||
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
|
||||
[store(Module, X, false) || { X, { _, _, _, false } } <- overridable(Module),
|
||||
not 'Elixir.Module':'defines?'(Module, X)].
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
|
||||
+174
-102
@@ -5,45 +5,44 @@
|
||||
-export([default_macros/0, default_functions/0, default_requires/0,
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/8, expand_require/8, find_import/4,
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(BUILTIN, 'Elixir.Kernel').
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
[ { ?builtin, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
default_macros() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
[ { ?builtin, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
default_requires() ->
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
|
||||
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(_Meta, Name, Arity, S) ->
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case find_dispatch(Tuple, S#elixir_scope.macros) of
|
||||
false -> false;
|
||||
Receiver -> Receiver
|
||||
end;
|
||||
Receiver -> Receiver
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } -> Receiver;
|
||||
{ macro, Receiver } -> Receiver;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% Function retrieval
|
||||
|
||||
import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case find_dispatch(Tuple, S#elixir_scope.macros) of
|
||||
false -> { { 'fun', ?line(Meta), { function, Name, Arity } }, S };
|
||||
_ -> false
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
nomatch ->
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
@@ -51,7 +50,9 @@ require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Meta, Receiver, Name, Arity, S)
|
||||
false ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
%% Function dispatch
|
||||
@@ -61,25 +62,27 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case expand_import(Meta, Tuple, Args, Module, S#elixir_scope.function,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S)
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
@@ -87,114 +90,172 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_translator:translate_each({ { '.', Meta, [erlang, Name] }, Meta, Args }, S);
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module,
|
||||
S#elixir_scope.function, S#elixir_scope.requires, S) of
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, Tree } ->
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
|
||||
case find_dispatch(Tuple, Macros) of
|
||||
false ->
|
||||
Fun = (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, Module),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Module, Module),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, Requires, SEnv) }
|
||||
end;
|
||||
?BUILTIN ->
|
||||
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
Result = find_dispatch(Meta, Tuple, Extra, S),
|
||||
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?builtin } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
{ macro, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
Fun = (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
|
||||
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true -> { ok, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
|
||||
false -> { error, noexpansion }
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
{ error, noexpansion }
|
||||
end
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
|
||||
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, Module),
|
||||
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),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> { ok, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
|
||||
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) of
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
Requires = S#elixir_scope.requires,
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
|
||||
true -> ok;
|
||||
false ->
|
||||
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, elixir_scope:filename(SEnv), ?MODULE, Tuple)
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
end,
|
||||
|
||||
Line = ?line(Meta),
|
||||
SArg = {Line,SEnv},
|
||||
SArg = {Line,S},
|
||||
|
||||
try
|
||||
apply(Fun, [SArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), [{ file, elixir_scope:filename(SEnv) }, { line, Line }] },
|
||||
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
ProperName = ?elixir_macro(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv).
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
{ TR, TS } = elixir_translator:translate_each(
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(?line(Meta), Tree),
|
||||
S#elixir_scope{check_clauses=false}
|
||||
),
|
||||
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
|
||||
S
|
||||
).
|
||||
|
||||
%% Helpers
|
||||
|
||||
find_dispatch(Tuple, [{ Name, Values }|T]) ->
|
||||
case is_element(Tuple, Values) of
|
||||
true -> Name;
|
||||
false -> find_dispatch(Tuple, T)
|
||||
end;
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
|
||||
find_dispatch(_Tuple, []) -> false.
|
||||
find_dispatch(Meta, Tuple, S) ->
|
||||
find_dispatch(Meta, Tuple, [], S).
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
case is_import(Meta, Tuple, S) of
|
||||
{ import, _ } = Import ->
|
||||
Import;
|
||||
false ->
|
||||
Functions = S#elixir_scope.functions,
|
||||
Macros = Extra ++ S#elixir_scope.macros,
|
||||
File = S#elixir_scope.file,
|
||||
FunMatch = find_dispatch(Tuple, Functions),
|
||||
MacMatch = find_dispatch(Tuple, Macros),
|
||||
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
{ [Receiver], [] } -> { function, Receiver };
|
||||
{ [], [] } -> nomatch;
|
||||
_ ->
|
||||
{ Name, Arity } = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Error)
|
||||
end
|
||||
end.
|
||||
|
||||
find_dispatch(Tuple, List) ->
|
||||
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
|
||||
|
||||
is_import(Meta, Tuple, S) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = Import ->
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, Context } ->
|
||||
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
|
||||
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
|
||||
andalso Import;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
not_an_import(Tuple, Context, Dict) ->
|
||||
case orddict:find(Context, Dict) of
|
||||
{ ok, Pairs } ->
|
||||
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
|
||||
error ->
|
||||
true
|
||||
end.
|
||||
|
||||
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
@@ -208,20 +269,11 @@ munge_stacktrace(Info, [H|T], S) ->
|
||||
munge_stacktrace(_, [], _) ->
|
||||
[].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
|
||||
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
|
||||
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~s",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case Receiver == ?BUILTIN andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
@@ -232,6 +284,20 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, 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({ 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)]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||
get_optional_macros(erlang) -> [];
|
||||
|
||||
@@ -250,7 +316,7 @@ get_optional_macros(Receiver) ->
|
||||
|
||||
in_elixir_functions() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(functions)
|
||||
?builtin:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -259,7 +325,7 @@ in_elixir_functions() ->
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(macros)
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -273,6 +339,10 @@ in_erlang_functions() ->
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_list, 1 },
|
||||
{ binary_to_list, 3 },
|
||||
{ binary_to_term, 1 },
|
||||
@@ -283,8 +353,11 @@ in_erlang_functions() ->
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
@@ -379,7 +452,6 @@ in_erlang_macros() ->
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{function,1},
|
||||
{function,2},
|
||||
{function,3},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
@@ -387,4 +459,4 @@ in_erlang_macros() ->
|
||||
{'receive',1},
|
||||
{'try',1},
|
||||
{'xor',2}
|
||||
].
|
||||
].
|
||||
|
||||
@@ -4,25 +4,16 @@
|
||||
-export([syntax_error/3, syntax_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_assign_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_assign_or_guard_scope/3,
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
inspect(Other) -> Other.
|
||||
|
||||
to_dot($-) -> $.;
|
||||
to_dot(L) -> L.
|
||||
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
@@ -37,7 +28,7 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
|
||||
|
||||
syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
|
||||
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
@@ -61,7 +52,7 @@ parse_error(Meta, File, Error, Token) ->
|
||||
|
||||
BinToken = if
|
||||
Token == [] -> <<>>;
|
||||
true -> iolist_to_binary(Token)
|
||||
true -> unicode:characters_to_binary(Token)
|
||||
end,
|
||||
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
@@ -77,10 +68,10 @@ form_error(Meta, File, Module, Desc) ->
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Line, File, Message) -> deprecation(Line, File, Message, []).
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
|
||||
deprecation(Line, File, Message, Args) ->
|
||||
io:format(file_format(Line, File, io_lib:format(Message, Args))).
|
||||
deprecation(Meta, File, Message, Args) ->
|
||||
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
@@ -94,10 +85,13 @@ handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
|
||||
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
|
||||
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
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
|
||||
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
@@ -105,10 +99,13 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
[];
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
@@ -134,26 +131,26 @@ handle_file_error(File, {Line,Module,Desc}) ->
|
||||
|
||||
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Meta, Kind, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
|
||||
|
||||
assert_no_assign_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_assign_scope(Meta, Kind, S),
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_assign_scope(Meta, Kind, #elixir_scope{context=assign} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
|
||||
assert_no_assign_scope(_Meta, _Kind, _S) -> [].
|
||||
assert_no_match_scope(Meta, Kind, #elixir_scope{context=match} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside match clause", [Kind]);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside guard", [Kind]);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~s outside module", [Kind]);
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~s outside function", [Kind]);
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
%% Helpers
|
||||
@@ -161,11 +158,17 @@ assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Functio
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
raise(?line(Meta), File, Kind, Message);
|
||||
|
||||
raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
erlang:raise(error, Exception, Stacktrace).
|
||||
|
||||
file_format(0, File, Message) ->
|
||||
io_lib:format("~ts: ~ts~n", [File, Message]);
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
|
||||
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
%% Modified version of filelib:wildcard that can handle "**"
|
||||
%% and automatically skips directories starting with "." by default.
|
||||
|
||||
%% %CopyrightBegin%
|
||||
%%
|
||||
%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
|
||||
%%
|
||||
%% The contents of this file are subject to the Erlang Public License,
|
||||
%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You should have received a copy of the
|
||||
%% Erlang Public License along with this software. If not, it can be
|
||||
%% retrieved online at http://www.erlang.org/.
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and limitations
|
||||
%% under the License.
|
||||
%%
|
||||
%% %CopyrightEnd%
|
||||
|
||||
-module(elixir_glob).
|
||||
-compile({no_auto_import,[error/1]}).
|
||||
-export([wildcard/1]).
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
-define(HANDLE_ERROR(Expr),
|
||||
try
|
||||
Expr
|
||||
catch
|
||||
error:{badpattern,_}=UnUsUalVaRiAbLeNaMe ->
|
||||
%% Get the stack backtrace correct.
|
||||
erlang:error(UnUsUalVaRiAbLeNaMe)
|
||||
end).
|
||||
|
||||
wildcard(Pattern) when is_list(Pattern) ->
|
||||
?HANDLE_ERROR(do_wildcard(Pattern, file)).
|
||||
|
||||
do_wildcard(Pattern, Mod) when is_list(Pattern) ->
|
||||
do_wildcard_comp(do_compile_wildcard(Pattern), Mod).
|
||||
|
||||
do_wildcard_comp({compiled_wildcard,{exists,File}}, Mod) ->
|
||||
case eval_read_file_info(File, Mod) of
|
||||
{ok,_} -> [File];
|
||||
_ -> []
|
||||
end;
|
||||
do_wildcard_comp({compiled_wildcard,[Base|Rest]}, Mod) ->
|
||||
do_wildcard_1([Base], Rest, Mod).
|
||||
|
||||
%%%
|
||||
%%% Pattern matching using a compiled wildcard.
|
||||
%%%
|
||||
|
||||
do_wildcard_1(Files, Pattern, Mod) ->
|
||||
do_wildcard_2(Files, Pattern, [], Mod).
|
||||
|
||||
do_wildcard_2([File|Rest], Pattern, Result, Mod) ->
|
||||
do_wildcard_2(Rest, Pattern, do_wildcard_3(File, Pattern, Result, Mod), Mod);
|
||||
do_wildcard_2([], _, Result, _Mod) ->
|
||||
Result.
|
||||
|
||||
do_wildcard_3(Base, [[double_star]|Rest], Result, Mod) ->
|
||||
lists:sort(do_double_star(current, [Base], Rest, Result, Mod, true));
|
||||
do_wildcard_3(Base, [Pattern|Rest], Result, Mod) ->
|
||||
case do_list_dir(Base, Mod) of
|
||||
{ok, Files0} ->
|
||||
Files = lists:sort(Files0),
|
||||
Matches = wildcard_4(Pattern, Files, Base, []),
|
||||
do_wildcard_2(Matches, Rest, Result, Mod);
|
||||
_ ->
|
||||
Result
|
||||
end;
|
||||
do_wildcard_3(Base, [], Result, _Mod) ->
|
||||
[Base|Result].
|
||||
|
||||
wildcard_4(Pattern, [File|Rest], Base, Result) when is_binary(File) ->
|
||||
case wildcard_5(Pattern, binary_to_list(File)) of
|
||||
true ->
|
||||
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
|
||||
false ->
|
||||
wildcard_4(Pattern, Rest, Base, Result)
|
||||
end;
|
||||
wildcard_4(Pattern, [File|Rest], Base, Result) ->
|
||||
case wildcard_5(Pattern, File) of
|
||||
true ->
|
||||
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
|
||||
false ->
|
||||
wildcard_4(Pattern, Rest, Base, Result)
|
||||
end;
|
||||
wildcard_4(_Patt, [], _Base, Result) ->
|
||||
Result.
|
||||
|
||||
wildcard_5([question|Rest1], [_|Rest2]) ->
|
||||
wildcard_5(Rest1, Rest2);
|
||||
wildcard_5([accept], _) ->
|
||||
true;
|
||||
wildcard_5([double_star], _) ->
|
||||
true;
|
||||
wildcard_5([star|Rest], File) ->
|
||||
do_star(Rest, File);
|
||||
wildcard_5([{one_of, Ordset}|Rest], [C|File]) ->
|
||||
case ordsets:is_element(C, Ordset) of
|
||||
true -> wildcard_5(Rest, File);
|
||||
false -> false
|
||||
end;
|
||||
wildcard_5([{alt, Alts}], File) ->
|
||||
do_alt(Alts, File);
|
||||
wildcard_5([C|Rest1], [C|Rest2]) when is_integer(C) ->
|
||||
wildcard_5(Rest1, Rest2);
|
||||
wildcard_5([X|_], [Y|_]) when is_integer(X), is_integer(Y) ->
|
||||
false;
|
||||
wildcard_5([], []) ->
|
||||
true;
|
||||
wildcard_5([], [_|_]) ->
|
||||
false;
|
||||
wildcard_5([_|_], []) ->
|
||||
false.
|
||||
|
||||
do_double_star(Base, [H|T], Rest, Result, Mod, Root) ->
|
||||
Full = join(Base, H),
|
||||
Result1 = case do_list_dir(Full, Mod) of
|
||||
{ok, Files} ->
|
||||
do_double_star(Full, Files, Rest, Result, Mod, false);
|
||||
_ -> Result
|
||||
end,
|
||||
Result2 = case Root andalso Rest == [] of
|
||||
true -> Result1;
|
||||
false -> do_wildcard_3(Full, Rest, Result1, Mod)
|
||||
end,
|
||||
do_double_star(Base, T, Rest, Result2, Mod, Root);
|
||||
do_double_star(_Base, [], _Rest, Result, _Mod, _Root) ->
|
||||
Result.
|
||||
|
||||
do_star(Pattern, [X|Rest]) ->
|
||||
case wildcard_5(Pattern, [X|Rest]) of
|
||||
true -> true;
|
||||
false -> do_star(Pattern, Rest)
|
||||
end;
|
||||
do_star(Pattern, []) ->
|
||||
wildcard_5(Pattern, []).
|
||||
|
||||
do_alt([Alt|Rest], File) ->
|
||||
case wildcard_5(Alt, File) of
|
||||
true -> true;
|
||||
false -> do_alt(Rest, File)
|
||||
end;
|
||||
do_alt([], _File) ->
|
||||
false.
|
||||
|
||||
do_list_dir(current, Mod) -> eval_list_dir(".", Mod);
|
||||
do_list_dir(Dir, Mod) -> eval_list_dir(Dir, Mod).
|
||||
|
||||
join(current, File) -> File;
|
||||
join(Base, File) -> filename:join(Base, File).
|
||||
|
||||
|
||||
%%% Compiling a wildcard.
|
||||
|
||||
do_compile_wildcard(Pattern) ->
|
||||
{compiled_wildcard,compile_wildcard_1(Pattern)}.
|
||||
|
||||
compile_wildcard_1(Pattern) ->
|
||||
[Root|Rest] = filename:split(Pattern),
|
||||
case filename:pathtype(Root) of
|
||||
relative ->
|
||||
compile_wildcard_2([Root|Rest], current);
|
||||
_ ->
|
||||
compile_wildcard_2(Rest, [Root])
|
||||
end.
|
||||
|
||||
compile_wildcard_2([Part|Rest], Root) ->
|
||||
case compile_part(Part) of
|
||||
Part ->
|
||||
compile_wildcard_2(Rest, join(Root, Part));
|
||||
Pattern ->
|
||||
compile_wildcard_3(Rest, [Pattern,Root])
|
||||
end;
|
||||
compile_wildcard_2([], Root) -> {exists,Root}.
|
||||
|
||||
compile_wildcard_3([Part|Rest], Result) ->
|
||||
compile_wildcard_3(Rest, [compile_part(Part)|Result]);
|
||||
compile_wildcard_3([], Result) ->
|
||||
lists:reverse(Result).
|
||||
|
||||
compile_part(Part) ->
|
||||
compile_part(Part, false, []).
|
||||
|
||||
compile_part_to_sep(Part) ->
|
||||
compile_part(Part, true, []).
|
||||
|
||||
compile_part([], true, _) ->
|
||||
error(missing_delimiter);
|
||||
compile_part([$,|Rest], true, Result) ->
|
||||
{ok, $,, lists:reverse(Result), Rest};
|
||||
compile_part([$}|Rest], true, Result) ->
|
||||
{ok, $}, lists:reverse(Result), Rest};
|
||||
compile_part([$?|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [question|Result]);
|
||||
compile_part([$*,$*], Upto, Result) ->
|
||||
compile_part([], Upto, [double_star|Result]);
|
||||
compile_part([$*,$*|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [star|Result]);
|
||||
compile_part([$*], Upto, Result) ->
|
||||
compile_part([], Upto, [accept|Result]);
|
||||
compile_part([$*|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [star|Result]);
|
||||
compile_part([$[|Rest], Upto, Result) ->
|
||||
case compile_charset(Rest, ordsets:new()) of
|
||||
{ok, Charset, Rest1} ->
|
||||
compile_part(Rest1, Upto, [Charset|Result]);
|
||||
error ->
|
||||
compile_part(Rest, Upto, [$[|Result])
|
||||
end;
|
||||
compile_part([${|Rest], Upto, Result) ->
|
||||
case compile_alt(Rest) of
|
||||
{ok, Alt} ->
|
||||
lists:reverse(Result, [Alt]);
|
||||
error ->
|
||||
compile_part(Rest, Upto, [${|Result])
|
||||
end;
|
||||
compile_part([X|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [X|Result]);
|
||||
compile_part([], _Upto, Result) ->
|
||||
lists:reverse(Result).
|
||||
|
||||
compile_charset([$]|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element($], Ordset));
|
||||
compile_charset([$-|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element($-, Ordset));
|
||||
compile_charset([], _Ordset) ->
|
||||
error;
|
||||
compile_charset(List, Ordset) ->
|
||||
compile_charset1(List, Ordset).
|
||||
|
||||
compile_charset1([Lower, $-, Upper|Rest], Ordset) when Lower =< Upper ->
|
||||
compile_charset1(Rest, compile_range(Lower, Upper, Ordset));
|
||||
compile_charset1([$]|Rest], Ordset) ->
|
||||
{ok, {one_of, Ordset}, Rest};
|
||||
compile_charset1([X|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element(X, Ordset));
|
||||
compile_charset1([], _Ordset) ->
|
||||
error.
|
||||
|
||||
compile_range(Lower, Current, Ordset) when Lower =< Current ->
|
||||
compile_range(Lower, Current-1, ordsets:add_element(Current, Ordset));
|
||||
compile_range(_, _, Ordset) ->
|
||||
Ordset.
|
||||
|
||||
compile_alt(Pattern) ->
|
||||
compile_alt(Pattern, []).
|
||||
|
||||
compile_alt(Pattern, Result) ->
|
||||
case compile_part_to_sep(Pattern) of
|
||||
{ok, $,, AltPattern, Rest} ->
|
||||
compile_alt(Rest, [AltPattern|Result]);
|
||||
{ok, $}, AltPattern, Rest} ->
|
||||
NewResult = [AltPattern|Result],
|
||||
RestPattern = compile_part(Rest),
|
||||
{ok, {alt, [Alt++RestPattern || Alt <- NewResult]}};
|
||||
Pattern ->
|
||||
error
|
||||
end.
|
||||
|
||||
error(Reason) ->
|
||||
erlang:error({badpattern,Reason}).
|
||||
|
||||
eval_read_file_info(File, file) ->
|
||||
file:read_file_info(File);
|
||||
eval_read_file_info(File, erl_prim_loader) ->
|
||||
case erl_prim_loader:read_file_info(File) of
|
||||
error -> {error, erl_prim_loader};
|
||||
Res-> Res
|
||||
end;
|
||||
eval_read_file_info(File, Mod) ->
|
||||
Mod:read_file_info(File).
|
||||
|
||||
eval_list_dir(Dir, file) ->
|
||||
file:list_dir(Dir);
|
||||
eval_list_dir(Dir, erl_prim_loader) ->
|
||||
case erl_prim_loader:list_dir(Dir) of
|
||||
error -> {error, erl_prim_loader};
|
||||
Res-> Res
|
||||
end;
|
||||
eval_list_dir(Dir, Mod) ->
|
||||
Mod:list_dir(Dir).
|
||||
@@ -1,35 +1,12 @@
|
||||
%% Module responsible for handling imports and conflicts.
|
||||
%% Module responsible for handling imports and conflicts
|
||||
%% in between local functions and imports.
|
||||
%% For imports dispatch, please check elixir_dispatch.
|
||||
-module(elixir_import).
|
||||
-export([import/5, recorded_locals/1, format_error/1,
|
||||
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
|
||||
build_table/1, delete_table/1, record/4]).
|
||||
-export([import/5, format_error/1,
|
||||
ensure_no_local_conflict/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
table(Module) -> ?atom_concat([i, Module]).
|
||||
|
||||
build_table(Module) ->
|
||||
ets:new(table(Module), [set, named_table, public]).
|
||||
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
|
||||
record(_Kind, _Tuple, _Receiver, nil) ->
|
||||
false;
|
||||
|
||||
record(import, Tuple, Receiver, Module) ->
|
||||
try
|
||||
ets:insert(table(Module), { Tuple, Receiver })
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
recorded_locals(Module) ->
|
||||
Table = table(Module),
|
||||
Match = { '$1', Module },
|
||||
Result = ets:match(Table, Match),
|
||||
ets:match_delete(Table, Match),
|
||||
lists:append(Result).
|
||||
%% IMPORT HELPERS
|
||||
|
||||
%% Update the scope to consider the imports for aliases
|
||||
%% based on the given options and selector.
|
||||
@@ -41,9 +18,9 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
false -> S;
|
||||
true ->
|
||||
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
|
||||
Functions = calculate(Meta, Ref, Opts,
|
||||
S#elixir_scope.functions, FunctionsFun, S),
|
||||
S#elixir_scope{functions=Functions}
|
||||
{ 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}
|
||||
end,
|
||||
|
||||
SM = case IncludeAll or (Selector == macros) of
|
||||
@@ -55,24 +32,32 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
false -> get_macros(Meta, Ref, SF)
|
||||
end
|
||||
end,
|
||||
Macros = calculate(Meta, Ref, Opts,
|
||||
SF#elixir_scope.macros, MacrosFun, SF),
|
||||
SF#elixir_scope{macros=Macros}
|
||||
{ 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.
|
||||
|
||||
%% IMPORT FUNCTION RELATED HELPERS
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
|
||||
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
All = keydelete(Key, Old),
|
||||
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, RawOnly } ->
|
||||
Only = expand_fun_arity(Meta, only, RawOnly, S),
|
||||
{ only, Only } ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
@@ -84,50 +69,37 @@ calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> AvailableFun(true);
|
||||
{ except, [] } -> AvailableFun(true);
|
||||
{ except, RawExcept } ->
|
||||
Except = expand_fun_arity(Meta, except, RawExcept, S),
|
||||
{ except, Except } ->
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun(true) -- Except;
|
||||
{Key,ToRemove} -> ToRemove -- Except
|
||||
{Key,OldImports} -> OldImports -- Except
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
%% Normalize the data before storing it
|
||||
Set = ordsets:from_list(New),
|
||||
Final = remove_internals(Set),
|
||||
Set = ordsets:from_list(New),
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] -> All;
|
||||
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
_ ->
|
||||
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
|
||||
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
[{ Key, Final }|All]
|
||||
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
{ [{ Key, Final }|keydelete(Key, Old)],
|
||||
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
|
||||
end.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
|
||||
expand_fun_arity(Meta, Kind, Value, S) ->
|
||||
{ TValue, _S } = elixir_translator:translate_each(Value, S),
|
||||
cons_to_keywords(Meta, Kind, TValue, S).
|
||||
|
||||
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
|
||||
[tuple_to_fun_arity(Meta, Kind, Left, S)];
|
||||
|
||||
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
|
||||
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
|
||||
|
||||
cons_to_keywords(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
|
||||
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
||||
{ Atom, Integer };
|
||||
|
||||
tuple_to_fun_arity(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
if_quoted(Meta, Temp, Callback) ->
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, Context } ->
|
||||
Current = case orddict:find(Context, Temp) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> []
|
||||
end,
|
||||
orddict:store(Context, Callback(Current), Temp);
|
||||
_ ->
|
||||
Temp
|
||||
end.
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
|
||||
@@ -168,83 +140,43 @@ get_optional_macros(Module) ->
|
||||
%% VALIDATION HELPERS
|
||||
|
||||
%% Check if any of the locals defined conflicts with an invoked
|
||||
%% Elixir "implemented in Erlang" macro. Checking if a local
|
||||
%% conflicts with an import is automatically done by Erlang.
|
||||
%% Elixir "implemented in Erlang" macro.
|
||||
|
||||
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Module, AllDefined, local_conflict).
|
||||
|
||||
%% Find conlicts in the given list of functions with
|
||||
%% the recorded set of imports.
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
Table = table(Module),
|
||||
Matches = [X || X <- AllDefined, ets:member(Table, X)],
|
||||
|
||||
case Matches of
|
||||
[{Name,Arity}|_] ->
|
||||
Key = ets:lookup_element(Table, {Name, Arity }, 2),
|
||||
Tuple = { import_conflict, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
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_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
|
||||
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, non_overridable_macros()),
|
||||
end, special_form()),
|
||||
|
||||
case Values /= [] of
|
||||
true ->
|
||||
Tuple = { Reason, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
|
||||
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
|
||||
end;
|
||||
|
||||
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% Find conlicts in the given list of functions with the set of imports.
|
||||
%% Used internally to ensure a newly imported fun or macro does not
|
||||
%% conflict with an already imported set.
|
||||
|
||||
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
|
||||
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
|
||||
case Filtered of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { already_imported, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ensure_no_conflicts(Meta, File, Functions, T)
|
||||
end;
|
||||
|
||||
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
|
||||
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({already_imported,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("function ~s/~B already imported from ~s", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~s.~s/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("imported ~s.~s/~B conflicts with local function",
|
||||
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 ~s/~B because it conflicts with Elixir special forms", [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}}) ->
|
||||
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~s", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
|
||||
%% LIST HELPERS
|
||||
|
||||
@@ -262,8 +194,6 @@ intersection([H|T], All) ->
|
||||
|
||||
intersection([], _All) -> [].
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
@@ -283,9 +213,9 @@ remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
|
||||
%% Macros implemented in Erlang that are not overridable.
|
||||
%% Macros implemented in Erlang that are not importable.
|
||||
|
||||
non_overridable_macros() ->
|
||||
special_form() ->
|
||||
[
|
||||
{'^',1},
|
||||
{'=',2},
|
||||
@@ -309,6 +239,7 @@ non_overridable_macros() ->
|
||||
{'__CALLER__',0},
|
||||
{'__MODULE__',0},
|
||||
{'__FILE__',0},
|
||||
{'__DIR__',0},
|
||||
{'__aliases__','*'},
|
||||
{'quote',1},
|
||||
{'quote',2},
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
@@ -19,8 +18,8 @@ extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
|
||||
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, _File, _Interpol, [Last], _Buffer, Search, _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("unexpected end of string: ~ts", [[hd(Search)]]), [Last] } };
|
||||
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
@@ -29,33 +29,23 @@ translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
translate({ '[]', _Meta, [] }, S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
|
||||
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{'|',_,[Left,Right]} ->
|
||||
Exprs = [Left|RArgs],
|
||||
{ Tail, ST } = translate_each(Right, S),
|
||||
ListS = umergec(S, ST);
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
Exprs = [RTail|RArgs],
|
||||
Tail = { nil, 0 },
|
||||
ST = S,
|
||||
ListS = S
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
|
||||
end,
|
||||
|
||||
{ FExprs, FS } = case S#elixir_scope.context of
|
||||
assign ->
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } =
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end,
|
||||
|
||||
{ FExprs, umergev(ST, FS) };
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
@@ -75,7 +65,7 @@ translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -106,37 +96,28 @@ build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_list(H) ->
|
||||
case is_default_or_utf(Types) of
|
||||
true ->
|
||||
{ NewAcc, NewS } = lists:foldl(fun(L, { LA, LS }) ->
|
||||
{ FL, FS } = Fun(L, LS),
|
||||
{ [{ bin_element, ?line(Meta), FL, Size, Types }|LA], FS }
|
||||
end, { Acc, S }, H),
|
||||
build_bitstr_each(Fun, T, Meta, NewS, NewAcc);
|
||||
false ->
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
|
||||
end;
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_bitstring(H) ->
|
||||
case is_default_or_utf(Types) of
|
||||
true ->
|
||||
Line = ?line(Meta),
|
||||
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
|
||||
NewAcc = lists:foldl(fun({ bin_element, _, Expr, _, _ }, FinalAcc) ->
|
||||
[{ bin_element, Line, Expr, Size, Types }|FinalAcc]
|
||||
end, Acc, Elements),
|
||||
build_bitstr_each(Fun, T, Meta, S, NewAcc);
|
||||
false ->
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
|
||||
end;
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types).
|
||||
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc]).
|
||||
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);
|
||||
{ cons, _, _, _ } = Cons ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
|
||||
{ nil, _ } ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, Acc);
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end.
|
||||
|
||||
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]);
|
||||
|
||||
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;
|
||||
@@ -219,7 +200,7 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ ?line(Meta), S })) of
|
||||
H ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
|
||||
end.
|
||||
|
||||
@@ -5,13 +5,10 @@
|
||||
-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,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
%% Operators
|
||||
|
||||
@@ -23,7 +20,7 @@ translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
Op == '<-' orelse Op == '--' ->
|
||||
assert_no_assign_or_guard_scope(Meta, Op, S),
|
||||
assert_no_match_or_guard_scope(Meta, Op, S),
|
||||
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
@@ -53,34 +50,36 @@ translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
||||
%% Functions
|
||||
|
||||
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
elixir_translator:translate_fn(Meta, Pairs, S);
|
||||
|
||||
translate({ function, Meta, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
|
||||
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
|
||||
translate({ function, Meta, [M, F, A] }, S);
|
||||
|
||||
translate({ function, Meta, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
|
||||
translate({ function, Meta, [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),
|
||||
|
||||
translate({ function, Meta, [_] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
|
||||
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,
|
||||
|
||||
translate({ function, Meta, [_, _] = Args }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
case translate_args(Args, S) of
|
||||
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
|
||||
case elixir_dispatch:import_function(Meta, Name, Arity, SA) of
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
Else -> Else
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
|
||||
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_assign_or_guard_scope(Meta, 'function', S),
|
||||
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 };
|
||||
|
||||
@@ -112,7 +111,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
}, S);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot dynamically set attribute @~s inside a function", [Name])
|
||||
"cannot dynamically set attribute @~ts inside a function", [Name])
|
||||
end;
|
||||
_ when is_atom(Args) or (Args == []) ->
|
||||
case S#elixir_scope.function of
|
||||
@@ -124,17 +123,17 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_tree_helpers:abstract_syntax(Contents), S }
|
||||
{ elixir_tree_helpers:elixir_to_erl(Contents), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~s, got: ~p", [Name, length(Args)])
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'case', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
|
||||
@@ -148,12 +147,11 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
S = RawS#elixir_scope{noname=true},
|
||||
assert_no_assign_or_guard_scope(Meta, 'try', S),
|
||||
translate({'try', Meta, [Clauses]}, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S),
|
||||
{ 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)),
|
||||
@@ -164,12 +162,13 @@ translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
|
||||
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, umergec(RawS, SE) };
|
||||
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) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'receive', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
@@ -196,54 +195,44 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
{ atom, _, Module } ->
|
||||
FullModule = module_ref(Ref, Module, S#elixir_scope.module),
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting(S#elixir_scope.module, FullModule) of
|
||||
false -> S;
|
||||
Alias -> element(2, translate_each({ alias, Meta, [FullModule, [{ as, Alias }]] }, S))
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
|
||||
false -> S
|
||||
end,
|
||||
|
||||
{
|
||||
{ atom, Meta, FullModule },
|
||||
RS#elixir_scope{scheduled=[FullModule|S#elixir_scope.scheduled]}
|
||||
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
|
||||
};
|
||||
_ ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
{ elixir_module:translate(Meta, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
|
||||
MS = FS#elixir_scope{local=nil},
|
||||
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
|
||||
|
||||
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
|
||||
translate({Kind, Meta, [Call, nil]}, S);
|
||||
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
|
||||
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
|
||||
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
|
||||
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
||||
|
||||
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
|
||||
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
|
||||
error -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
|
||||
Tuple -> Tuple
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Meta, Name, Args, S),
|
||||
|
||||
TName = elixir_tree_helpers:abstract_syntax(Name),
|
||||
TArgs = elixir_tree_helpers:abstract_syntax(Args),
|
||||
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
|
||||
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
|
||||
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, S), S };
|
||||
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TName, NS } = translate_each(Name, S),
|
||||
{ TArgs, AS } = translate_each(Args, NS),
|
||||
{ TGuards, GS } = translate_each(Guards, AS),
|
||||
{ TExpr, ES } = translate_each(Expr, GS),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, ES), ES };
|
||||
{ TName, SN } = translate_each(Name, S),
|
||||
{ TArgs, SA } = translate_each(Args, SN),
|
||||
{ TGuards, SG } = translate_each(Guards, SA),
|
||||
{ TExpr, SE } = translate_each(Expr, SG),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
|
||||
|
||||
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
||||
|
||||
@@ -277,21 +266,23 @@ translate_in(Meta, Left, Right, S) ->
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
Expr = case TRight of
|
||||
{ InGuard, TExpr } = case TRight of
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
||||
end, { op, Line, '==', Var, H }, T);
|
||||
end, { op, Line, '==', Var, H }, T),
|
||||
{ Cache, Expr };
|
||||
{ string, _, [H|T] } ->
|
||||
lists:foldl(fun(X, Acc) ->
|
||||
Expr = lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T);
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T),
|
||||
{ Cache, Expr };
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
|
||||
Expr = case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
|
||||
increasing_compare(Line, Var, Start, End);
|
||||
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
|
||||
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
|
||||
decreasing_compare(Line, Var, Start, End);
|
||||
_ ->
|
||||
{ op, Line, 'orelse',
|
||||
@@ -301,14 +292,20 @@ translate_in(Meta, Left, Right, S) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
decreasing_compare(Line, Var, Start, End) } }
|
||||
end;
|
||||
end,
|
||||
{ Cache, Expr };
|
||||
_ ->
|
||||
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")
|
||||
case Cache of
|
||||
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")
|
||||
end
|
||||
end,
|
||||
|
||||
case Cache of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, Expr ] }, SV };
|
||||
false -> { Var, Expr, SV }
|
||||
case InGuard of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { Var, TExpr, SV }
|
||||
end.
|
||||
|
||||
increasing_compare(Line, Var, Start, End) ->
|
||||
@@ -327,14 +324,26 @@ rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
module_ref(Raw, Module, Nesting) when is_atom(Raw); Nesting == nil ->
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
|
||||
module_ref({ '__aliases__', _, ['Elixir'|_] }, Module, _Nesting) ->
|
||||
Module;
|
||||
%% defmodule Hello
|
||||
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, H]);
|
||||
|
||||
module_ref(_Raw, Module, Nesting) ->
|
||||
elixir_aliases:concat([Nesting, Module]).
|
||||
%% defmodule Hello.World
|
||||
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
Aliases when is_list(Aliases) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module])
|
||||
end;
|
||||
|
||||
%% defmodule Elixir.Hello.World
|
||||
expand_module(_Raw, Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
@@ -349,10 +358,3 @@ spec_to_macro(callback) -> defcallback.
|
||||
% Pack a list of expressions from a block.
|
||||
pack({ 'block', _, Exprs }) -> Exprs;
|
||||
pack(Expr) -> [Expr].
|
||||
|
||||
assert_no_aliases_name(Meta, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
|
||||
syntax_error(Meta, File, Message, [atom_to_binary(Atom, utf8)]);
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -1,7 +1,11 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4, format_error/1]).
|
||||
-export([translate/4, compile/5, data_table/1, docs_table/1,
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
@@ -14,7 +18,7 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
false -> Line = 1
|
||||
end,
|
||||
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
|
||||
{ Value, FinalBinding }.
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
@@ -31,7 +35,7 @@ data_table(Module) ->
|
||||
Module.
|
||||
|
||||
docs_table(Module) ->
|
||||
?atom_concat([o, Module]).
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
@@ -41,7 +45,7 @@ docs_table(Module) ->
|
||||
|
||||
translate(Meta, Ref, Block, S) ->
|
||||
Line = ?line(Meta),
|
||||
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
|
||||
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
@@ -49,8 +53,13 @@ translate(Meta, Ref, Block, S) ->
|
||||
|
||||
%% The compilation hook.
|
||||
|
||||
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
|
||||
C = elixir_compiler:get_opts(),
|
||||
S = case lists:member(Module, FileModules) of
|
||||
true -> RawS;
|
||||
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
|
||||
end,
|
||||
|
||||
File = S#elixir_scope.file,
|
||||
FileList = binary_to_list(File),
|
||||
|
||||
@@ -59,14 +68,23 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[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),
|
||||
elixir_tracker:ensure_all_imports_used(Line, File, Module),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
@@ -76,10 +94,10 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
Binary = load_form(Line, Final, S),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
ets:delete(data_table(Module)),
|
||||
elixir_tracker:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
elixir_def:delete_table(Module),
|
||||
elixir_import:delete_table(Module)
|
||||
ets:delete(data_table(Module))
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
@@ -112,20 +130,13 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
|
||||
%% Keep docs in another table since we don't want to pull out
|
||||
%% all the binaries every time a new documentation is stored.
|
||||
DocsTable = docs_table(Module),
|
||||
ets:new(DocsTable, [ordered_set, named_table, public]),
|
||||
|
||||
%% We keep a separated table for function definitions
|
||||
%% and another one for imports. We keep them in different
|
||||
%% tables for organization and speed purpose (since the
|
||||
%% imports table is frequently written to).
|
||||
elixir_def:build_table(Module),
|
||||
elixir_import:build_table(Module).
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_tracker:setup(Module).
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
@@ -133,32 +144,32 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
S = scope_for_eval(Module, RawS),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
eval_callbacks(Line, Module, before_compile, [Module], NewS),
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
eval_callbacks(Line, Module, before_compile, [Env], NewS),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Value.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
|
||||
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions, C) ->
|
||||
Functions = case elixir_compiler:get_opt(internal, C) of
|
||||
true -> RawFunctions;
|
||||
false -> record_rewrite_functions(RawFunctions)
|
||||
false -> record_rewrite_functions(Module, RawFunctions)
|
||||
end,
|
||||
|
||||
{ FinalExport, FinalFunctions } =
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
|
||||
|
||||
Recorded = elixir_import:recorded_locals(Module),
|
||||
elixir_def_local:check_unused_local_macros(File, Recorded, Defmacrop),
|
||||
|
||||
{ FinalExport ++ Private, [
|
||||
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
|
||||
{ FinalExport ++ PrivateTuple, [
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
] }.
|
||||
|
||||
record_rewrite_functions(Functions) ->
|
||||
record_rewrite_functions(Module, Functions) ->
|
||||
lists:map(fun
|
||||
({ function, Line, Name, Arity, Clauses }) ->
|
||||
Rewriten = [begin
|
||||
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Clause),
|
||||
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Module, Clause),
|
||||
C
|
||||
end || Clause <- Clauses],
|
||||
{ function, Line, Name, Arity, Rewriten };
|
||||
@@ -189,8 +200,8 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
|
||||
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
_ ->
|
||||
@@ -238,7 +249,8 @@ spec_for_macro(Else) -> Else.
|
||||
load_form(Line, Forms, S) ->
|
||||
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
eval_callbacks(Line, Module, after_compile, [Module, Binary], EvalS),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -286,11 +298,11 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
end.
|
||||
|
||||
functions_clause(Def) ->
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
|
||||
|
||||
macros_clause(Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
@@ -302,14 +314,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:abstract_syntax(Docs)] };
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers: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:abstract_syntax({ Line, Docs })] };
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
|
||||
|
||||
moduledoc_clause(_Line, _Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
|
||||
@@ -320,42 +332,42 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
S = RawS#elixir_scope{check_clauses=false},
|
||||
eval_callbacks(Line, Module, Name, Args, S) ->
|
||||
Binding = binding_for_eval(Module, []),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Requires = S#elixir_scope.requires,
|
||||
Meta = [{line,Line}],
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
Expr = { { '.', Meta, [M,F] }, Meta, Args },
|
||||
Scope = case ordsets:is_element(M, Requires) of
|
||||
true -> S;
|
||||
false -> S#elixir_scope{requires=ordsets:add_element(M, Requires)}
|
||||
end,
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
apply(M, F, Args),
|
||||
{ { atom, 0, nil }, S }
|
||||
end),
|
||||
|
||||
{ Tree, _ } = elixir_translator:translate_each(Expr, Scope),
|
||||
|
||||
try
|
||||
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()])
|
||||
case Tree of
|
||||
{ atom, _, Atom } ->
|
||||
Atom;
|
||||
_ ->
|
||||
try
|
||||
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()])
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
|
||||
% ERROR HANDLING
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~s/~B is internal and should not be overridden", [Name, Arity]);
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
format_error({ invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
|
||||
format_error({ module_defined, Module }) ->
|
||||
io_lib:format("redefining module ~s", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({ module_in_definition, Module }) ->
|
||||
io_lib:format("cannot define module ~s because it is currently being defined",
|
||||
io_lib:format("cannot define module ~ts because it is currently being defined",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
|
||||
@@ -2,7 +2,7 @@ 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
|
||||
add_op mult_op unary_op two_op pipeline_op bin_concat_op
|
||||
add_op mult_op unary_op two_op regex_op right_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
open_paren close_paren empty_paren
|
||||
@@ -10,8 +10,9 @@ Nonterminals
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
|
||||
stab stab_op stab_expr stab_maybe_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_op 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
|
||||
@@ -30,7 +31,7 @@ Terminals
|
||||
'true' 'false' 'nil'
|
||||
'=' '+' '-' '*' '/' '++' '--' '**' '//'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '/>' '=~'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '=~'
|
||||
'&&' '||' '!' '...' '..'
|
||||
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
|
||||
.
|
||||
@@ -53,13 +54,14 @@ Left 140 or_op.
|
||||
Left 150 and_op.
|
||||
Left 160 comp_expr_op.
|
||||
Left 170 in_op.
|
||||
Left 180 range_op.
|
||||
Left 190 three_op.
|
||||
Left 200 add_op.
|
||||
Left 210 mult_op.
|
||||
Right 220 bin_concat_op.
|
||||
Right 230 two_op.
|
||||
Right 290 pipeline_op.
|
||||
Right 180 regex_op.
|
||||
Right 190 right_op.
|
||||
Left 200 range_op.
|
||||
Left 210 three_op.
|
||||
Left 220 add_op.
|
||||
Left 230 mult_op.
|
||||
Right 240 bin_concat_op.
|
||||
Right 250 two_op.
|
||||
Nonassoc 300 unary_op.
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
@@ -102,7 +104,8 @@ op_expr -> match_op expr : { '$1', '$2' }.
|
||||
op_expr -> add_op expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op expr : { '$1', '$2' }.
|
||||
op_expr -> two_op expr : { '$1', '$2' }.
|
||||
op_expr -> pipeline_op expr : { '$1', '$2' }.
|
||||
op_expr -> regex_op expr : { '$1', '$2' }.
|
||||
op_expr -> right_op expr : { '$1', '$2' }.
|
||||
op_expr -> andand_op expr : { '$1', '$2' }.
|
||||
op_expr -> three_op expr : { '$1', '$2' }.
|
||||
op_expr -> oror_op expr : { '$1', '$2' }.
|
||||
@@ -123,7 +126,8 @@ matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipeline_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> regex_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
|
||||
@@ -142,16 +146,16 @@ matched_op_expr -> comp_expr_op 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 do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
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 do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
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 : build_identifier('$1', '$2').
|
||||
call_expr -> dot_op_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
call_expr -> dot_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
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).
|
||||
@@ -192,7 +196,7 @@ base_expr -> sigil : build_sigil('$1').
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
|
||||
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
|
||||
do_block -> do_eol stab eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$3']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
@@ -209,15 +213,18 @@ block_eol -> block_identifier eol : '$1'.
|
||||
stab -> stab_expr : ['$1'].
|
||||
stab -> stab eol stab_expr : ['$3'|'$1'].
|
||||
|
||||
stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
|
||||
block_item -> block_eol stab eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
|
||||
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
|
||||
block_item -> block_eol : { ?exprs('$1'), nil }.
|
||||
|
||||
block_list -> block_item : ['$1'].
|
||||
@@ -268,12 +275,11 @@ two_op -> '--' eol : '$1'.
|
||||
two_op -> '**' : '$1'.
|
||||
two_op -> '**' eol : '$1'.
|
||||
|
||||
pipeline_op -> '=~' : '$1'.
|
||||
pipeline_op -> '=~' eol : '$1'.
|
||||
pipeline_op -> '|>' : '$1'.
|
||||
pipeline_op -> '|>' eol : '$1'.
|
||||
pipeline_op -> '/>' : '$1'.
|
||||
pipeline_op -> '/>' eol : '$1'.
|
||||
regex_op -> '=~' : '$1'.
|
||||
regex_op -> '=~' eol : '$1'.
|
||||
|
||||
right_op -> '|>' : '$1'.
|
||||
right_op -> '|>' eol : '$1'.
|
||||
|
||||
three_op -> '&&&' : '$1'.
|
||||
three_op -> '&&&' eol : '$1'.
|
||||
@@ -387,6 +393,11 @@ parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }.
|
||||
matched_comma_expr -> matched_expr : ['$1'].
|
||||
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_no_parens_strict -> call_args_no_parens : '$1'.
|
||||
call_args_no_parens_strict -> open_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 -> 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']).
|
||||
@@ -490,6 +501,8 @@ build_tuple(Marker, Args) ->
|
||||
|
||||
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(Exprs) -> { '__block__', [], Exprs }.
|
||||
|
||||
@@ -542,7 +555,7 @@ build_fn(Op, Stab) ->
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir-Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
@@ -553,10 +566,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) -> binary_to_list(H);
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> unicode:characters_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, [erlang, binary_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
|
||||
{ { '.', Meta, [unicode, characters_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
|
||||
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) ->
|
||||
@@ -581,3 +594,25 @@ build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
%% it assumes that a block is being spliced, wrapping
|
||||
%% the splicing in a __block__. But in the stab cause,
|
||||
%% we can have (unquote_splicing(1,2,3)) -> :ok, in such
|
||||
%% case, we don't actually want the block, since it is
|
||||
%% an arg style call. unwrap_splice unwraps the splice
|
||||
%% from such blocks.
|
||||
unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
|
||||
Splice;
|
||||
|
||||
unwrap_splice(Other) -> Other.
|
||||
|
||||
%% Errors
|
||||
|
||||
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 "
|
||||
"function call, do not insert spaces in between the function name and the "
|
||||
"opening parentheses. Syntax error before: ", ")").
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
%% Responsible handling Elixir partials with &1, &2 and so on.
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= assign ->
|
||||
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= match ->
|
||||
case convert(Args, S, Opt) of
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
|
||||
+144
-111
@@ -1,5 +1,6 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([quote/3, linify/2, join_quoted/5]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -21,69 +22,109 @@ do_linify(Line, List) when is_list(List) ->
|
||||
|
||||
do_linify(_, Else) -> Else.
|
||||
|
||||
%% Join quoted checks for quote arguments at runtime
|
||||
%% in order to properly insert them into the tree
|
||||
join_quoted(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
|
||||
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir-" ++ _ ->
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
_ ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
join_quoted(Meta, Left, Right, Args, _File) when is_atom(Right) ->
|
||||
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
join_quoted(Meta, _Left, _Right, _Args, File) ->
|
||||
unquote(File, Meta, _Left, _Right, _Args) ->
|
||||
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
|
||||
|
||||
%% Translation
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
quote(Expr, #elixir_quote{
|
||||
line=keep,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=Unquote,
|
||||
mark=false
|
||||
}, nil).
|
||||
|
||||
quote({ 'unquote_splicing', Meta, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
erl_escape(Expr, Unquote, S) ->
|
||||
{ QExpr, TQ } = escape(Expr, Unquote),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
quote(Else, Q, S) ->
|
||||
do_quote(Else, Q, S).
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
quote(Expr, Q, S) ->
|
||||
do_quote(Expr, Q, S).
|
||||
|
||||
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
|
||||
do_quote(Expr, Q#elixir_quote{expand_aliases=false}, S);
|
||||
erl_quote(Expr, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Q, S),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|_] = Aliases }, #elixir_quote{expand_aliases=true} = Q, S) when is_atom(H) and H /= 'Elixir' ->
|
||||
Line = ?line(Meta),
|
||||
{ TAliases, SA } = do_quote(['Elixir'|Aliases], Q, S),
|
||||
%% Actual quoting and helpers
|
||||
|
||||
{ { tuple, Line, [
|
||||
{ atom, Line, '__aliases__' },
|
||||
meta(Meta, Q),
|
||||
TAliases
|
||||
] }, SA };
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
|
||||
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
|
||||
{ TTuple, TQ#elixir_quote{unquote=true} };
|
||||
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
{ Expr, Q#elixir_quote{unquoted=true} };
|
||||
|
||||
%% 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_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_tuple({ Target, NewMeta, Args }, Q, S);
|
||||
|
||||
%% Aliases
|
||||
|
||||
do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S) ->
|
||||
{ TExpr, TQ } = do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S),
|
||||
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
|
||||
|
||||
%% Vars
|
||||
|
||||
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, S) when is_atom(Left) ->
|
||||
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, S);
|
||||
|
||||
%% Unquote
|
||||
|
||||
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Meta, [Expr] }, Args, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S);
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Meta, [Expr] }, nil, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, S);
|
||||
|
||||
do_quote({ Left, Meta, nil }, Q, S) when is_atom(Left) ->
|
||||
Line = ?line(Meta),
|
||||
Tuple = { tuple, Line, [
|
||||
{ atom, Line, Left },
|
||||
meta(Meta, Q),
|
||||
{ atom, Line, Q#elixir_quote.var_context }
|
||||
] },
|
||||
{ Tuple, S };
|
||||
%% Imports
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true} = Q, S) when is_atom(Name) ->
|
||||
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(function, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
@@ -92,93 +133,85 @@ do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true}
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, S) of
|
||||
false -> do_quote_tuple(Tuple, Q, S);
|
||||
Receiver -> do_quote_tuple({ Name, [{import,Receiver}|Meta], ArgsOrAtom }, Q, S)
|
||||
Receiver ->
|
||||
ImportMeta = lists:keystore(import, 1,
|
||||
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
{ import, Receiver }),
|
||||
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
|
||||
end;
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, Q, S) ->
|
||||
do_quote_tuple(Tuple, Q, S);
|
||||
|
||||
% Handle two item tuples but still allow them to be spliced.
|
||||
do_quote({ Left, Right }, Q, S) when
|
||||
not is_tuple(Left) orelse (element(1, Left) /= unquote_splicing),
|
||||
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
|
||||
{ TLeft, LS } = do_quote(Left, Q, S),
|
||||
{ TRight, RS } = do_quote(Right, Q, LS),
|
||||
{ { tuple, line(Q), [TLeft, TRight] }, RS };
|
||||
%% Literals
|
||||
|
||||
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
|
||||
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
|
||||
is_tuple(Right) andalso (element(1, Right) == unquote_splicing) ->
|
||||
do_quote({ '{}', [], [Left, Right] }, Q, S);
|
||||
|
||||
do_quote({ Left, Right }, Q, S) ->
|
||||
do_quote({ '{}', [{line,line(Q)}], [Left, Right] }, Q, S);
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
|
||||
{ { '{}', [], TT }, TQ };
|
||||
|
||||
do_quote(List, Q, S) when is_list(List) ->
|
||||
splice(List, Q, [], [], S);
|
||||
do_splice(lists:reverse(List), Q, S);
|
||||
|
||||
do_quote(Number, Q, S) when is_integer(Number) ->
|
||||
{ { integer, line(Q), Number }, S };
|
||||
do_quote(Other, Q, _) ->
|
||||
{ Other, Q }.
|
||||
|
||||
do_quote(Number, Q, S) when is_float(Number) ->
|
||||
{ { float, line(Q), Number }, S };
|
||||
%% Quote helpers
|
||||
|
||||
do_quote(Atom, Q, S) when is_atom(Atom) ->
|
||||
{ { atom, line(Q), Atom }, S };
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
|
||||
|
||||
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
case (lists:keyfind(import_fa, 1, 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 } })
|
||||
end,
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
{ TLeft, LS } = do_quote(Left, Q, S),
|
||||
{ TRight, RS } = do_quote(Right, Q, LS),
|
||||
|
||||
%% We need to remove line numbers from quoted exprs
|
||||
%% otherwise the line number quotes in the macro will
|
||||
%% get mixed with the unquoted contents
|
||||
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
|
||||
{ Tuple, RS }.
|
||||
|
||||
% Loop through the list finding each unquote_splicing entry.
|
||||
|
||||
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
|
||||
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
|
||||
{ TArgs, TS } = elixir_translator:translate_each(Args, NewS),
|
||||
splice(T, Q, [], [TArgs|NewAcc], TS);
|
||||
|
||||
splice([{ '|', Meta, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
|
||||
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
|
||||
{ TRight, SR } = do_quote(Right, Q, SL),
|
||||
{ { op, ?line(Meta), '++', TLeft, TRight }, SR };
|
||||
|
||||
splice([H|T], Q, Buffer, Acc, S) ->
|
||||
splice(T, Q, [H|Buffer], Acc, S);
|
||||
|
||||
splice([], Q, Buffer, Acc, S) ->
|
||||
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
|
||||
case NewAcc of
|
||||
[] ->
|
||||
{ { nil, line(Q) }, NewS };
|
||||
[List] ->
|
||||
{ List, NewS };
|
||||
_ ->
|
||||
List = elixir_tree_helpers:build_simple_reverse_list(line(Q), NewAcc),
|
||||
{ ?wrap_call(line(Q), lists, append, [List]), NewS }
|
||||
end.
|
||||
|
||||
from_buffer_to_acc([], _Q, Acc, S) ->
|
||||
{ Acc, S };
|
||||
|
||||
from_buffer_to_acc(Buffer, Q, Acc, S) ->
|
||||
{ New, NewS } = elixir_tree_helpers:build_reverse_list(
|
||||
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, line(Q), S),
|
||||
{ [New|Acc], NewS }.
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
|
||||
|
||||
meta(Meta, #elixir_quote{line=keep}) ->
|
||||
elixir_tree_helpers:abstract_syntax(Meta);
|
||||
Meta;
|
||||
meta(Meta, #elixir_quote{line=nil}) ->
|
||||
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta));
|
||||
meta(Meta, #elixir_quote{line=Line} = Q) ->
|
||||
{ cons,
|
||||
line(Q),
|
||||
{ tuple, line(Q), [{ atom, line(Q), line }, Line] },
|
||||
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta))
|
||||
}.
|
||||
lists:keydelete(line, 1, Meta);
|
||||
meta(Meta, #elixir_quote{line=Line}) ->
|
||||
lists:keystore(line, 1, Meta, { line, Line }).
|
||||
|
||||
line(#elixir_quote{line={ integer, _, Line }}) -> Line;
|
||||
line(_) -> 0.
|
||||
%% 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(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
|
||||
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
|
||||
|
||||
do_splice([H|T], Q, S, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, S),
|
||||
do_splice(T, TQ, S, [TH|Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _S, Buffer, Acc) ->
|
||||
{ do_splice_join(Buffer, Acc), Q }.
|
||||
|
||||
do_splice_join(Left, []) -> Left;
|
||||
do_splice_join([], Right) -> Right;
|
||||
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
%% Convenience functions used to manipulate scope
|
||||
%% and its variables.
|
||||
%% Convenience functions used to manipulate scope and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
build_erl_var/3, build_ex_var/3,
|
||||
build_erl_var/4, build_ex_var/4,
|
||||
serialize/1, deserialize/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1, filename/1,
|
||||
to_erl_env/1, to_ex_env/1,
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
Line = ?line(Meta),
|
||||
@@ -22,7 +18,7 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
{ { var, Line, Name }, S };
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
@@ -30,8 +26,8 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
Else -> build_erl_var(Line, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
|
||||
Else -> build_erl_var(Line, Name, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
|
||||
true -> { { var, Line, Name }, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
@@ -58,9 +54,6 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
|
||||
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
|
||||
|
||||
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
|
||||
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
|
||||
|
||||
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
|
||||
New = orddict:update_counter(Key, 1, Counter),
|
||||
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
|
||||
@@ -82,26 +75,28 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env(Scope) ->
|
||||
elixir_tree_helpers:abstract_syntax(to_ex_env(Scope)).
|
||||
|
||||
to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env', is_integer(Line) ->
|
||||
setelement(4, Tuple, Line);
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
|
||||
|
||||
filename(#elixir_scope{file=File}) -> File;
|
||||
filename(Other) -> element(3, Other).
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
serialize(S) ->
|
||||
elixir_tree_helpers:abstract_syntax(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases, S#elixir_scope.scheduled }
|
||||
elixir_tree_helpers: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,
|
||||
S#elixir_scope.context_modules }
|
||||
).
|
||||
|
||||
serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
@@ -119,15 +114,18 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
|
||||
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
|
||||
|
||||
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, Scheduled }, Vars) ->
|
||||
deserialize_with_vars({ File, Functions, Requires, Macros,
|
||||
Aliases, MacroFunctions, MacroMacros, MacroAliases, FileModules }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
check_clauses=CheckClauses,
|
||||
requires=Requires,
|
||||
macros=Macros,
|
||||
aliases=Aliases,
|
||||
scheduled=Scheduled,
|
||||
macro_functions=MacroFunctions,
|
||||
macro_macros=MacroMacros,
|
||||
macro_aliases=MacroAliases,
|
||||
context_modules=FileModules,
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
@@ -141,26 +139,30 @@ umergev(S1, S2) ->
|
||||
C1 = S1#elixir_scope.clause_vars,
|
||||
C2 = S2#elixir_scope.clause_vars,
|
||||
S2#elixir_scope{
|
||||
vars=orddict:merge(fun var_merger/3, V1, V2),
|
||||
vars=merge_vars(V1, V2),
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the first
|
||||
% with the counter values from the second one.
|
||||
% Receives two scopes and return the later scope
|
||||
% keeping the variables from the first (counters,
|
||||
% imports and everything else are passed forward).
|
||||
|
||||
umergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
|
||||
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
|
||||
S2#elixir_scope{
|
||||
vars=S1#elixir_scope.vars,
|
||||
temp_vars=S1#elixir_scope.temp_vars,
|
||||
clause_vars=S1#elixir_scope.clause_vars
|
||||
}.
|
||||
|
||||
% Merge variables trying to find the most recently created.
|
||||
|
||||
merge_vars(V, V) -> V;
|
||||
merge_vars(V1, V2) ->
|
||||
orddict:merge(fun var_merger/3, V1, V2).
|
||||
|
||||
merge_clause_vars(nil, _C2) -> nil;
|
||||
merge_clause_vars(_C1, nil) -> nil;
|
||||
merge_clause_vars(C, C) -> C;
|
||||
merge_clause_vars(C1, C2) ->
|
||||
orddict:merge(fun var_merger/3, C1, C2).
|
||||
|
||||
|
||||
+138
-127
@@ -107,7 +107,7 @@ tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
|
||||
|
||||
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
|
||||
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { EndLine, Message, [] } };
|
||||
|
||||
% Base integers
|
||||
@@ -141,14 +141,14 @@ tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote
|
||||
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
|
||||
end;
|
||||
|
||||
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
Error ->
|
||||
Sigil = [$%,S,H],
|
||||
interpolation_error(Error, " (for sigil ~s starting at line ~B)", [Sigil, Line])
|
||||
interpolation_error(Error, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
end;
|
||||
|
||||
% Char tokens
|
||||
@@ -207,10 +207,18 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
% Dot identifier/operators
|
||||
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
|
||||
Token = check_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% ## Containers
|
||||
|
||||
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
case [T|Tail] of
|
||||
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
[$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
@@ -239,7 +247,7 @@ 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 = tokenize_call_identifier(identifier, Line, Atom, Rest),
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
|
||||
@@ -262,10 +270,6 @@ tokenize([$'|T], Line, Scope, Tokens) ->
|
||||
|
||||
% Atoms
|
||||
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|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 } ->
|
||||
@@ -278,6 +282,10 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
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]);
|
||||
|
||||
% Atom identifiers/operators
|
||||
|
||||
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
|
||||
@@ -339,18 +347,12 @@ tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
|
||||
% ## Containers + punctuation tokens
|
||||
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
|
||||
Token = { list_to_atom([T,T]), Line },
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end;
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
T == ${; T == $}; T == $[; T == $]; T == $); T == $, ->
|
||||
Token = { list_to_atom([T]), Line },
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end;
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
% ## Comparison two token operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
|
||||
@@ -380,10 +382,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
% Aliases
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
{ Rest, Alias } = tokenize_identifier(String, [], false),
|
||||
{ Rest, Alias } = tokenize_identifier(String, []),
|
||||
Atom = unsafe_to_atom(Alias, Scope),
|
||||
case Rest of
|
||||
[$:|T] when hd(T) /= $: ->
|
||||
[$:|T] when ?is_space(hd(T)) ->
|
||||
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
|
||||
@@ -392,14 +394,16 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
% Identifier
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
|
||||
case handle_keyword(Line, Kind, Identifier, Tokens) of
|
||||
false ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
[Check|T] ->
|
||||
case handle_terminator(Check, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Check|T])
|
||||
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
|
||||
end;
|
||||
|
||||
@@ -407,17 +411,16 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
|
||||
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
|
||||
Sign == $+ orelse Sign == $-,
|
||||
Space == $\s orelse Space == $\t,
|
||||
NotMarker /= $\s, NotMarker /= $\t, NotMarker /= $\r,
|
||||
NotMarker /= $\n, NotMarker /= $:, NotMarker /= $(,
|
||||
NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
% Spaces
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when T == $\s; T == $\r; T == $\t ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
@@ -444,13 +447,13 @@ handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
|
||||
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, Parts, [$:|Rest] } when hd(Rest) /= $: ->
|
||||
{ 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]);
|
||||
_ ->
|
||||
{ error, { Line, "invalid interpolation in key", [$"|T] } }
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
|
||||
end;
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H),Line,unescape_tokens(Parts) },
|
||||
@@ -459,15 +462,13 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
end.
|
||||
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
|
||||
verify_kw_and_space(Line, Op, Rest, Scope),
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
|
||||
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
|
||||
verify_kw_and_space(Line, Op, Rest, Scope),
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
|
||||
@@ -477,7 +478,7 @@ handle_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
|
||||
|
||||
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = tokenize_call_identifier(identifier, Line, Op, Rest),
|
||||
Token = check_call_identifier(identifier, Line, Op, Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]).
|
||||
|
||||
%% Helpers
|
||||
@@ -501,7 +502,7 @@ collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
|
||||
collect_modifiers(Rest, Buffer) ->
|
||||
{ Rest, lists:reverse(Buffer) }.
|
||||
|
||||
% Extract heredocs
|
||||
%% Heredocs
|
||||
|
||||
extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
|
||||
case extract_heredoc(Line, T, H) of
|
||||
@@ -532,7 +533,7 @@ extract_heredoc(Line0, Rest0, Marker) ->
|
||||
|
||||
heredoc_error(ErrorLine, StartLine, Marker) ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = io_lib:format("missing terminator: ~s (for heredoc starting at line ~B)", [Terminator, StartLine]),
|
||||
Message = io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)", [Terminator, StartLine]),
|
||||
{ error, { ErrorLine, Message, [] } }.
|
||||
|
||||
%% Remove spaces from heredoc based on the position of the final quotes.
|
||||
@@ -543,7 +544,7 @@ remove_heredoc_spaces(Body, 0) ->
|
||||
remove_heredoc_spaces(Body, Spaces) ->
|
||||
remove_heredoc_spaces([0|Body], [], Spaces, Spaces).
|
||||
|
||||
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, H == $\s orelse H == $\t ->
|
||||
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
|
||||
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
|
||||
|
||||
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
|
||||
@@ -585,7 +586,7 @@ extract_heredoc_line(_, _) ->
|
||||
|
||||
%% Extract each heredoc line trying to find a match according to the marker.
|
||||
|
||||
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when H == $\t; H == $\s ->
|
||||
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when ?is_horizontal_space(H) ->
|
||||
extract_heredoc_line(Marker, T, [H|Buffer], Counter + 1);
|
||||
|
||||
extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) ->
|
||||
@@ -597,9 +598,9 @@ extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) -
|
||||
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
|
||||
extract_heredoc_line(Rest, Buffer).
|
||||
|
||||
next_is_break([H|T]) when H == $\t; H == $\s -> next_is_break(T);
|
||||
next_is_break([$\r,$\n|T]) -> T;
|
||||
next_is_break([$\n|T]) -> T;
|
||||
next_is_break([H|T]) when ?is_horizontal_space(H) -> next_is_break(T);
|
||||
next_is_break("\r\n" ++ T) -> T;
|
||||
next_is_break("\n" ++ T) -> T;
|
||||
next_is_break([]) -> [];
|
||||
next_is_break(_) -> false.
|
||||
|
||||
@@ -615,121 +616,113 @@ merge_heredoc_extra([H|T], Buffer) ->
|
||||
merge_heredoc_extra([], Buffer) ->
|
||||
Buffer.
|
||||
|
||||
% Integers and floats
|
||||
% At this point, we are at least sure the first digit is a number.
|
||||
%% Integers and floats
|
||||
%% At this point, we are at least sure the first digit is a number.
|
||||
|
||||
% Check if we have a point followed by a number;
|
||||
%% Check if we have a point followed by a number;
|
||||
tokenize_number([$.,H|T], Acc, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H,$.|Acc], true);
|
||||
|
||||
% Check if we have an underscore followed by a number;
|
||||
%% Check if we have an underscore followed by a number;
|
||||
tokenize_number([$_,H|T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H|Acc], Bool);
|
||||
|
||||
% Check if we have e- followed by numbers. Valid only for floats.
|
||||
%% Check if we have e- followed by numbers (valid only for floats);
|
||||
tokenize_number([$e,S,H|T], Acc, true) when ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number(T, [H,S,$e|Acc], true);
|
||||
|
||||
% Check if we have e followed by numbers. Valid only for floats.
|
||||
%% Check if we have e followed by numbers (valid only for floats);
|
||||
tokenize_number([$e,H|T], Acc, true) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H,$e|Acc], true);
|
||||
|
||||
% Just numbers;
|
||||
%% Finally just numbers.
|
||||
tokenize_number([H|T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H|Acc], Bool);
|
||||
|
||||
% Cast to float...
|
||||
%% Cast to float...
|
||||
tokenize_number(Rest, Acc, true) ->
|
||||
{ Rest, list_to_float(lists:reverse(Acc)) };
|
||||
|
||||
% Or integer.
|
||||
%% Or integer.
|
||||
tokenize_number(Rest, Acc, false) ->
|
||||
{ Rest, list_to_integer(lists:reverse(Acc)) }.
|
||||
|
||||
% Hex
|
||||
tokenize_hex([H|T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H|Acc]);
|
||||
tokenize_hex(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 16) }.
|
||||
|
||||
% Octal
|
||||
tokenize_octal([H|T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H|Acc]);
|
||||
tokenize_octal(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 8) }.
|
||||
|
||||
% Bin
|
||||
tokenize_bin([H|T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H|Acc]);
|
||||
tokenize_bin(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 2) }.
|
||||
|
||||
% Comments
|
||||
%% Comments
|
||||
|
||||
tokenize_comment("\r\n" ++ _ = Rest) -> Rest;
|
||||
tokenize_comment("\n" ++ _ = Rest) -> Rest;
|
||||
tokenize_comment([_|Rest]) -> tokenize_comment(Rest);
|
||||
tokenize_comment([]) -> [].
|
||||
|
||||
% Identifiers
|
||||
%% Atoms
|
||||
%% Handle atoms specially since they support @
|
||||
|
||||
% Tokenize identifier. At this point, the validity of
|
||||
% the first character was already verified.
|
||||
tokenize_atom([H|T], Acc, Scope) when ?is_atom(H) ->
|
||||
tokenize_atom(T, [H|Acc], Scope);
|
||||
|
||||
tokenize_identifier([H|T], Acc, Marker) when Marker == atom, H == $@;
|
||||
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_ ->
|
||||
tokenize_identifier(T, [H|Acc], Marker);
|
||||
tokenize_atom([H|T], Acc, Scope) when H == $?; H == $! ->
|
||||
{ T, unsafe_to_atom(lists:reverse([H|Acc]), Scope) };
|
||||
|
||||
tokenize_identifier([], Acc, _Marker) ->
|
||||
{ [], lists:reverse(Acc) };
|
||||
tokenize_atom(Rest, Acc, Scope) ->
|
||||
{ Rest, unsafe_to_atom(lists:reverse(Acc), Scope) }.
|
||||
|
||||
tokenize_identifier(Rest, Acc, _Marker) ->
|
||||
%% Identifiers
|
||||
%% At this point, the validity of the first character was already verified.
|
||||
|
||||
tokenize_identifier([H|T], Acc) when ?is_identifier(H) ->
|
||||
tokenize_identifier(T, [H|Acc]);
|
||||
|
||||
tokenize_identifier(Rest, Acc) ->
|
||||
{ Rest, lists:reverse(Acc) }.
|
||||
|
||||
% Tokenize atom identifier, which also accepts punctuated identifiers
|
||||
tokenize_atom(String, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc, atom),
|
||||
case Rest of
|
||||
[H|T] when H == $?; H == $! ->
|
||||
{ T, unsafe_to_atom(Identifier ++ [H], Scope) };
|
||||
_ ->
|
||||
{ Rest, unsafe_to_atom(Identifier, Scope) }
|
||||
end.
|
||||
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
|
||||
% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
tokenize_any_identifier(String, Line, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc, false),
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc),
|
||||
|
||||
case Rest of
|
||||
[H,$:|T] when H == $? orelse H == $!, hd(T) /= $: ->
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
verify_kw_and_space(Line, Atom, T, Scope),
|
||||
{ T, { kw_identifier, Line, Atom } };
|
||||
[H|T] when H == $?; H == $! ->
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
{ T, tokenize_call_identifier(punctuated_identifier, Line, Atom, T) };
|
||||
[$:|T] when hd(T) /= $: ->
|
||||
tokenize_kw_or_call_identifier(punctuated_identifier, Line, Atom, T);
|
||||
_ ->
|
||||
Atom = unsafe_to_atom(Identifier, Scope),
|
||||
verify_kw_and_space(Line, Atom, T, Scope),
|
||||
{ T, { kw_identifier, Line, Atom } };
|
||||
_ ->
|
||||
{ Rest, tokenize_call_identifier(identifier, Line, unsafe_to_atom(Identifier, Scope), Rest) }
|
||||
tokenize_kw_or_call_identifier(identifier, Line, Atom, Rest)
|
||||
end.
|
||||
|
||||
tokenize_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
case Rest of
|
||||
[$(|_] -> { paren_identifier, Line, Atom };
|
||||
[$[|_] -> { bracket_identifier, Line, Atom };
|
||||
_ ->
|
||||
case next_is_block(Rest) of
|
||||
false -> { Kind, Line, Atom };
|
||||
BlockIdentifier -> { BlockIdentifier, Line, Atom }
|
||||
end
|
||||
end.
|
||||
%% Tokenize kw or call identifier (paren | bracket | do)
|
||||
|
||||
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
|
||||
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
|
||||
io:format("~ts:~w: keyword argument ~s: must be followed by space~n", [File, 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) ->
|
||||
{ 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) }.
|
||||
|
||||
%% 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_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_; H == $: ->
|
||||
next_is_block([$d,$o,H|_]) when ?is_identifier(H); ?is_terminator(H) ->
|
||||
false;
|
||||
|
||||
next_is_block([$d,$o|_]) ->
|
||||
@@ -741,21 +734,18 @@ next_is_block(_) ->
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
% Error handling
|
||||
%% Error handling
|
||||
|
||||
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
|
||||
{ error, { Line, io_lib:format("~s" ++ Extension, [Message|Args]), Token } }.
|
||||
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
|
||||
|
||||
% Terminators
|
||||
%% Terminators
|
||||
|
||||
string_type($") -> bin_string;
|
||||
string_type($') -> list_string.
|
||||
|
||||
sigil_terminator($() -> $);
|
||||
sigil_terminator($[) -> $];
|
||||
sigil_terminator(${) -> $};
|
||||
sigil_terminator($<) -> $>;
|
||||
sigil_terminator(O) -> O.
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens) ->
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
|
||||
Scope;
|
||||
@@ -788,7 +778,7 @@ 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: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
|
||||
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { Line, Message, [] } };
|
||||
|
||||
check_terminator({ E, Line }, []) when
|
||||
@@ -798,6 +788,15 @@ check_terminator({ E, Line }, []) when
|
||||
check_terminator(_, Terminators) ->
|
||||
Terminators.
|
||||
|
||||
string_type($") -> bin_string;
|
||||
string_type($') -> list_string.
|
||||
|
||||
sigil_terminator($() -> $);
|
||||
sigil_terminator($[) -> $];
|
||||
sigil_terminator(${) -> $};
|
||||
sigil_terminator($<) -> $>;
|
||||
sigil_terminator(O) -> O.
|
||||
|
||||
terminator('fn') -> 'end';
|
||||
terminator('do') -> 'end';
|
||||
terminator('(') -> ')';
|
||||
@@ -805,31 +804,43 @@ terminator('[') -> ']';
|
||||
terminator('{') -> '}';
|
||||
terminator('<<') -> '>>'.
|
||||
|
||||
% Keywords check
|
||||
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
[{ Identifier, Line, Atom }|Tokens];
|
||||
%% Keywords checking
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when
|
||||
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
{ ok, [{ Identifier, Line, Atom }|Tokens] };
|
||||
|
||||
check_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> [{ Atom, Line }|Tokens];
|
||||
op -> add_token_with_nl({ Atom, Line }, Tokens);
|
||||
block -> [{ block_identifier, Line, Atom }|Tokens];
|
||||
false -> false
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
do ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
false -> nomatch
|
||||
end;
|
||||
|
||||
handle_keyword(_, _, _, _) -> false.
|
||||
check_keyword(_, _, _, _) -> nomatch.
|
||||
|
||||
% Keywords
|
||||
do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
is_boolean(keyword(Atom));
|
||||
do_keyword_valid(_) -> true.
|
||||
|
||||
% Regular keywords
|
||||
keyword('fn') -> true;
|
||||
keyword('do') -> true;
|
||||
keyword('end') -> true;
|
||||
keyword('true') -> true;
|
||||
keyword('false') -> true;
|
||||
keyword('nil') -> true;
|
||||
keyword('not') -> true;
|
||||
|
||||
% Special handling for do
|
||||
keyword('do') -> do;
|
||||
|
||||
% Bin operator keywords
|
||||
keyword('and') -> op;
|
||||
keyword('or') -> op;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
%% Module responsible for tracking invocations of locals,
|
||||
%% imports and requires.
|
||||
-module(elixir_tracker).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
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,
|
||||
warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__dispatch_tracker').
|
||||
-define(tracker, 'Elixir.Module.DispatchTracker').
|
||||
|
||||
%% RECORD
|
||||
|
||||
setup(Module) ->
|
||||
case code:ensure_loaded(?tracker) of
|
||||
{ module, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
|
||||
{ error, _ } -> ok
|
||||
end.
|
||||
|
||||
cleanup(Module) ->
|
||||
if_tracker(Module, fun(Pid) -> ?tracker:stop(Pid) end).
|
||||
|
||||
record_local(Tuple, Module) when is_atom(Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_local(Pid, Tuple),
|
||||
true
|
||||
end).
|
||||
record_local(Tuple, _Module, Function)
|
||||
when Function == nil; Function == Tuple -> false;
|
||||
record_local(Tuple, Module, Function) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_local(Pid, Function, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_warn(Ref, Warn, Line, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_warnable(Pid, Ref, Warn, Line),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_import(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_import(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_defaults(_Tuple, _Kind, _Module, 0) ->
|
||||
true;
|
||||
record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
case ets:lookup(Module, ?attr) of
|
||||
[{ ?attr, Pid }] -> Callback(Pid);
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, { import_conflict, Error })
|
||||
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
|
||||
end),
|
||||
ok.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_imports(Pid)]
|
||||
end),
|
||||
ok.
|
||||
|
||||
warn_unused_local(File, Module, Private) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
Args = [ { Fun, Kind, Defaults } ||
|
||||
{ Fun, Kind, _Line, true, Defaults } <- Private],
|
||||
|
||||
Unused = ?tracker:collect_unused_locals(Pid, Args),
|
||||
|
||||
[ begin
|
||||
{ _, _, Line, _, _ } = lists:keyfind(element(2, Error), 1, Private),
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, Error })
|
||||
end || Error <- Unused ]
|
||||
end).
|
||||
|
||||
format_error({import_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 }) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},1}) ->
|
||||
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},Count}) ->
|
||||
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
||||
|
||||
format_error({unused_def,{Name, Arity},defp}) ->
|
||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||
|
||||
format_error({unused_def,{Name, Arity},defmacrop}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
@@ -1,38 +0,0 @@
|
||||
-module(elixir_transform).
|
||||
-export([parse_transform/2]).
|
||||
|
||||
parse_transform(Forms, _) ->
|
||||
do_transform(Forms).
|
||||
|
||||
do_transform({ atom, Line, Atom }) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir." ++ _ = List ->
|
||||
Module = [case T of
|
||||
$. -> $-;
|
||||
_ -> T
|
||||
end || T <- List],
|
||||
{ atom, Line, list_to_atom(Module) };
|
||||
_ ->
|
||||
{ atom, Line, Atom }
|
||||
end;
|
||||
|
||||
do_transform({ Name, Item1 }) ->
|
||||
{ Name, do_transform(Item1) };
|
||||
|
||||
do_transform({ Name, Item1, Item2 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4, Item5 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4), do_transform(Item5) };
|
||||
|
||||
do_transform(List) when is_list(List) ->
|
||||
[do_transform(X) || X <- List];
|
||||
|
||||
do_transform(Other) when not is_tuple(Other) ->
|
||||
Other.
|
||||
@@ -7,16 +7,17 @@
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_assign_or_guard_scope/3]).
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, Tokens } ->
|
||||
case elixir_parser:parse(Tokens) of
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, { _, _, _ } } = Else -> Else
|
||||
catch
|
||||
@@ -38,7 +39,7 @@ translate(Forms, S) ->
|
||||
|
||||
%% Assignment operator
|
||||
|
||||
translate_each({'=', Meta, [Left, Right]}, S) ->
|
||||
translate_each({ '=', Meta, [Left, Right] }, S) ->
|
||||
assert_no_guard_scope(Meta, '=', S),
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
|
||||
@@ -61,6 +62,9 @@ translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{erl,true}],[{do,Expr}]] }, S) ->
|
||||
{ Expr, S };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
@@ -94,42 +98,13 @@ translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
translate_each({ alias, Meta, [Ref,[]] }, S);
|
||||
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, alias, S),
|
||||
validate_opts(Meta, alias, [as], KV, S),
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
{ New, SF } = case lists:keyfind(as, 1, KV) of
|
||||
Opt when Opt == { as, true }; Opt == false ->
|
||||
{ elixir_aliases:last(Old), SR };
|
||||
{ as, false } ->
|
||||
{ Old, SR };
|
||||
{ as, Other } ->
|
||||
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
|
||||
case TOther of
|
||||
{ atom, _, Atom } -> { Atom, SA };
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, expected an atom or alias as argument")
|
||||
end
|
||||
end,
|
||||
|
||||
%% Avoid creating aliases if first == last
|
||||
%% unecessarily polluting the aliases dict
|
||||
case New == Old of
|
||||
true -> { { nil, ?line(Meta) }, SF };
|
||||
false ->
|
||||
case string:tokens(atom_to_list(New), "-") of
|
||||
[_,_] -> [];
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { nil, ?line(Meta) }, SF#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
} }
|
||||
end;
|
||||
translate_alias(Meta, true, Old, TKV, ST);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
|
||||
end;
|
||||
@@ -138,25 +113,15 @@ translate_each({ require, Meta, [Ref] }, S) ->
|
||||
translate_each({ require, Meta, [Ref, []] }, S);
|
||||
|
||||
translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'require', S),
|
||||
validate_opts(Meta, require, [as], KV, S),
|
||||
assert_no_match_or_guard_scope(Meta, require, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
|
||||
As = case lists:keyfind(as, 1, KV) of
|
||||
false -> false;
|
||||
{ as, Value } -> Value
|
||||
end,
|
||||
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, SR),
|
||||
|
||||
SF = SR#elixir_scope{
|
||||
requires=ordsets:add_element(Old, S#elixir_scope.requires)
|
||||
},
|
||||
|
||||
translate_each({ alias, Meta, [Ref, [{ as, As }]] }, SF);
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
translate_require(Meta, Old, TKV, ST);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
|
||||
end;
|
||||
@@ -167,34 +132,34 @@ translate_each({ import, Meta, [Left] }, S) ->
|
||||
translate_each({ import, Meta, [Left,Opts] }, S) when is_list(Opts) ->
|
||||
translate_each({ import, Meta, [default, Left, Opts]}, S);
|
||||
|
||||
translate_each({ import, Meta, [Selector, Left] }, S) ->
|
||||
translate_each({ import, Meta, [Selector, Left, []]}, S);
|
||||
translate_each({ import, Meta, [Left,Right] }, S) ->
|
||||
%% Second argument is ambiguous, translate it and take a peek
|
||||
case translate_each(Right, S) of
|
||||
{ { atom, _, _ }, _ } ->
|
||||
translate_each({ import, Meta, [Left, Right, []]}, S);
|
||||
_ ->
|
||||
translate_each({ import, Meta, [default, Left, Right]}, S)
|
||||
end;
|
||||
|
||||
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'import', S),
|
||||
translate_each({ import, Meta, [Left, Right, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, S),
|
||||
{ TSelector, SL } = translate_each(Left, S),
|
||||
{ TRef, SR } = translate_each(Right, SL),
|
||||
{ TKV, ST } = translate_opts(Meta, import, [as, only, except, warn], no_alias_opts(KV), SR),
|
||||
|
||||
Selector = case TSelector of
|
||||
{ atom, _, SelectorAtom } -> SelectorAtom;
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
|
||||
end,
|
||||
|
||||
Ref = case TRef of
|
||||
{ atom, _, RefAtom } -> RefAtom;
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
|
||||
end,
|
||||
|
||||
validate_opts(Meta, import, [as, only, except], Opts, S),
|
||||
|
||||
As = case lists:keyfind(as, 1, Opts) of
|
||||
false -> false;
|
||||
{ as, Value } -> Value
|
||||
end,
|
||||
|
||||
elixir_aliases:ensure_loaded(Meta, Ref, SR),
|
||||
SF = elixir_import:import(Meta, Ref, Opts, Selector, SR),
|
||||
translate_each({ require, Meta, [Ref, [{ as, As }]] }, SF);
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
|
||||
translate_require(Meta, Old, TKV, SF);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
|
||||
end;
|
||||
|
||||
%% Pseudo variables
|
||||
|
||||
@@ -204,8 +169,12 @@ translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(S#elixir_scope.file, S);
|
||||
|
||||
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(filename:dirname(S#elixir_scope.file), S);
|
||||
|
||||
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ elixir_scope:to_erl_env({ ?line(Meta), S }), S };
|
||||
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
|
||||
{ elixir_tree_helpers:elixir_to_erl(Env), S };
|
||||
|
||||
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
@@ -213,8 +182,8 @@ translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases) of
|
||||
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom}, S };
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom }, S };
|
||||
Aliases ->
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
|
||||
@@ -223,74 +192,71 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
{ { atom, ?line(Meta), elixir_aliases:concat(Atoms) }, SA };
|
||||
false ->
|
||||
Args = [elixir_tree_helpers:build_simple_list(?line(Meta), TAliases)],
|
||||
Args = [elixir_tree_helpers:list_to_cons(?line(Meta), TAliases)],
|
||||
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% Quoting
|
||||
|
||||
translate_each({ Unquote, Meta, _Args }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
translate_each({ quote, Meta, [Left, Right] }, S) ->
|
||||
translate_each({ quote, Meta, [orddict:from_list(Left ++ Right)] }, S);
|
||||
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
|
||||
end;
|
||||
|
||||
translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, T) of
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, E } -> E;
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(var_context, 1, T) of
|
||||
{ var_context, VarContext } ->
|
||||
expand_var_context(Meta, VarContext,
|
||||
"invalid argument given for var_context in quote", S);
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, false } -> nil;
|
||||
_ ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
GetExpansion = fun(Key) ->
|
||||
case lists:keyfind(Key, 1, T) of
|
||||
{ Key, Bool } when is_boolean(Bool) ->
|
||||
Bool;
|
||||
false ->
|
||||
case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> true
|
||||
end
|
||||
end
|
||||
end,
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
|
||||
Aliases = GetExpansion(expand_aliases),
|
||||
Imports = GetExpansion(expand_imports),
|
||||
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
false -> { nil, nil }
|
||||
end,
|
||||
|
||||
Line = case lists:keyfind(line, 1, T) of
|
||||
Line = case lists:keyfind(line, 1, TKV) of
|
||||
{ line, LineValue } -> LineValue;
|
||||
false -> DefaultLine
|
||||
end,
|
||||
|
||||
{ TLine, SL } = case Line of
|
||||
keep -> { keep, S };
|
||||
nil -> { nil, S };
|
||||
_ -> translate_each(Line, S)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, T) of
|
||||
File = case lists:keyfind(file, 1, TKV) of
|
||||
{ file, FileValue } -> FileValue;
|
||||
false -> DefaultFile
|
||||
end,
|
||||
@@ -300,7 +266,7 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
_ -> File
|
||||
end,
|
||||
|
||||
TExprs = if
|
||||
WExprs = if
|
||||
is_binary(TFile) ->
|
||||
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
|
||||
File == nil ->
|
||||
@@ -309,17 +275,18 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, T) of
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, false } -> false;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{var_context=Context, line=TLine, unquote=Unquote,
|
||||
expand_aliases=Aliases, expand_imports=Imports},
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
elixir_quote:quote(TExprs, Q, SL);
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(WExprs, Q, ST),
|
||||
{ TExprs, TS };
|
||||
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
|
||||
@@ -332,7 +299,7 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S);
|
||||
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
|
||||
Expanded = expand_var_context(Meta, Kind, "invalid second argument for var!", S),
|
||||
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
|
||||
end);
|
||||
@@ -343,7 +310,7 @@ translate_each({ 'var!', Meta, [_, _] }, S) ->
|
||||
%% Functions
|
||||
|
||||
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'fn', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'fn', S),
|
||||
translate_fn(Meta, Pairs, S);
|
||||
|
||||
%% Comprehensions
|
||||
@@ -356,7 +323,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
|
||||
translate_each({ super, Meta, Args } = Original, S) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_assign_or_guard_scope(Meta, super, S),
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
Module = assert_module_scope(Meta, super, S),
|
||||
Function = assert_function_scope(Meta, super, S),
|
||||
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
||||
@@ -364,9 +331,12 @@ translate_each({ super, Meta, Args } = Original, S) ->
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) ->
|
||||
is_atom(Args) andalso Arity == 0 ->
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
length(Args) == Arity ->
|
||||
is_atom(Args) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "omitting arguments of super is deprecated, please call super with arguments instead"),
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
not is_atom(Args) andalso length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
|
||||
@@ -385,20 +355,21 @@ translate_each({ 'super?', Meta, [] }, S) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, Meta, Value }, S };
|
||||
error ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~s", [Name])
|
||||
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot access variable ^~s outside of assignment", [Name]);
|
||||
"cannot use ^~ts outside of match clauses", [Name]);
|
||||
|
||||
translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
|
||||
@@ -413,28 +384,23 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
false ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, Receiver } ->
|
||||
{ TRes, TS } = translate_each({ { '.', Meta, [Receiver, Atom] }, Meta, Args },
|
||||
S#elixir_scope{check_requires=false}),
|
||||
{ TRes, TS#elixir_scope{check_requires=S#elixir_scope.check_requires} };
|
||||
false ->
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~s or cannot invoke local ~s/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Meta, File, "cannot invoke local ~s/~B inside guard", [Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback)
|
||||
end;
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
|
||||
Else -> Else
|
||||
end;
|
||||
Else -> Else
|
||||
@@ -457,9 +423,23 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), Callback);
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end);
|
||||
_ ->
|
||||
Callback()
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end;
|
||||
Else -> Else
|
||||
end;
|
||||
@@ -472,7 +452,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
translate_each({ Atom, Meta, Args }, S);
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
|
||||
_ ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
@@ -495,6 +475,14 @@ translate_each(Literal, S) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ TOpts, TS } = translate_each(Opts, S),
|
||||
FOpts = elixir_tree_helpers:erl_to_elixir(TOpts),
|
||||
validate_opts(Meta, Kind, Allowed, FOpts, TS),
|
||||
{ FOpts, TS }.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
|
||||
@@ -503,6 +491,69 @@ validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
%% Quote
|
||||
|
||||
expand_quote_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
expand_quote_context(Meta, Alias, Msg, S) ->
|
||||
case translate_each(Alias, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
Atom;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
end.
|
||||
|
||||
%% Require
|
||||
|
||||
translate_require(Meta, Old, TKV, S) ->
|
||||
SF = S#elixir_scope{
|
||||
requires=ordsets:add_element(Old, S#elixir_scope.requires)
|
||||
},
|
||||
translate_alias(Meta, false, Old, TKV, SF).
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
|
||||
New = case lists:keyfind(as, 1, TKV) of
|
||||
{ as, true } ->
|
||||
elixir_aliases:last(Old);
|
||||
{ as, false } ->
|
||||
Old;
|
||||
{ as, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
false ->
|
||||
if
|
||||
IncludeByDefault -> elixir_aliases:last(Old);
|
||||
true -> Old
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, expected an atom or alias in option :as")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
||||
false -> KV
|
||||
end;
|
||||
|
||||
no_alias_opts(KV) -> KV.
|
||||
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
%% Function
|
||||
|
||||
translate_fn(Meta, Clauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
@@ -521,28 +572,16 @@ translate_fn(Meta, Clauses, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
translate_local(Meta, Name, Args, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, S#elixir_scope.local },
|
||||
{ atom, Line, Name }
|
||||
},
|
||||
{ { call, Line, Remote, TArgs }, NS }.
|
||||
|
||||
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
expand_var_context(Meta, Alias, Msg, S) ->
|
||||
case translate_each(Alias, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
Atom;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
end.
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
|
||||
%% Translate args
|
||||
|
||||
@@ -563,9 +602,9 @@ expand_var_context(Meta, Alias, Msg, S) ->
|
||||
|
||||
translate_arg(Arg, { Acc, S }) ->
|
||||
{ TArg, TAcc } = translate_each(Arg, Acc),
|
||||
{ TArg, { umergec(S, TAcc), umergev(S, TAcc) } }.
|
||||
{ TArg, { umergec(Acc, TAcc), umergev(S, TAcc) } }.
|
||||
|
||||
translate_args(Args, #elixir_scope{context=assign} = S) ->
|
||||
translate_args(Args, #elixir_scope{context=match} = S) ->
|
||||
translate(Args, S);
|
||||
|
||||
translate_args(Args, S) ->
|
||||
@@ -641,11 +680,14 @@ translate_comprehension_do(Meta, bc, _Expr, S) ->
|
||||
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
|
||||
translate_each(Expr, S).
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [Left, Right]}, S) ->
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
||||
|
||||
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% to generate erlang abstract format for basic structures
|
||||
%% as lists, condition clauses, etc.
|
||||
|
||||
-module(elixir_tree_helpers).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1,
|
||||
build_list/4, build_list/5, build_simple_list/2,
|
||||
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
|
||||
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
get_line(Opts) ->
|
||||
@@ -21,53 +18,57 @@ split_last(List) -> split_last(List, []).
|
||||
split_last([H], Acc) -> { lists:reverse(Acc), H };
|
||||
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
|
||||
|
||||
abstract_syntax(Tree) ->
|
||||
erl_syntax:revert(erl_syntax:abstract(Tree)).
|
||||
cons_to_list({ nil, _ }) -> [];
|
||||
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
|
||||
|
||||
cons_to_list({ cons, _, Left, { nil, _ } }) ->
|
||||
[Left];
|
||||
list_to_cons(Line, List) ->
|
||||
list_to_cons(Line, List, { nil, Line }).
|
||||
|
||||
cons_to_list({ cons, _, Left, Right }) ->
|
||||
[Left|cons_to_list(Right)].
|
||||
list_to_cons(Line, List, Tail) ->
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
{ cons, Line, X, Acc }
|
||||
end, Tail, List).
|
||||
|
||||
% Build a list translating each expression and accumulating
|
||||
% vars in one pass. It uses tail-recursive form.
|
||||
%
|
||||
% It receives a function to translate each expression given
|
||||
% in Exprs, a Line used to build the List and the variables
|
||||
% scope V is passed down item by item.
|
||||
%
|
||||
% The function needs to return a tuple where the first element
|
||||
% is an erlang abstract form and the second is the new variables
|
||||
% list.
|
||||
build_list(Fun, Exprs, Line, S) ->
|
||||
build_list(Fun, Exprs, Line, S, {nil, Line}).
|
||||
%% erl <-> elixir
|
||||
|
||||
build_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
|
||||
build_list_each(Fun, lists:reverse(Exprs), Line, S, Tail).
|
||||
elixir_to_erl(Tree) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
% Same as build_list, but the list given is in reverse other.
|
||||
build_reverse_list(Fun, Exprs, Line, S) ->
|
||||
build_list_each(Fun, Exprs, Line, S, {nil,Line}).
|
||||
elixir_to_erl(Tree) when is_list(Tree) ->
|
||||
elixir_to_erl_cons(lists:reverse(Tree), { nil, 0 });
|
||||
|
||||
build_reverse_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
|
||||
build_list_each(Fun, Exprs, Line, S, Tail).
|
||||
elixir_to_erl(Tree) when is_atom(Tree) ->
|
||||
{ atom, 0, Tree };
|
||||
|
||||
% Builds a simple list, without translatation, just by generating the cons-cell.
|
||||
build_simple_list(Line, Args) ->
|
||||
{ List, [] } = build_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
|
||||
List.
|
||||
elixir_to_erl(Tree) when is_integer(Tree) ->
|
||||
{ integer, 0, Tree };
|
||||
|
||||
build_simple_reverse_list(Line, Args) ->
|
||||
{ List, [] } = build_reverse_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
|
||||
List.
|
||||
elixir_to_erl(Tree) when is_float(Tree) ->
|
||||
{ float, 0, Tree };
|
||||
|
||||
build_list_each(_Fun, [], _Line, S, Acc) ->
|
||||
{ Acc, S };
|
||||
elixir_to_erl(Tree) when is_binary(Tree) ->
|
||||
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] }.
|
||||
|
||||
build_list_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_list_each(Fun, T, Line, NS, { cons, Line, Expr, Acc }).
|
||||
elixir_to_erl_cons([H|T], Acc) ->
|
||||
elixir_to_erl_cons(T, { cons, 0, elixir_to_erl(H), Acc });
|
||||
|
||||
elixir_to_erl_cons([], Acc) -> Acc.
|
||||
|
||||
erl_to_elixir({ tuple, _, Args }) ->
|
||||
list_to_tuple([erl_to_elixir(X) || X <- Args]);
|
||||
|
||||
erl_to_elixir({ Kind, _, Value }) when Kind == atom; Kind == integer; Kind == float ->
|
||||
Value;
|
||||
|
||||
erl_to_elixir({ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] }) ->
|
||||
list_to_binary(String);
|
||||
|
||||
erl_to_elixir({ nil, _ }) -> [];
|
||||
|
||||
erl_to_elixir({ cons, _, Left, Right }) -> [erl_to_elixir(Left)|erl_to_elixir(Right)];
|
||||
|
||||
erl_to_elixir(Other) ->
|
||||
{ '__scope__', [], [[{erl,true}],[{do,Other}]] }.
|
||||
|
||||
%% Others
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
@@ -49,7 +48,6 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
validate_rescue_access(Meta, Condition, S),
|
||||
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
|
||||
end;
|
||||
|
||||
@@ -57,7 +55,7 @@ each_clause(Meta, {rescue,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause(Meta, {Key,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s in try", [Key]).
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -65,8 +63,12 @@ each_clause(Meta, {Key,_,_}, S) ->
|
||||
normalize_rescue(Meta, List, S) when is_list(List) ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
|
||||
|
||||
%% rescue _
|
||||
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
|
||||
false;
|
||||
|
||||
%% rescue var -> var in _
|
||||
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom), Name /= '_' ->
|
||||
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
|
||||
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
@@ -85,22 +87,13 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
end
|
||||
end;
|
||||
|
||||
normalize_rescue(_, { '=', Meta, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
|
||||
when is_atom(Name) and is_atom(Atom) ->
|
||||
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
|
||||
false;
|
||||
|
||||
normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
|
||||
when is_atom(Name) and is_atom(Atom) ->
|
||||
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
|
||||
false;
|
||||
|
||||
normalize_rescue(Meta, Condition, S) ->
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
|
||||
_ ->
|
||||
false
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
|
||||
"a variable or be in the `var in [alias]` format")
|
||||
end.
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
@@ -242,30 +235,12 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
] },
|
||||
{ 'or', Meta, [IsNotTuple, IsException] }.
|
||||
|
||||
%% Validate rescue access
|
||||
|
||||
validate_rescue_access(Meta, { '=', _, [Left, Right] }, S) ->
|
||||
validate_rescue_access(Meta, Left, S),
|
||||
validate_rescue_access(Meta, Right, S);
|
||||
|
||||
validate_rescue_access(Meta, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
|
||||
case elixir_translator:translate_each(Element, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
case lists:member(Atom, erlang_rescues()) of
|
||||
false -> [];
|
||||
true -> elixir_errors:syntax_error(Meta, S#elixir_scope.file, "cannot (yet) pattern match against erlang exceptions")
|
||||
end;
|
||||
_ -> []
|
||||
end;
|
||||
|
||||
validate_rescue_access(_, _, _) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
format_error({ rescue_no_match, Var, Alias }) ->
|
||||
VarBinary = 'Elixir.Macro':to_binary(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_binary(Alias),
|
||||
Message = "rescue clause (~s = ~s) can never match, maybe you meant to write: ~s in [~s] ?",
|
||||
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
|
||||
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
||||
|
||||
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
ExUnit.start []
|
||||
|
||||
defmodule KernelTest do
|
||||
use ExUnit.Case, async: true
|
||||
doctest Kernel
|
||||
doctest Kernel.SpecialForms
|
||||
doctest Access.List
|
||||
doctest Binary.Chars.List
|
||||
doctest Binary.Inspect.Atom
|
||||
doctest Binary.Inspect.BitString
|
||||
doctest Binary.Inspect.List
|
||||
doctest Binary.Inspect.Tuple
|
||||
doctest Binary.Inspect.Number
|
||||
doctest Binary.Inspect.Regex
|
||||
doctest Bitwise
|
||||
doctest Code
|
||||
doctest Dict
|
||||
doctest Enum
|
||||
doctest IO.ANSI
|
||||
doctest Regex
|
||||
doctest String
|
||||
doctest OptionParser
|
||||
doctest Path
|
||||
doctest Module
|
||||
doctest Macro
|
||||
doctest List
|
||||
doctest Keyword
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule AccessTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -13,12 +13,6 @@ defmodule AccessTest do
|
||||
assert nil[:foo] == nil
|
||||
end
|
||||
|
||||
test :function do
|
||||
function = fn x -> x == :foo end
|
||||
assert function[:foo] == true
|
||||
assert function[:bar] == false
|
||||
end
|
||||
|
||||
# Test nil at compilation time does not fail
|
||||
# and that @config[:foo] has proper precedence.
|
||||
nil = @config[:foo]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Binary.Chars.AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -23,7 +23,7 @@ defmodule Binary.Chars.AtomTest do
|
||||
end
|
||||
|
||||
test :alias_atom do
|
||||
assert to_binary(Foo.Bar) == "Elixir-Foo-Bar"
|
||||
assert to_binary(Foo.Bar) == "Elixir.Foo.Bar"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,9 +51,8 @@ defmodule Binary.Chars.NumberTest do
|
||||
end
|
||||
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.00000000000000000000e+00"
|
||||
assert to_binary(1.0e10) == "1.00000000000000000000e+10"
|
||||
assert to_binary(1.0e+10) == "1.00000000000000000000e+10"
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,3 +71,89 @@ defmodule Binary.Chars.ListTest do
|
||||
assert to_binary([]) == ""
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Binary.Chars.ErrorsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :list do
|
||||
assert to_binary([0,1, 2, 3,255]) == <<0,1,2,3,255>>
|
||||
assert to_binary([0,[1, "hello"], 2, [["bye"]]]) == <<0,1,104,101,108,108,111,2,98,121,101>>
|
||||
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [256]. Only iolists are supported", fn ->
|
||||
to_binary([256])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001,10001,100001]. Only iolists are supported", fn ->
|
||||
to_binary([1001, 10001, 100001])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom,13,\"hello\"]. Only iolists are supported", fn ->
|
||||
to_binary([:atom, 13, "hello"])
|
||||
end
|
||||
end
|
||||
|
||||
test :tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,2,3}", fn ->
|
||||
to_binary({1,2,3})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_in_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,[2],:atom}", fn ->
|
||||
to_binary({1, [2], :atom})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1,{[2],:atom}]. Only iolists are supported", fn ->
|
||||
to_binary([1, {[2], :atom}])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>,:atom\]\. Only iolists are supported$", fn ->
|
||||
to_binary([self(), :atom])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_fun do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom,#Function<.+?>\]\. Only iolists are supported$", fn ->
|
||||
to_binary([:atom, fn -> end])
|
||||
end
|
||||
end
|
||||
|
||||
test :record do
|
||||
# FIXME: is this expected error message?
|
||||
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.ArgumentError.to_binary/1", fn ->
|
||||
to_binary(ArgumentError[])
|
||||
end
|
||||
|
||||
# FIXME: is this expected error message?
|
||||
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.File.Stat.to_binary/1", fn ->
|
||||
to_binary(File.Stat[])
|
||||
end
|
||||
end
|
||||
|
||||
test :pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #PID<.+?>$", fn ->
|
||||
to_binary(self())
|
||||
end
|
||||
end
|
||||
|
||||
test :ref do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Reference<.+?>$", fn ->
|
||||
to_binary(make_ref()) == ""
|
||||
end
|
||||
end
|
||||
|
||||
test :function do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Function<.+?>$", fn ->
|
||||
to_binary(fn -> end)
|
||||
end
|
||||
end
|
||||
|
||||
test :port do
|
||||
[port|_] = Port.list
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Port<.+?>$", fn ->
|
||||
to_binary(port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Binary.Inspect.AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -62,6 +62,8 @@ defmodule Binary.Inspect.BitStringTest do
|
||||
assert inspect("f\no") == "\"f\\no\""
|
||||
assert inspect("f\\o") == "\"f\\\\o\""
|
||||
assert inspect("f\ao") == "\"f\\ao\""
|
||||
assert inspect(%B{#{#a}) == "\"\\\#{#a\""
|
||||
assert inspect(%B{ #{#a}) == "\" \\\#{#a\""
|
||||
end
|
||||
|
||||
test :utf8 do
|
||||
@@ -86,9 +88,8 @@ defmodule Binary.Inspect.NumberTest do
|
||||
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "1.0e10"
|
||||
assert inspect(1.0e+10) == "1.0e10"
|
||||
assert inspect(1.0e-10) == "1.0e-10"
|
||||
assert inspect(1.0e10) == "10000000000.0"
|
||||
assert inspect(1.0e-10) == "0.0000000001"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,6 +130,12 @@ defmodule Binary.Inspect.TupleTest do
|
||||
assert inspect(RuntimeError.new) == "RuntimeError[message: \"runtime error\"]"
|
||||
end
|
||||
|
||||
defrecord :something, [:a, :b]
|
||||
|
||||
test :non_module_record do
|
||||
assert inspect(:something.new) == ":something[a: nil, b: nil]"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert inspect({}) == "{}"
|
||||
end
|
||||
@@ -173,7 +180,7 @@ defmodule Binary.Inspect.ListTest do
|
||||
end
|
||||
|
||||
test :codepoints do
|
||||
assert inspect('é') == "[195,169]"
|
||||
assert inspect('é') == "'é'"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule Binary.LiteralTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -98,6 +98,14 @@ bar
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
|
||||
end
|
||||
|
||||
@binary "new "
|
||||
@charlist 'old '
|
||||
|
||||
test :bitsyntax_with_expansion do
|
||||
assert <<@binary, "world">> == "new world"
|
||||
assert <<@charlist, "world">> == "old world"
|
||||
end
|
||||
|
||||
test :bitsyntax_translation do
|
||||
refb = "sample"
|
||||
sec_data = "another"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule Bitwise.FunctionsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user