Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32d4739dad | ||
|
|
650cfc0448 | ||
|
|
d4d824022f | ||
|
|
c6284557e7 | ||
|
|
a8a2f86a23 | ||
|
|
fc219e7dca | ||
|
|
96a103d222 | ||
|
|
f525f5b8cb | ||
|
|
5c30a687fa | ||
|
|
9ffe8b0e97 | ||
|
|
12c8564a26 | ||
|
|
903aa60fec | ||
|
|
969bc968ee | ||
|
|
e941d78daf | ||
|
|
8d3475825b | ||
|
|
68c890e179 | ||
|
|
6f73d7dbb9 | ||
|
|
c2ac967123 | ||
|
|
dd573c9806 | ||
|
|
58ee415e63 | ||
|
|
561397da7a | ||
|
|
3d2438c625 | ||
|
|
a08710bfda | ||
|
|
151695b43c | ||
|
|
2d8259b432 | ||
|
|
ae03e10db5 | ||
|
|
208fbaf5ef | ||
|
|
820ca0a57e | ||
|
|
cf309ede14 | ||
|
|
da0a95695d | ||
|
|
b2541a1b93 | ||
|
|
5df40f7c42 | ||
|
|
5e24dbaddb | ||
|
|
3f8e3d4d38 | ||
|
|
391fee3ea2 | ||
|
|
6cee8572e0 | ||
|
|
f2d9e802cd | ||
|
|
5247961185 | ||
|
|
53a2618cba | ||
|
|
d360addc65 | ||
|
|
08e7da305c | ||
|
|
afab1f8d73 | ||
|
|
749266b649 | ||
|
|
c4dcaf540d | ||
|
|
561d6ea0d1 | ||
|
|
1574f61a8b | ||
|
|
7dc941227f | ||
|
|
ca026039a3 | ||
|
|
4b06a2d316 | ||
|
|
00299fe7cb | ||
|
|
9e6b650efd | ||
|
|
c56d0760f6 | ||
|
|
aedc78536f | ||
|
|
0c17fa0ab2 | ||
|
|
752a09c988 | ||
|
|
d5a8821ec1 | ||
|
|
aa7400aaad | ||
|
|
d849fc5dca | ||
|
|
3d73d1a359 | ||
|
|
18bcef3ef4 | ||
|
|
115fd99bfb | ||
|
|
879499b409 | ||
|
|
e265771c6e | ||
|
|
f9f728cb66 | ||
|
|
e321caee24 | ||
|
|
5dd3a23752 | ||
|
|
c572cb80fc | ||
|
|
b761f1a605 | ||
|
|
c1f907b412 | ||
|
|
c45d0f4f2f | ||
|
|
406f0dcffe | ||
|
|
9dbc216c11 | ||
|
|
22b455fad4 | ||
|
|
9807b506eb | ||
|
|
624423686d | ||
|
|
e5cba5e4b5 | ||
|
|
1f15bf72e7 | ||
|
|
e6bc5a273c | ||
|
|
8fa6c4f96f | ||
|
|
b7e8f97de1 | ||
|
|
5fc68fe71b | ||
|
|
808260cd1b | ||
|
|
d9152c08d8 | ||
|
|
4048e542d9 | ||
|
|
b08273700f | ||
|
|
0d5512931e | ||
|
|
7fc37de058 | ||
|
|
c4dac6fd7a | ||
|
|
d3122ddcc5 | ||
|
|
74cfcda902 | ||
|
|
6fc6dd4352 | ||
|
|
7014986f65 | ||
|
|
031f4e942f | ||
|
|
9904bdce9d | ||
|
|
754418493f | ||
|
|
27000ea1b4 | ||
|
|
620ee4298c | ||
|
|
1d09da7e53 | ||
|
|
5115256853 | ||
|
|
0d715343de | ||
|
|
441547d8e4 | ||
|
|
5e123e6519 | ||
|
|
5b45ee5c8d | ||
|
|
939942bdb3 | ||
|
|
e64be43090 | ||
|
|
da898e1ac9 | ||
|
|
8dba46a0ac | ||
|
|
633eb871ee | ||
|
|
91c0fa71fa | ||
|
|
87da4e91bb | ||
|
|
7768828d9c | ||
|
|
8ece000489 | ||
|
|
1297fa5953 | ||
|
|
c7a2442bc7 | ||
|
|
f88ee1c23a | ||
|
|
64a22fc15d | ||
|
|
07cd772962 | ||
|
|
160a8255ef | ||
|
|
cca430522d | ||
|
|
db417110e7 | ||
|
|
67896b56c5 | ||
|
|
d21d603617 | ||
|
|
fdd108fc43 | ||
|
|
19ff7a03f9 | ||
|
|
7f5744bde7 | ||
|
|
b87797f788 | ||
|
|
48b19ec580 | ||
|
|
f2e200f832 | ||
|
|
a541f53dd1 | ||
|
|
6ce77b1b49 | ||
|
|
5222c7737f | ||
|
|
b0cf65b5cd | ||
|
|
7cdc25538e | ||
|
|
b82cd8cbd0 | ||
|
|
9365556ea4 | ||
|
|
de376663f0 | ||
|
|
f24ec671c2 | ||
|
|
c0f7a268c7 | ||
|
|
7b4e0915b1 | ||
|
|
2ea7de7361 | ||
|
|
82cc435fd7 | ||
|
|
8eab556f55 | ||
|
|
5a37594853 | ||
|
|
82f3fa00d0 | ||
|
|
c97dc3bd15 | ||
|
|
d6e99e2f5a | ||
|
|
46b6f82473 | ||
|
|
c80e43ca42 | ||
|
|
80e41f8b4b | ||
|
|
9e16cf36ed | ||
|
|
1d8e39a2df | ||
|
|
21db352039 | ||
|
|
45f033d15a | ||
|
|
8eba2fcba0 | ||
|
|
e476c06798 | ||
|
|
0b6b7ea0e4 | ||
|
|
e62ef92a4b | ||
|
|
7be271876e | ||
|
|
7c639f9252 | ||
|
|
f03e707e8e | ||
|
|
38e63451d6 | ||
|
|
5e1366c946 | ||
|
|
0470ebf5af | ||
|
|
b71642045d | ||
|
|
582e814422 | ||
|
|
d15ad4da80 | ||
|
|
bc0089ff42 | ||
|
|
bff8a6ae75 | ||
|
|
6d4ad4ade6 | ||
|
|
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 | ||
|
|
057691722e | ||
|
|
0ddb31992b | ||
|
|
0e2752f7c2 | ||
|
|
76b2255d7b | ||
|
|
93d5128db3 | ||
|
|
6d98020923 | ||
|
|
77110f970b | ||
|
|
cf6ce9a3fd | ||
|
|
be0dea7c0a | ||
|
|
b0b444bbd3 | ||
|
|
3e45188e88 | ||
|
|
51171cb8e1 | ||
|
|
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 |
@@ -0,0 +1 @@
|
||||
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
|
||||
@@ -14,3 +14,4 @@ erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
@@ -7,7 +7,3 @@ notifications:
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B03
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
+146
-3
@@ -1,3 +1,147 @@
|
||||
# v0.9.3 (2013-06-23)
|
||||
|
||||
* enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
* [Kernel] Add --warnings-as-errors to Elixir's compiler options
|
||||
* [Kernel] Print warnings to stderr
|
||||
* [Kernel] Warn on undefined module attributes
|
||||
* [Kernel] Emit warning for `x in []` in guards
|
||||
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
|
||||
* [Kernel] `quote` now allows a binding as an option
|
||||
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
|
||||
* [Mix] Implement `Mix.Version` for basic versioniong semantics
|
||||
* [Mix] Support creation and installation of archives (.ez files)
|
||||
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
|
||||
* [Record] Allow types to be given to `defrecord` and `defrecordp`
|
||||
|
||||
* bug fix
|
||||
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
|
||||
* [Kernel] Improve error messages on default clauses clash
|
||||
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
|
||||
* [Kernel] Improve error messages when default clauses from different definitions collide
|
||||
* [Kernel] `^x` variables should always refer to the value before the expression
|
||||
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
|
||||
* [Mix] Mix now properly evaluates rebar scripts
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
|
||||
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
|
||||
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed. Check `elixir_parser.yrl` for more information.
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
# 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
|
||||
@@ -73,7 +217,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
|
||||
@@ -100,7 +244,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`
|
||||
@@ -129,7 +273,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
|
||||
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,7 +44,7 @@ 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) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@@ -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
|
||||
|
||||
@@ -91,15 +92,24 @@ clean:
|
||||
rm -rf lib/elixir/src/*_parser.erl
|
||||
rm -rf lib/elixir/test/ebin
|
||||
|
||||
clean_exbeam:
|
||||
@ rm -f lib/*/ebin/Elixir.*.beam
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
docs: compile
|
||||
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 "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir/blob/master/%{path}#L%{line}"
|
||||
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
|
||||
|
||||
../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
|
||||
@@ -124,11 +134,11 @@ test_erlang: compile
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_doctest 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_doctest: compile
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
|
||||
@ 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,43 +14,42 @@ 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.
|
||||
|
||||
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
|
||||
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `.dev` extension from VERSION
|
||||
1) Remove `+dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
@@ -18,7 +18,7 @@ This document simply outlines the release process:
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) After release, bump versions and add `.dev` back
|
||||
9) After release, bump versions and add `+dev` back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+23
-7
@@ -2,7 +2,7 @@
|
||||
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version
|
||||
-v Prints version and exit
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
@@ -13,6 +13,8 @@ if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; 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
|
||||
--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
|
||||
|
||||
@@ -24,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
|
||||
@@ -39,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)
|
||||
-e|-r|-pr|-pa|-pz|--remsh)
|
||||
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}"
|
||||
@@ -67,12 +74,21 @@ done
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ "$ERL_LIBS" = "" ]
|
||||
then
|
||||
ERL_LIBS="$SCRIPT_PATH/../lib"
|
||||
else
|
||||
PATHSEP=":"
|
||||
if [ "$OSTYPE" = "msys" ]; then PATHSEP=";" ; fi
|
||||
ERL_LIBS="$ERL_LIBS$PATHSEP$SCRIPT_PATH/../lib"
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS "$ERL_LIBS" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec erl -env ERL_LIBS "$ERL_LIBS" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
fi
|
||||
|
||||
+102
-12
@@ -1,25 +1,115 @@
|
||||
@echo off
|
||||
SETLOCAL enabledelayedexpansion
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
if %argc%== 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
goto parseopts
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r command Requires the given file/pattern (*)
|
||||
echo -pr command Requires the given file/pattern in parallel (*)
|
||||
echo -pa path Prepend the given path to Erlang code path (*)
|
||||
echo -pz path Append the given path to Erlang code path (*)
|
||||
echo --app app Start the given app and its dependencies (*)
|
||||
echo --no-halt Do not halt the Erlang VM after execution
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -pr file Requires the given files/patterns in parallel (*)
|
||||
echo -pa path Prepends the given path to Erlang code path (*)
|
||||
echo -pz path Appends the given path to Erlang code path (*)
|
||||
echo --app app Start the given app and its dependencies (*)
|
||||
echo --erl switches Switches to be passed down to erlang (*)
|
||||
echo --name name Makes and assigns a name to the distributed node
|
||||
echo --sname name Makes and assigns a short name to the distributed node
|
||||
echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
|
||||
goto :EOF
|
||||
|
||||
:parseopts
|
||||
|
||||
rem Parameters for Erlang
|
||||
set parsErlang=
|
||||
|
||||
rem Make sure we keep a copy of all parameters
|
||||
set allPars=%*
|
||||
|
||||
rem Get the original path name from the batch file
|
||||
set originPath=%~dp0
|
||||
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par=%1
|
||||
shift
|
||||
if "%par%"=="" (
|
||||
rem if no parameters defined
|
||||
goto :run
|
||||
)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
|
||||
set parsErlang=%parsErlang% -detached
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
|
||||
set parsErlang=%parsErlang% -hidden
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
|
||||
set parsErlang=%parsErlang% -setcookie %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
|
||||
set parsErlang=%parsErlang% -sname %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
|
||||
set parsErlang=%parsErlang% -name %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
|
||||
set beforeExtra=%beforeExtra% %~1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
rem ******* elixir parameters **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
:run
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
+3
-2
@@ -6,6 +6,7 @@ if [ $# -eq 0 ]; then
|
||||
--no-docs Do not attach documentation to compiled modules
|
||||
--no-debug-info Do not attach debug info to compiled modules
|
||||
--ignore-module-conflict
|
||||
--warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
@@ -14,7 +15,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 +25,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir +compile "$@"
|
||||
|
||||
+2
-1
@@ -13,9 +13,10 @@ echo -o The directory to output compiled files
|
||||
echo --no-docs Do not attach documentation to compiled modules
|
||||
echo --no-debug-info Do not attach debug info to compiled modules
|
||||
echo --ignore-module-conflict
|
||||
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
echo.
|
||||
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 %*
|
||||
|
||||
@@ -13,7 +13,12 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); 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
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--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
|
||||
@@ -23,7 +28,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
|
||||
@@ -33,4 +38,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
|
||||
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" %*
|
||||
@@ -75,7 +75,7 @@ defmodule EEx.Compiler do
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp is_empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
end
|
||||
|
||||
defp is_empty?({ :<>, _, [left, right] }) do
|
||||
|
||||
@@ -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
|
||||
@@ -20,8 +20,8 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
@@ -63,7 +63,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%,?>|t], line, buffer) do
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
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
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.SmartEngineTest do
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1,2,3] do %><%= x %>\n<% end %>"
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
|
||||
@@ -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
|
||||
@@ -21,7 +21,7 @@ defmodule EEx.TokenizerTest do
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" },{ :expr, 2, '=', ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, '=', ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -101,7 +101,7 @@ defmodule EExTest do
|
||||
end
|
||||
|
||||
test "evaluates with require code" do
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1,2,3], \",\" %>"
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>"
|
||||
end
|
||||
|
||||
test "evaluates with end of token" do
|
||||
@@ -242,7 +242,7 @@ Number 3
|
||||
"""
|
||||
|
||||
string = """
|
||||
<%= Enum.map [1,2,3], fn x -> %>
|
||||
<%= Enum.map [1, 2, 3], fn x -> %>
|
||||
Number <%= x %>
|
||||
<% end %>
|
||||
"""
|
||||
@@ -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,19 +11,18 @@
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
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
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
temp_vars=[], %% a dict of all variables defined in a particular assign
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
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
|
||||
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
|
||||
@@ -36,21 +35,42 @@
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
vars_hygiene=nil,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
imports_hygiene=true,
|
||||
unquote=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)).
|
||||
|
||||
%% 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 == $').
|
||||
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
|
||||
|
||||
%% 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.
|
||||
@@ -53,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
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule Behaviour do
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } -> :ok
|
||||
:error ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
@@ -113,4 +113,4 @@ defmodule Behaviour do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,14 +51,22 @@ defimpl Binary.Chars, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> to_binary 'foo'
|
||||
iex> to_binary('foo')
|
||||
"foo"
|
||||
iex> to_binary ["foo", 'bar']
|
||||
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
|
||||
|
||||
@@ -66,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
|
||||
@@ -146,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)
|
||||
@@ -154,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
|
||||
|
||||
@@ -276,7 +274,7 @@ defimpl Binary.Inspect, for: List do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
escape(:unicode.characters_to_binary(thing), ?')
|
||||
Keyword.keyword?(thing) ->
|
||||
keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
@@ -285,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
|
||||
|
||||
@@ -295,6 +293,16 @@ defimpl Binary.Inspect, for: List do
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
@@ -306,7 +314,7 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1,2,3})
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1,2,3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
@@ -317,33 +325,17 @@ 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
|
||||
|
||||
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
record_join(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
@@ -367,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
|
||||
|
||||
@@ -392,12 +384,19 @@ defimpl Binary.Inspect, for: Number do
|
||||
|
||||
"""
|
||||
|
||||
@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
|
||||
|
||||
@@ -413,11 +412,11 @@ defimpl Binary.Inspect, for: Regex do
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ defmodule Bitwise do
|
||||
your module:
|
||||
|
||||
iex> use Bitwise
|
||||
...> bnot 1
|
||||
iex> bnot 1
|
||||
-2
|
||||
|
||||
iex> use Bitwise
|
||||
...> 1 &&& 1
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
You can select to include only or skip operators by passing options:
|
||||
@@ -127,4 +125,4 @@ defmodule Bitwise do
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+120
-50
@@ -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:
|
||||
|
||||
@@ -72,11 +72,13 @@ defmodule Code do
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity
|
||||
and the second a list of imported function names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity
|
||||
and the second a list of imported macro names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
|
||||
Notice that setting any ov the values above overrides Elixir default
|
||||
Notice that setting any of the values above overrides Elixir default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
automatically required the `Kernel` module, in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros as `if`, `case`,
|
||||
@@ -84,63 +86,116 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
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:
|
||||
|
||||
iex> Code.eval "a + b", [a: 1, b: 2], __ENV__
|
||||
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
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval(string, binding, opts) do
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
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
|
||||
|
||||
iex> contents = quote hygiene: [vars: false], do: a + b
|
||||
...> Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
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:
|
||||
|
||||
iex> contents = quote hygiene: [vars: false], do: a + b
|
||||
...> Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
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 // [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(quoted, binding, env.to_keywords)
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) do
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_quoted(quoted, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
defp validate_eval_opts(opts) do
|
||||
if f = opts[:functions], do: validate_imports(:functions, f)
|
||||
if m = opts[:macros], do: validate_imports(:macros, m)
|
||||
if a = opts[:aliases], do: validate_aliases(:aliases, a)
|
||||
if r = opts[:requires], do: validate_requires(:requires, r)
|
||||
end
|
||||
|
||||
defp validate_requires(kind, requires) do
|
||||
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_aliases(kind, aliases) do
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
|
||||
is_atom(k) and is_atom(v)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_imports(kind, imports) do
|
||||
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
|
||||
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
|
||||
is_atom(name) and is_integer(arity)
|
||||
end)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns `{ :ok, ast }`
|
||||
Converts the given string to quoted form. It returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
@@ -153,42 +208,53 @@ defmodule Code do
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_string/1
|
||||
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_ast(string, opts // []) do
|
||||
def string_to_quoted(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast!(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted!(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns the ast if it succeeds,
|
||||
Converts the given string to quoted form. It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a TokenMissingError
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
SyntaxError otherwise.
|
||||
|
||||
Check `Code.string_to_ast/2` for options information.
|
||||
Check `Code.string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_ast!(string, opts // []) do
|
||||
def string_to_quoted!(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
|
||||
unpack_ast(line, res)
|
||||
unpack_quote(line, res)
|
||||
end
|
||||
|
||||
defp unpack_ast(_line, []), do: nil
|
||||
defp unpack_ast(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
|
||||
defp unpack_quote(_line, []), do: nil
|
||||
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_quote(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 +271,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)
|
||||
@@ -234,7 +300,7 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Check compiler_options/1 for more information.
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
:elixir_code_server.call :compiler_options
|
||||
@@ -246,13 +312,17 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* docs - when true, retain documentation in the compiled module.
|
||||
True by default;
|
||||
* debug_info - when true, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
* ignore_module_conflict - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
* `:docs` - when true, retain documentation in the compiled module.
|
||||
True by default;
|
||||
|
||||
* `:debug_info` - when true, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
|
||||
* `:ignore_module_conflict` - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
|
||||
+212
-55
@@ -25,15 +25,15 @@ defmodule Dict do
|
||||
protocol:
|
||||
|
||||
iex> dict = HashDict.new
|
||||
...> dict = Dict.put dict, :hello, :world
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enum.Iterator` protocol, allowing one to write:
|
||||
And also the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each dict, fn { k, v } ->
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end
|
||||
end)
|
||||
|
||||
"""
|
||||
|
||||
@@ -41,20 +41,26 @@ defmodule Dict do
|
||||
|
||||
@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
|
||||
@@ -66,7 +72,7 @@ defmodule Dict do
|
||||
is_tuple(unquote(dict)) ->
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
List.Dict
|
||||
ListDict
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -78,8 +84,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Enum.sort Dict.keys d
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -93,8 +99,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Enum.sort Dict.values d
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -108,8 +114,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Dict.size d
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -123,12 +129,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.has_key?(d, :a)
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.has_key?(d, :b)
|
||||
iex> Dict.has_key?(d, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -143,16 +147,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :b
|
||||
iex> Dict.get(d, :b)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :b, 3
|
||||
iex> Dict.get(d, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -160,23 +160,45 @@ defmodule Dict do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) do
|
||||
target(dict).get!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the `{ :ok, value }` associated with `key` in `dict`.
|
||||
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
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1])
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get! d, :b
|
||||
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 """
|
||||
@@ -185,9 +207,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.put d, :a, 3
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -201,9 +223,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.put_new d, :a, 3
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -218,12 +240,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.delete d, :a
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new [b: 2]
|
||||
iex> d = HashDict.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
@@ -239,9 +261,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = HashDict.new [a: 1, b: 2]
|
||||
...> d2 = HashDict.new [a: 3, d: 4]
|
||||
...> d = Dict.merge d1, d2
|
||||
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]
|
||||
|
||||
@@ -258,11 +280,11 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = HashDict.new [a: 1, b: 2]
|
||||
...> d2 = HashDict.new [a: 3, d: 4]
|
||||
...> d = Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
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
|
||||
...> end)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
@@ -272,15 +294,42 @@ 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
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.update d, :a, fn val -> -val end
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :a, fn(val) -> -val end)
|
||||
...> Dict.get(d, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -296,9 +345,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.update d, :c, 3, fn val -> -val end
|
||||
...> Dict.get d, :c
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -307,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 given `keys` are 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`.
|
||||
"""
|
||||
@@ -315,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.
|
||||
|
||||
+614
-781
File diff suppressed because it is too large
Load Diff
+139
-129
@@ -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
|
||||
|
||||
@@ -94,9 +192,7 @@ defmodule Exception do
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil)
|
||||
|
||||
def format_stacktrace(trace) do
|
||||
def format_stacktrace(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
@@ -105,31 +201,31 @@ 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
|
||||
|
||||
@doc """
|
||||
Prints the current stacktrace to standard output.
|
||||
|
||||
A stacktrace must be given as argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
Notice that doing to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def print_stacktrace(trace // nil) do
|
||||
IO.write format_stacktrace(trace)
|
||||
def format_caller(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
if entry = Enum.at(trace, 1) do
|
||||
format_stacktrace_entry(entry)
|
||||
else
|
||||
"nofile:0: "
|
||||
end
|
||||
end
|
||||
|
||||
## 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
|
||||
@@ -184,101 +280,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"
|
||||
|
||||
+213
-171
@@ -38,10 +38,12 @@ defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil] do
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} from #{source exception} to #{destination exception}: #{formatted}"
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,28 +56,30 @@ end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains function to manipulate files.
|
||||
Many of the functions that interact with the filesystem
|
||||
have their naming based on its UNIX variants. For
|
||||
example, deleting a file is done with `File.rm`.
|
||||
Getting its stats with `File.stat`.
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
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
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `File.open/2`,
|
||||
`File.copy/3` and others. This module also provides higher
|
||||
level functions that works with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `File.cp/3` and remove files and directories recursively
|
||||
via `File.rm_rf/2`
|
||||
|
||||
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.
|
||||
|
||||
Most of the functions in this module return `:ok`
|
||||
or `{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
otherwise. Those function are also followed by
|
||||
a variant that ends with `!` which returns the
|
||||
result (without the `{ :ok, result }` tuple) in
|
||||
case of success or raises an exception in case it
|
||||
fails. For example:
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
otherwise. Those function are also followed by a variant
|
||||
that ends with `!` which returns the result (without the
|
||||
`{ :ok, result }` tuple) in case of success or raises an
|
||||
exception in case it fails. For example:
|
||||
|
||||
File.read("hello.txt")
|
||||
#=> { :ok, "World" }
|
||||
@@ -89,16 +93,10 @@ defmodule File do
|
||||
File.read!("invalid.txt")
|
||||
#=> raises File.Error
|
||||
|
||||
In general, a developer should use the former in case
|
||||
he wants to react in the fie does not exist. The latter
|
||||
should be used when the developer expects his software
|
||||
to fail in case the file cannot be read (i.e. it is
|
||||
literally an exception).
|
||||
|
||||
Finally, the functions in this module accept either
|
||||
a char lists or a binary. When manipulating paths, a char
|
||||
list is returned if one is given as argument. However,
|
||||
when reading files, binaries are always returned.
|
||||
In general, a developer should use the former in case he wants
|
||||
to react if the file does not exist. The latter should be used
|
||||
when the developer expects his software to fail in case the
|
||||
file cannot be read (i.e. it is literally an exception).
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
@@ -125,7 +123,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument exists.
|
||||
Returns true if the given path exists.
|
||||
It can be regular file, directory, socket,
|
||||
symbolic link, named pipe or device file.
|
||||
|
||||
@@ -233,7 +231,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{ :ok, info }` tuple, where info is as a
|
||||
returns a `{ :ok, info }` tuple, where info is a
|
||||
`File.Info` record. Retuns `{ :error, reason }` with
|
||||
the same reasons as `File.read` if a failure occurs.
|
||||
|
||||
@@ -310,17 +308,24 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents of `source` to `destination`. Both
|
||||
parameters can be a filename or an io device opened with `File.open`.
|
||||
`bytes_count` specifies the number of bytes to count, the default
|
||||
being `:infinity`.
|
||||
Copies the contents of `source` to `destination`.
|
||||
|
||||
If file `destination` already exists, it is overriden
|
||||
Both parameters can be a filename or an io device opened
|
||||
with `File.open/2`. `bytes_count` specifies the number of
|
||||
bytes to copy, the default being `:infinity`.
|
||||
|
||||
If file `destination` already exists, it is overwritten
|
||||
by the contents in `source`.
|
||||
|
||||
Returns `{ :ok, bytes_copied }` if successful,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
Compared to the `File.cp/3`, this function is more low-level,
|
||||
allowing a copy from device to device limited by a number of
|
||||
bytes. On the other hand, `File.cp/3` performs more extensive
|
||||
checks on both source and destination and it also preserves
|
||||
the file mode after copy.
|
||||
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/2`.
|
||||
"""
|
||||
@@ -329,7 +334,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `copy/3` but raises an File.CopyError if it fails.
|
||||
The same as `copy/3` but raises an `File.CopyError` if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
"""
|
||||
def copy!(source, destination, bytes_count // :infinity) do
|
||||
@@ -342,40 +347,36 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination`.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
if `source` and `destination` are a file or a directory.
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
|
||||
If both are files, it simply copies `source` to
|
||||
`destination`. However, if `destination` is a directory,
|
||||
it copies the contents of `source` to `destination/source`
|
||||
recursively.
|
||||
Similar to the command `cp` in Unix systems, this function
|
||||
behaves differently depending if `destination` is a directory
|
||||
or not. In particular, if `destination` is a directory, it
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overriden,
|
||||
false otherwise. It defaults to return true.
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return true if the existing file
|
||||
should be overwritten, false otherwise. It defaults to return true.
|
||||
|
||||
It returns `:ok` in case of success, returns
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `File.copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
if dir?(source) do
|
||||
{ :error, :eisdir }
|
||||
else
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
{ :error, _ } = error -> error
|
||||
_ -> :ok
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
{ :error, reason, _ } -> { :error, reason }
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -388,7 +389,8 @@ defmodule File do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -405,7 +407,7 @@ defmodule File do
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overriden,
|
||||
true if the existing file should be overwritten,
|
||||
false otherwise. It defaults to return true.
|
||||
|
||||
If a directory already exists in the destination
|
||||
@@ -434,7 +436,7 @@ defmodule File do
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r "samples/.", "tmp", fn(source, destination) ->
|
||||
IO.gets("Overriding #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -448,7 +450,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
case do_cp_r(source, output, callback, []) do
|
||||
{ :error, _ } = error -> error
|
||||
{ :error, _, _ } = error -> error
|
||||
res -> { :ok, res }
|
||||
end
|
||||
end
|
||||
@@ -460,19 +462,26 @@ defmodule File do
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
case cp_r(source, destination, callback) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
{ :error, reason, file } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination),
|
||||
on: file
|
||||
end
|
||||
end
|
||||
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } ->
|
||||
do_cp_link(link, src, dest, callback, acc)
|
||||
_ ->
|
||||
case :elixir.file_type(src) do
|
||||
{ :ok, :regular } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{ :ok, :symlink } ->
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
{ :ok, :directory } ->
|
||||
case F.list_dir(src) do
|
||||
{ :ok, files } ->
|
||||
case mkdir(dest) do
|
||||
@@ -480,12 +489,12 @@ defmodule File do
|
||||
Enum.reduce(files, [dest|acc], fn(x, acc) ->
|
||||
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
|
||||
end)
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
|
||||
end
|
||||
{ :error, :enotdir } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
{ :ok, _ } -> { :error, :eio, src }
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -514,12 +523,12 @@ defmodule File do
|
||||
{ :ok, _ } ->
|
||||
copy_file_mode!(src, dest)
|
||||
[dest|acc]
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -533,12 +542,12 @@ defmodule File do
|
||||
rm(dest)
|
||||
case F.make_symlink(link, dest) do
|
||||
:ok -> [dest|acc]
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -638,7 +647,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Remove files and directories recursively at the given `path`.
|
||||
Symlinks are not followed but simply removed, non existing
|
||||
Symlinks are not followed but simply removed, non-existing
|
||||
files are simply ignored (i.e. doesn't make this function fail).
|
||||
|
||||
Returns `{ :ok, files_and_directories }` with all files and
|
||||
@@ -671,18 +680,18 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> res
|
||||
reason -> reason
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
reason -> reason
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enotdir } ->
|
||||
{ :error, reason } when reason in [:enotdir, :eio] ->
|
||||
case rm(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> entry
|
||||
reason -> reason
|
||||
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enoent } -> entry
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -704,8 +713,10 @@ defmodule File do
|
||||
def rm_rf!(path) do
|
||||
case rm_rf(path) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
|
||||
{ :error, reason, _ } ->
|
||||
raise File.Error, reason: reason,
|
||||
action: "remove files and directories recursively from",
|
||||
path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -753,14 +764,14 @@ defmodule File do
|
||||
|
||||
This function returns:
|
||||
|
||||
* { :ok, io_device } - The file has been opened in the requested mode.
|
||||
* `{ :ok, io_device }` - The file has been opened in the requested mode.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the io_device is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An io_device returned
|
||||
If any process to which the `io_device` is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An `io_device` returned
|
||||
from this call can be used as an argument to the `IO` module functions.
|
||||
|
||||
* { :error, reason } - The file could not be opened.
|
||||
* `{ :error, reason }` - The file could not be opened.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -930,11 +941,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
|
||||
@@ -944,13 +950,13 @@ defmodule File do
|
||||
passed into `Enum`. The device is iterated line
|
||||
by line, at the end of iteration the file is closed.
|
||||
|
||||
This reads the file as utf-8. CHeck out `File.biniterator`
|
||||
This reads the file as utf-8. Check out `File.biniterator`
|
||||
to handle the file as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
An example that lazily iterates a file replacing all double
|
||||
quotes per single quotes and write each line to a target file
|
||||
quotes per single quotes and writes each line to a target file
|
||||
is shown below:
|
||||
|
||||
{ :ok, device } = File.open("README.md")
|
||||
@@ -962,50 +968,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 """
|
||||
@@ -1013,50 +996,87 @@ 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`.
|
||||
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
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, biniterator(device) }
|
||||
error -> error
|
||||
def biniterator!(file, mode // []) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `biniterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
open!(file, mode) |> biniterator
|
||||
def chmod(file, mode) do
|
||||
F.change_mode(file, mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chmod!(file, mode) do
|
||||
case chmod(file, mode) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the user group given by the group id `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
def chgrp(file, gid) do
|
||||
F.change_group(file, gid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chgrp!(file, gid) do
|
||||
case chgrp(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
def chown(file, uid) do
|
||||
F.change_owner(file, uid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chown!(file, gid) do
|
||||
case chown(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -1075,4 +1095,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
|
||||
|
||||
@@ -55,28 +55,34 @@ defmodule GenEvent.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_event
|
||||
@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
|
||||
|
||||
@@ -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
|
||||
|
||||
+209
-102
@@ -33,27 +33,26 @@ defmodule HashDict do
|
||||
# set of buckets
|
||||
|
||||
# The ordered record contains a single bucket
|
||||
ordered_threshold = 8
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
expand_load = 5
|
||||
contract_load = 2
|
||||
node_bitmap = 0b111
|
||||
node_shift = 3
|
||||
node_size = 8
|
||||
node_template = :erlang.make_tuple(node_size, [])
|
||||
node_escaped = Macro.escape(node_template)
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: node_size * expand_load,
|
||||
contract_on: contract_load,
|
||||
root: node_template
|
||||
expand_on: @node_size * @expand_load,
|
||||
contract_on: @contract_load,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
@@ -73,7 +72,7 @@ defmodule HashDict do
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b,1},{:a,2}]
|
||||
HashDict.new [{:b, 1}, {:a, 2}]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@@ -153,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.
|
||||
"""
|
||||
@@ -171,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 } ->
|
||||
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, unquote(node_size)),
|
||||
expand_on: div(trie(dict, :expand_on), unquote(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 """
|
||||
@@ -216,6 +209,19 @@ defmodule HashDict do
|
||||
ordered()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two dicts are equal
|
||||
"""
|
||||
def equal?(dict1, dict2) do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
@@ -224,7 +230,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
dict_fold(dict, [], [&1|&2])
|
||||
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -264,14 +270,55 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
end
|
||||
end
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
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
|
||||
@@ -279,12 +326,12 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, unquote(node_size))
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: unquote(ordered_threshold), bucket: bucket), key, value) do
|
||||
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
|
||||
root = node_relocate(bucket, 0)
|
||||
dict_put(trie(size: unquote(ordered_threshold), root: root), key, value)
|
||||
dict_put(trie(size: @ordered_threshold, root: root), key, value)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
|
||||
@@ -295,7 +342,7 @@ defmodule HashDict do
|
||||
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
dict = trie(dict, root: root, depth: depth + 1,
|
||||
expand_on: size * unquote(node_size), contract_on: contract_on * unquote(node_size))
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
dict_put(dict, key, value)
|
||||
end
|
||||
|
||||
@@ -305,61 +352,122 @@ 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)
|
||||
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
|
||||
defp bucket_put([{key, _}|bucket], key, { :put, value }) do
|
||||
{ [{key, value}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{key, value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key, fun.(value)}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,_}|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 }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [{key,value}], 1 }
|
||||
{ [{key, value}], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [{key,initial}], 1 }
|
||||
{ [{key, initial}], 1 }
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([{k,_}=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!([], key, value), do: [{key,value}]
|
||||
defp bucket_put!([{k, _}|_]=bucket, key, value) when k > key, do: [{key, value}|bucket]
|
||||
defp bucket_put!([{key, _}|bucket], key, value), do: [{key, value}|bucket]
|
||||
defp bucket_put!([{_, _}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [{key, value}]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([{k,_}|_] = bucket, key) when 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
|
||||
@@ -372,15 +480,15 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& unquote(node_bitmap)
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (unquote(node_shift) * n)) &&& unquote(node_bitmap)
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> unquote(node_shift)
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
## Node helpers
|
||||
@@ -412,16 +520,16 @@ defmodule HashDict do
|
||||
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 } -> { set_elem(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 } -> { set_elem(node, pos, new), -1 }
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -431,7 +539,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, unquote(node_size))
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
@@ -453,28 +561,25 @@ defmodule HashDict do
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) when depth > 1 do
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth, n), node_contract(b2, depth, n), node_contract(b3, depth, n),
|
||||
node_contract(b4, depth, n), node_contract(b5, depth, n), node_contract(b6, depth, n),
|
||||
node_contract(b7, depth, n), node_contract(b8, depth, n) }
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
|
||||
unquote(node_escaped)
|
||||
|> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
|
||||
acc
|
||||
|> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
end
|
||||
defp each_contract([{k, _v}=e|acc], [{key, _value}|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // unquote(node_escaped), bucket, n) do
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
@@ -482,9 +587,11 @@ defmodule HashDict do
|
||||
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
|
||||
@@ -495,6 +602,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
|
||||
|
||||
+33
-3
@@ -125,6 +125,17 @@ defmodule IO do
|
||||
:io.put_chars erl_dev, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given arguments to stderr as a warning.
|
||||
|
||||
If `--warnings-as-errors` is true and this function is
|
||||
called during compilation, compilation will fail.
|
||||
"""
|
||||
def warn(item) do
|
||||
:elixir_code_server.cast(:register_warning)
|
||||
:io.put_chars :standard_error, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. A set of options can be given.
|
||||
@@ -142,12 +153,15 @@ defmodule IO do
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
def inspect(device, item, opts) do
|
||||
puts device, Binary.Inspect.inspect(item, opts)
|
||||
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.
|
||||
|
||||
@@ -157,7 +171,23 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def getb(device // group_leader(), 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
|
||||
|
||||
|
||||
+23
-42
@@ -2,14 +2,12 @@ defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
binary_name = atom_to_binary(name)
|
||||
|
||||
quote do
|
||||
quote binding: [name: name, code: code] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}m"
|
||||
end
|
||||
|
||||
defp escape_sequence(<< unquote(binary_name), rest :: binary >>) do
|
||||
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
end
|
||||
@@ -22,9 +20,6 @@ defmodule IO.ANSI do
|
||||
(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.
|
||||
|
||||
Please be aware that in Erlang/OTP versions prior to R16, you will not
|
||||
be able to render ANSI escape sequences in iex or erlang shell
|
||||
"""
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
@@ -34,23 +29,11 @@ defmodule IO.ANSI do
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
|
||||
Please note that invoked while in shell (iex) in Erlang/OTP
|
||||
prior to R16, terminal?/0 will always return false because
|
||||
Erlang shell did not support ANSI escape sequences up until
|
||||
R16.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
if :erlang.system_info(:otp_release) < 'R16' and
|
||||
Process.whereis(:user) != device do
|
||||
# Shell prior to R16 doesn't support ANSI escape
|
||||
# sequences
|
||||
false
|
||||
else
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -89,11 +72,9 @@ defmodule IO.ANSI do
|
||||
@doc "Sets primary (default) font"
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
|
||||
Module.eval_quoted __ENV__, (quote do
|
||||
@doc "Sets alternative font #{unquote(font_n)}"
|
||||
defsequence(unquote(:"font_#{font_n}"), unquote(font_n + 10))
|
||||
end)
|
||||
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"
|
||||
@@ -112,13 +93,11 @@ defmodule IO.ANSI do
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
Module.eval_quoted __ENV__, (quote do
|
||||
@doc "Sets foreground color to #{unquote(color)}"
|
||||
defsequence unquote(color), unquote(code + 30)
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@doc "Sets background color to #{unquote(color)}"
|
||||
defsequence unquote(:"#{color}_background"), unquote(code + 40)
|
||||
end)
|
||||
@doc "Sets background color to #{color}"
|
||||
defsequence :"#{color}_background", code + 40
|
||||
end
|
||||
|
||||
@doc "Default text color"
|
||||
@@ -153,13 +132,13 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
It will also force a %{reset} to get appended to every string. If you don't
|
||||
want this behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
It will also append a %{reset} to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/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.
|
||||
@@ -168,7 +147,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
@@ -182,10 +161,10 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
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.
|
||||
@@ -194,8 +173,10 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
|
||||
+508
-449
File diff suppressed because it is too large
Load Diff
+163
-66
@@ -3,19 +3,26 @@ 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)
|
||||
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, IO.puts(:stderr, &1))
|
||||
System.halt(1)
|
||||
end
|
||||
end, config.halt
|
||||
end
|
||||
|
||||
@@ -39,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) ->
|
||||
@@ -52,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)
|
||||
@@ -69,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
|
||||
|
||||
@@ -83,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|_], _ } 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
|
||||
@@ -99,44 +102,42 @@ 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(["--app",h|t], config) do
|
||||
process_shared t, config.update_commands [{:app,h}|&1]
|
||||
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(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands [{:app, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, config.halt(false)
|
||||
end
|
||||
|
||||
defp process_shared(["-e",h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval,h}|&1]
|
||||
defp process_shared(["-e", h|t], config) 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
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands [{:require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require, h}|&1]
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -150,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(["-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
|
||||
defp process_argv(["+iex"|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -169,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
|
||||
|
||||
@@ -183,20 +182,24 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_compiler(["-o",h|t], config) do
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, config.output(h)
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
@@ -204,51 +207,145 @@ 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
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands([{:compile,config.compile}|&1]), [] }
|
||||
{ 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)
|
||||
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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
defexception Kernel.CompilationError, message: "compilation failed"
|
||||
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias :orddict, as: OrdDict
|
||||
|
||||
@@ -15,6 +17,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
the current file stops being compiled until the dependency is
|
||||
resolved.
|
||||
|
||||
If there is any error during compilation or if warnings_as_errors
|
||||
is set to true and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
A callback that is invoked every time a file is compiled
|
||||
with its name can be optionally given as argument.
|
||||
"""
|
||||
@@ -32,8 +38,14 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
:elixir_code_server.cast(:reset_warnings)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
|
||||
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
case :elixir_code_server.call(:compilation_status) do
|
||||
:ok -> result
|
||||
:error -> raise Kernel.CompilationError, [], []
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
@@ -64,7 +76,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], schedulers, result)
|
||||
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
@@ -138,4 +150,4 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule Kernel.RecordRewriter 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
|
||||
@@ -350,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
|
||||
@@ -363,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)
|
||||
@@ -372,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
|
||||
|
||||
@@ -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 ]
|
||||
|
||||
"""
|
||||
@@ -247,7 +247,7 @@ defmodule Kernel.SpecialForms do
|
||||
for example:
|
||||
|
||||
iex> import List
|
||||
...> flatten([1,[2],3])
|
||||
...> flatten([1, [2], 3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
@@ -305,6 +305,19 @@ defmodule Kernel.SpecialForms do
|
||||
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
|
||||
@@ -335,6 +348,11 @@ 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.
|
||||
|
||||
@@ -346,18 +364,17 @@ defmodule Kernel.SpecialForms do
|
||||
## 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);
|
||||
@@ -370,6 +387,9 @@ defmodule Kernel.SpecialForms do
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the context resolution happens at;
|
||||
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
|
||||
unquote is automatically disabled;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -379,21 +399,41 @@ defmodule Kernel.SpecialForms do
|
||||
:sum #=> Atoms
|
||||
1 #=> Integers
|
||||
2.0 #=> Floats
|
||||
[1,2] #=> Lists
|
||||
[1, 2] #=> Lists
|
||||
"binaries" #=> Binaries
|
||||
{key, value} #=> Tuple with two elements
|
||||
|
||||
## Hygiene
|
||||
## Hygiene and context
|
||||
|
||||
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.
|
||||
|
||||
This means aliases and imports defined inside the quoted refer
|
||||
to the context that defined the macro and not the context
|
||||
where the macro is expanded.
|
||||
For this mechanism to work, every quoted code is attached
|
||||
to a context. Consider the following example:
|
||||
|
||||
Furthermore, variables inside quote are also hygienic: a
|
||||
variable defined in a macro cannot affect the variables where
|
||||
the macro is expanded.
|
||||
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
|
||||
|
||||
@@ -591,6 +631,79 @@ defmodule Kernel.SpecialForms do
|
||||
code as if it was defined inside `GenServer.Behaviour` file, in
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
Elixir quote/unquote mechanisms provides a functionality called
|
||||
unquote fragments. Unquote fragments provide an easy to generate
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
In the example above, we have generated the function `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
We can invoke this macro as:
|
||||
|
||||
defkv [foo: 1, bar: 2]
|
||||
|
||||
However, we can't invoke it as follows:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
defkv kv
|
||||
|
||||
This is because the macro is expecting its arguments to be a
|
||||
key-value at **compilation** time. Since in the example above
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practive, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
Enum.each unquote(kv), fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
If you try to run our new macro, you will notice it won't
|
||||
even compile, complaining that the variables `k` and `v`
|
||||
do not exist. This is because of the ambiguity: `unquote(k)`
|
||||
can either be an unquote fragment, as previously, or a regular
|
||||
unquote as in `unquote(kv)`.
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
`kv` representation into the tree. That's when the `:binding`
|
||||
option comes to the rescue. By using `:binding`, we can
|
||||
automatically disable unquoting while still injecting the
|
||||
desired variables into the tree:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote binding: [kv: kv] do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
In fact, the `:binding` option is recommended every time one
|
||||
desires to inject a value into the quote.
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@@ -605,6 +718,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
|
||||
If the context is false, it is not stored in any particular
|
||||
context and the variable is not shared in between clauses.
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
@@ -616,10 +733,50 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro alias!(alias)
|
||||
|
||||
@doc """
|
||||
Unquotes the given expression from inside a macro.
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
value = 13
|
||||
quote do: sum(1, value, 3)
|
||||
|
||||
Which would then return:
|
||||
|
||||
{ :sum, [], [1, { :value, [], quoted }, 3] }
|
||||
|
||||
Which is not the expected result. For this, we use unquote:
|
||||
|
||||
value = 13
|
||||
quote do: sum(1, unquote(value), 3)
|
||||
#=> { :sum, [], [1, 13, 3] }
|
||||
|
||||
"""
|
||||
name = :unquote
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
Unquotes the given list expanding its arguments. Similar
|
||||
to unquote.
|
||||
|
||||
## Examples
|
||||
|
||||
values = [2, 3, 4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
"""
|
||||
name = :unquote_splicing
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
@@ -627,33 +784,33 @@ defmodule Kernel.SpecialForms do
|
||||
to loop lists and bitstrings:
|
||||
|
||||
# A list generator:
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
iex> lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
iex> list = [1,2,3,4]
|
||||
iex> list = [1, 2, 3, 4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A comprehension with two generators
|
||||
iex> lc x inlist [1,2], y inlist [2,3], do: x*y
|
||||
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
|
||||
iex> lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
|
||||
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> 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}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
"""
|
||||
@@ -677,7 +834,7 @@ defmodule Kernel.SpecialForms do
|
||||
and should not be invoked directly:
|
||||
|
||||
iex> quote do: (1; 2; 3)
|
||||
{ :__block__, [], [1,2,3] }
|
||||
{ :__block__, [], [1, 2, 3] }
|
||||
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
@@ -689,7 +846,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.
|
||||
"""
|
||||
@@ -732,4 +889,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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
Holds macros and functions for working with typespecs.
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
@@ -8,20 +8,19 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
For more details, see documentation for deftype, deftypep and defopaque in
|
||||
Kernel.Typespec
|
||||
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
|
||||
below.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
|
||||
For more details, see documentation for defspec and defcallback in
|
||||
Kernel.Typespec
|
||||
For more details, see documentation for `defspec` and `defcallback` below.
|
||||
|
||||
## Types
|
||||
|
||||
@@ -30,9 +29,10 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported: `list(integer())` and so are remote types: `Enum.t`.
|
||||
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
|
||||
|
||||
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
|
||||
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
|
||||
well.
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
|
||||
@@ -46,26 +46,20 @@ defmodule Kernel.Typespec do
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
|
||||
### Functions
|
||||
### Anonymous functions
|
||||
|
||||
Any function:
|
||||
Any anonymous function:
|
||||
|
||||
(fun(...) -> any)
|
||||
or
|
||||
((...) -> any)
|
||||
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
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
@@ -87,7 +81,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible to handle the attribute @type.
|
||||
This macro is the one responsible for handling the attribute `@type`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -102,7 +96,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible to handle the attribute @opaque.
|
||||
This macro is the one responsible for handling the attribute `@opaque`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -117,7 +111,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible to handle the attribute @typep.
|
||||
This macro is the one responsible for handling the attribute `@typep`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -132,7 +126,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible to handle the attribute @spec.
|
||||
This macro is the one responsible for handling the attribute `@spec`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -147,7 +141,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible to handle the attribute @callback.
|
||||
This macro is the one responsible for handling the attribute `@callback`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -194,7 +188,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given type
|
||||
Returns `true` if the current module defines a given type
|
||||
(private, opaque or not). This function is only available
|
||||
for modules being compiled.
|
||||
"""
|
||||
@@ -205,7 +199,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given spec.
|
||||
Returns `true` if the current module defines a given spec.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
@@ -214,7 +208,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a callback.
|
||||
Returns `true` if the current module defines a callback.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
@@ -225,7 +219,7 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
|
||||
end
|
||||
@@ -262,13 +256,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the beam.
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
case abstract_code(module) do
|
||||
@@ -279,7 +273,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
|
||||
@@ -289,26 +283,26 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all specs available from the beam.
|
||||
Returns all specs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all callbacks available from the beam.
|
||||
Returns all callbacks available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
@@ -333,9 +327,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,14 +344,13 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, {name, _meta, args} = type, caller) when
|
||||
is_atom(name) and
|
||||
not is_list(args) 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)
|
||||
type_spec = Macro.to_string(other)
|
||||
raise ArgumentError, message: "invalid type specification #{type_spec}"
|
||||
end
|
||||
|
||||
@@ -398,7 +391,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
def defspec(_type, other, _caller) do
|
||||
spec = Macro.to_binary(other)
|
||||
spec = Macro.to_string(other)
|
||||
raise ArgumentError, message: "invalid function type specification #{spec}"
|
||||
end
|
||||
|
||||
@@ -423,6 +416,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
|
||||
@@ -441,17 +445,17 @@ defmodule Kernel.Typespec do
|
||||
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :"->", [line: line], [{args, typespec_to_ast(result)}] }
|
||||
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :"->", [line: line], [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
|
||||
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type,line,:any, []} ] })
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
|
||||
@@ -465,16 +469,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
var =
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
{ 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
|
||||
@@ -483,7 +487,6 @@ defmodule Kernel.Typespec 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)] }
|
||||
@@ -524,14 +527,14 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
|
||||
exprs = :lists.reverse(collect_union(exprs))
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
|
||||
# Handle binaries
|
||||
defp typespec({:<<>>, meta, []}, _,_) do
|
||||
defp typespec({:<<>>, meta, []}, _, _) do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
|
||||
end
|
||||
|
||||
@@ -559,12 +562,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, return}]}, vars, caller)
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
IO.warn "(fun(...) -> ...) in typespecs is deprecated, please drop fun() and " <>
|
||||
"write (... -> ...) instead. #{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:->, meta, [{arguments, return}]}, vars, caller) when is_list(arguments) do
|
||||
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
|
||||
args = fn_args(meta, arguments, return, vars, caller)
|
||||
{ :type, line(meta), :fun, args }
|
||||
end
|
||||
@@ -610,14 +614,13 @@ defmodule Kernel.Typespec do
|
||||
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)
|
||||
@@ -666,8 +669,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
|
||||
@@ -685,9 +691,14 @@ 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,[]}] -> []
|
||||
[{:type, _, :any}, {:type, _, :any, []}] -> []
|
||||
x -> x
|
||||
end
|
||||
end
|
||||
@@ -704,4 +715,19 @@ defmodule Kernel.Typespec do
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
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
|
||||
|
||||
+66
-29
@@ -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,7 +56,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new [{:b,1},{:a,2}]
|
||||
iex> Keyword.new([{:b, 1}, {:a, 2}])
|
||||
[a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@@ -77,7 +74,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort Keyword.new [:a, :b], fn x -> {x,x} end
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@@ -100,11 +97,11 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get [a: 1], :a
|
||||
iex> Keyword.get([a: 1], :a)
|
||||
1
|
||||
iex> Keyword.get [a: 1], :b
|
||||
iex> Keyword.get([a: 1], :b)
|
||||
nil
|
||||
iex> Keyword.get [a: 1], :b, 3
|
||||
iex> Keyword.get([a: 1], :b, 3)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -118,31 +115,53 @@ 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
|
||||
|
||||
iex> Keyword.get! [a: 1], :a
|
||||
iex> Keyword.fetch!([a: 1], :a)
|
||||
1
|
||||
iex> Keyword.get! [a: 1], :b
|
||||
|
||||
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
|
||||
|
||||
iex> Keyword.get_values [a: 1, a: 2], :a
|
||||
iex> Keyword.get_values([a: 1, a: 2], :a)
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -157,7 +176,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.keys [a: 1, b: 2]
|
||||
iex> Keyword.keys([a: 1, b: 2])
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -171,7 +190,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.values [a: 1, b: 2]
|
||||
iex> Keyword.values([a: 1, b: 2])
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -188,9 +207,9 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete [a: 1, b: 2], :a
|
||||
iex> Keyword.delete([a: 1, b: 2], :a)
|
||||
[b: 2]
|
||||
iex> Keyword.delete [b: 2], :a
|
||||
iex> Keyword.delete([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@@ -199,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`.
|
||||
|
||||
@@ -207,7 +243,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put [a: 1, b: 2], :a, 3
|
||||
iex> Keyword.put([a: 1, b: 2], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@@ -222,7 +258,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put_new [a: 1, b: 2], :a, 3
|
||||
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -230,7 +266,7 @@ defmodule Keyword do
|
||||
def put_new(keywords, key, value) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
false -> [{key,value}|keywords]
|
||||
false -> [{key, value}|keywords]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -240,7 +276,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -255,7 +291,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4]) |> Enum.sort
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -270,9 +306,9 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
iex> end
|
||||
iex> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -313,6 +349,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.update([a: 1], :a, &1 * 2)
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, &1 * 2)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
@@ -342,7 +379,7 @@ defmodule Keyword do
|
||||
[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
|
||||
|
||||
+62
-39
@@ -10,12 +10,14 @@ defmodule List do
|
||||
first argument.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Given a list of lists, concatenates the sublists into a single list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat [[1,[2],3], [4], [5,6]]
|
||||
iex> List.concat([[1, [2], 3], [4], [5, 6]])
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -32,7 +34,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat [1,2,3], [4,5,6]
|
||||
iex> List.concat([1, 2, 3], [4, 5, 6])
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -47,7 +49,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.delete([1,2,3], 1)
|
||||
iex> List.delete([1, 2, 3], 1)
|
||||
[2,3]
|
||||
|
||||
"""
|
||||
@@ -60,10 +62,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.duplicate "hello", 3
|
||||
iex> List.duplicate("hello", 3)
|
||||
["hello","hello","hello"]
|
||||
|
||||
iex> List.duplicate [1,2], 2
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
@@ -77,10 +79,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten [1,[[2],3]]
|
||||
iex> List.flatten([1, [[2], 3]])
|
||||
[1,2,3]
|
||||
|
||||
iex> List.flatten [1,[[2],3]], [4,5]
|
||||
iex> List.flatten([1, [[2], 3]], [4, 5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@@ -98,10 +100,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldl [5,5], 10, fn x, acc -> x + acc end
|
||||
iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
|
||||
20
|
||||
|
||||
iex> List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -115,7 +117,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
|
||||
-2
|
||||
|
||||
"""
|
||||
@@ -128,11 +130,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.last []
|
||||
iex> List.last([])
|
||||
nil
|
||||
iex> List.last [1]
|
||||
iex> List.last([1])
|
||||
1
|
||||
iex> List.last [1, 2, 3]
|
||||
iex> List.last([1, 2, 3])
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -142,27 +144,9 @@ 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
|
||||
|
||||
iex> List.member? [1,2,3], 1
|
||||
true
|
||||
|
||||
iex> 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
|
||||
@@ -183,7 +167,7 @@ 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
|
||||
@@ -233,7 +217,7 @@ 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
|
||||
@@ -259,7 +243,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.wrap [1,2,3]
|
||||
iex> List.wrap([1, 2, 3])
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -280,10 +264,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.zip [[1, 2], [3, 4], [5, 6]]
|
||||
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
||||
[{1, 3, 5}, {2, 4, 6}]
|
||||
|
||||
iex> List.zip [[1, 2], [3], [5, 6]]
|
||||
iex> List.zip([[1, 2], [3], [5, 6]])
|
||||
[{1, 3, 5}]
|
||||
|
||||
"""
|
||||
@@ -298,10 +282,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.unzip [{1, 2}, {3, 4}]
|
||||
iex> List.unzip([{1, 2}, {3, 4}])
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
iex> List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
|
||||
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@@ -309,8 +293,47 @@ defmodule 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.
|
||||
"""
|
||||
@@ -90,7 +109,7 @@ defmodule List.Dict do
|
||||
def put_new(dict, key, val) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, _ } -> dict
|
||||
false -> [{key,val}|dict]
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,6 +131,25 @@ defmodule List.Dict do
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
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 +182,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.
|
||||
"""
|
||||
+192
-192
@@ -1,4 +1,4 @@
|
||||
import Kernel, except: [to_binary: 1]
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defmodule Macro do
|
||||
@moduledoc """
|
||||
@@ -14,10 +14,10 @@ defmodule Macro do
|
||||
:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
|
||||
:<<<, :>>>, :|||, :&&&, :^^^
|
||||
]
|
||||
end
|
||||
|
||||
@@ -26,7 +26,7 @@ defmodule Macro do
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-]
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -42,8 +42,8 @@ defmodule Macro do
|
||||
|
||||
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
|
||||
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
|
||||
extract_args(quote do: 1.(1, 2, 3)) == :error
|
||||
|
||||
"""
|
||||
def extract_args(expr) do
|
||||
@@ -54,6 +54,10 @@ defmodule Macro do
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `Macro.escape/2`
|
||||
which leaves unquote statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.escape(:foo)
|
||||
@@ -62,79 +66,12 @@ defmodule Macro do
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
|
||||
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
|
||||
1
|
||||
|
||||
"""
|
||||
def escape(expr) do
|
||||
do_escape(expr, false)
|
||||
end
|
||||
|
||||
defp do_escape({ { { :., meta, [left, :unquote] }, _, [expr] }, _, args }, true) do
|
||||
all = [do_escape(meta, true), do_escape(left, true), expr, do_escape(args, true)]
|
||||
quote do
|
||||
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
|
||||
end
|
||||
end
|
||||
|
||||
defp do_escape({ { :., meta, [left, :unquote] }, _, [expr] }, true) do
|
||||
all = [do_escape(meta, true), do_escape(left, true), expr, nil]
|
||||
quote do
|
||||
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
|
||||
end
|
||||
end
|
||||
|
||||
defp do_escape({ :unquote, _meta, [expr] }, true) do
|
||||
expr
|
||||
end
|
||||
|
||||
defp do_escape({ left, right }, unquote) when
|
||||
(not is_tuple(left) or elem(left, 1) != :unquote_splicing) and
|
||||
(not is_tuple(right) or elem(right, 1) != :unquote_splicing) do
|
||||
{ do_escape(left, unquote), do_escape(right, unquote) }
|
||||
end
|
||||
|
||||
defp do_escape({ :quote, meta, args }, true) when length(args) in 1..2 do
|
||||
{ :{}, [], do_escape([:quote, meta, args], false) }
|
||||
end
|
||||
|
||||
defp do_escape(tuple, unquote) when is_tuple(tuple) do
|
||||
{ :{}, [], do_escape(tuple_to_list(tuple), unquote) }
|
||||
end
|
||||
|
||||
defp do_escape(list, true) when is_list(list) do
|
||||
do_splice(Enum.reverse(list))
|
||||
end
|
||||
|
||||
defp do_escape(list, false) when is_list(list) do
|
||||
lc item inlist list, do: do_escape(item, false)
|
||||
end
|
||||
|
||||
defp do_escape(other, _unquote), do: other
|
||||
|
||||
defp do_splice([{ :|, meta, [{ :unquote_splicing, _, [left] }, right] }|t]) do
|
||||
spliced = { :++, meta, [do_splice(t, [], []), left] }
|
||||
{ :++, meta, [spliced, right] }
|
||||
end
|
||||
|
||||
defp do_splice(list) do
|
||||
do_splice(list, [], [])
|
||||
end
|
||||
|
||||
defp do_splice([{ :unquote_splicing, _meta, [expr] }|t], buffer, acc) do
|
||||
do_splice(t, [], do_splice_join(do_splice_join(expr, buffer), acc))
|
||||
end
|
||||
|
||||
defp do_splice([h|t], buffer, acc) do
|
||||
do_splice t, [do_escape(h, true)|buffer], acc
|
||||
end
|
||||
|
||||
defp do_splice([], buffer, acc), do: do_splice_join(buffer, acc)
|
||||
|
||||
defp do_splice_join([], right), do: right
|
||||
defp do_splice_join(left, []), do: left
|
||||
defp do_splice_join(left, right), do: { :++, [], [left, right] }
|
||||
|
||||
@doc false
|
||||
def escape_quoted(expr) do
|
||||
do_escape(expr, true)
|
||||
def escape(expr, opts // []) do
|
||||
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@@ -152,7 +89,7 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.unescape_binary "example\\n"
|
||||
iex> Macro.unescape_binary("example\\n")
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
@@ -216,7 +153,7 @@ defmodule Macro do
|
||||
|
||||
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__`
|
||||
sigils. Check the implementation of `Kernel.sigil_b`
|
||||
for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
@@ -231,112 +168,124 @@ defmodule Macro do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_binary(tree) do
|
||||
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
|
||||
to_string(tree)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given expression to a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
iex> Macro.to_string(quote do: foo.bar(1, 2, 3))
|
||||
"foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_binary(tree)
|
||||
def to_string(tree)
|
||||
|
||||
# Variables
|
||||
def to_binary({ var, _, atom }) when is_atom(atom) do
|
||||
def to_string({ var, _, atom }) when is_atom(atom) do
|
||||
atom_to_binary(var, :utf8)
|
||||
end
|
||||
|
||||
# Aliases
|
||||
def to_binary({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_binary(&1))
|
||||
def to_string({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_string(&1))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_binary({ :__block__, _, [expr] }) do
|
||||
to_binary(expr)
|
||||
def to_string({ :__block__, _, [expr] }) do
|
||||
to_string(expr)
|
||||
end
|
||||
|
||||
def to_binary({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_binary(expr), "\n "
|
||||
def to_string({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_string(expr), "\n "
|
||||
"(\n " <> block <> "\n)"
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_binary({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
|
||||
def to_string({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_binary({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
|
||||
def to_string({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
|
||||
end
|
||||
|
||||
# List containers
|
||||
def to_binary({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
|
||||
def to_string({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
|
||||
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] })
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
"fn " <> arrow_to_binary(arrow) <> " end"
|
||||
"fn " <> arrow_to_string(arrow) <> " end"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_binary(block) <> "\nend"
|
||||
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_string(block) <> "\nend"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_binary(block), "\n "
|
||||
def to_string({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_string(block), "\n "
|
||||
"fn\n " <> block <> "\nend"
|
||||
end
|
||||
|
||||
# Partial call
|
||||
def to_binary({ :&, _, [num] }) do
|
||||
def to_string({ :&, _, [num] }) do
|
||||
"&#{num}"
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_binary({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_binary(arrow, true) <> ")"
|
||||
def to_string({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_string(arrow, true) <> ")"
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_binary({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
|
||||
def to_string({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_string(left) <> " #{op} " <> op_to_string(right)
|
||||
end
|
||||
|
||||
# Splat when
|
||||
def to_string({ :when, _, args }) do
|
||||
{ left, right } = :elixir_tree_helpers.split_last(args)
|
||||
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_binary({ :not, _, [arg] }) do
|
||||
"not " <> to_binary(arg)
|
||||
def to_string({ :not, _, [arg] }) do
|
||||
"not " <> to_string(arg)
|
||||
end
|
||||
|
||||
def to_binary({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_binary(arg)
|
||||
def to_string({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_string(arg)
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_binary({ target, _, args }) when is_list(args) do
|
||||
def to_string({ target, _, args }) when is_list(args) do
|
||||
{ list, last } = :elixir_tree_helpers.split_last(args)
|
||||
case is_kw_blocks?(last) do
|
||||
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
|
||||
false -> call_to_binary_with_args(target, args)
|
||||
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
|
||||
false -> call_to_string_with_args(target, args)
|
||||
end
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
def to_binary({ left, right }) do
|
||||
to_binary({ :{}, [], [left, right] })
|
||||
def to_string({ left, right }) do
|
||||
to_string({ :{}, [], [left, right] })
|
||||
end
|
||||
|
||||
# Lists
|
||||
def to_binary(list) when is_list(list) do
|
||||
to_binary({ :[], [], list })
|
||||
def to_string(list) when is_list(list) do
|
||||
to_string({ :[], [], list })
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
|
||||
def to_string(other), do: inspect(other, raw: true)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@@ -346,56 +295,56 @@ 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(other), do: call_to_binary(other)
|
||||
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(other), do: call_to_string(other)
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
|
||||
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
|
||||
defp call_to_binary(other), do: to_binary(other)
|
||||
defp call_to_string(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
|
||||
defp call_to_string(other), do: to_string(other)
|
||||
|
||||
defp call_to_binary_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_binary(&1))
|
||||
call_to_binary(target) <> "(" <> args <> ")"
|
||||
defp call_to_string_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_string(&1))
|
||||
call_to_string(target) <> "(" <> args <> ")"
|
||||
end
|
||||
|
||||
defp kw_blocks_to_binary(kw) do
|
||||
defp kw_blocks_to_string(kw) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
|
||||
false -> acc
|
||||
end
|
||||
end) <> "end"
|
||||
end
|
||||
|
||||
defp kw_block_to_binary(key, value) do
|
||||
block = adjust_new_lines block_to_binary(value), "\n "
|
||||
defp kw_block_to_string(key, value) do
|
||||
block = adjust_new_lines block_to_string(value), "\n "
|
||||
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_binary({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, right }) ->
|
||||
defp block_to_string({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_binary({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_binary(&1))
|
||||
defp block_to_string({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_string(&1))
|
||||
end
|
||||
|
||||
defp block_to_binary(other), do: to_binary(other)
|
||||
defp block_to_string(other), do: to_string(other)
|
||||
|
||||
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_binary(expr) <> ")"
|
||||
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_string(expr) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_binary(expr), do: to_binary(expr)
|
||||
defp op_to_string(expr), do: to_string(expr)
|
||||
|
||||
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, right }) ->
|
||||
defp arrow_to_string({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, paren)
|
||||
left <> "-> " <> to_binary(right)
|
||||
left <> "-> " <> to_string(right)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -403,7 +352,7 @@ defmodule Macro do
|
||||
defp comma_join_or_empty_paren([], false), do: ""
|
||||
|
||||
defp comma_join_or_empty_paren(left, _) do
|
||||
Enum.map_join(left, ", ", to_binary(&1)) <> " "
|
||||
Enum.map_join(left, ", ", to_string(&1)) <> " "
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
@@ -416,32 +365,18 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representation and expands it. The following
|
||||
contents are expanded:
|
||||
Receives a AST node and expands it once. The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (__FILE__, __MODULE__, etc);
|
||||
* Module attributes reader (@foo);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression itself.
|
||||
|
||||
Notice that `Macro.expand` is not recursive and it does not
|
||||
expand child expressions. In this example
|
||||
|
||||
Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
|
||||
`!some_macro` will expand to something like:
|
||||
|
||||
case some_macro do
|
||||
false -> true
|
||||
nil -> true
|
||||
_ -> false
|
||||
end
|
||||
|
||||
Notice that the `!` operator is a macro that expands to a case.
|
||||
Even though `some_macro` is also a macro, it is not expanded
|
||||
because it is a child expression given to `!` as argument.
|
||||
In case the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `Macro.expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `Macro.expand/2` for expansion
|
||||
until the node no longer represents a macro and `Macro.expand_all/2`
|
||||
for recursive expansion.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -504,45 +439,50 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand(aliases, env) do
|
||||
expand(aliases, env, nil)
|
||||
def expand_once(aliases, env) do
|
||||
expand_once(aliases, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
defp expand({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, []) do
|
||||
atom when is_atom(atom) -> atom
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
atom when is_atom(atom) -> { atom, true, cache }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
|
||||
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
true -> { :elixir_aliases.concat(aliases), true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
|
||||
defp expand_once({ :@, _, [{ 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
|
||||
true -> { Module.get_attribute(module, name), true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
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({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
|
||||
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.module, true, cache }
|
||||
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.file, true, cache }
|
||||
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true, cache }
|
||||
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env, true, cache }
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
end
|
||||
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
module = env.module
|
||||
|
||||
@@ -552,43 +492,103 @@ defmodule Macro do
|
||||
[]
|
||||
end
|
||||
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, extra, to_erl_env(env, cache))
|
||||
env.module, extra, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
receiver = expand(left, env)
|
||||
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
{ receiver, _, _ } = expand_once(left, env, cache)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, to_erl_env(env, cache))
|
||||
args, env.module, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _receiver, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _receiver, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp expand(other, _env, _cache), do: other
|
||||
defp expand_once(other, _env, cache), do: { other, false, cache }
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Check `Macro.expand_once/2` for more information on how
|
||||
expansion works and `Macro.expand_all/2` for recursive expansion.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand(tree, env, cache) do
|
||||
expand_until({ tree, true, cache }, env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true, cache }, env) do
|
||||
expand_until(expand_once(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false, cache }, _env) do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Then it expands all of its children recursively.
|
||||
|
||||
Check `Macro.expand_once/2` for more information on how expansion
|
||||
works.
|
||||
"""
|
||||
def expand_all(tree, env) do
|
||||
expand_all(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, meta, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, meta, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
{ other, cache }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
|
||||
@@ -19,6 +19,8 @@ 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 }
|
||||
@@ -29,13 +31,15 @@ defmodule Macro.Env do
|
||||
@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__)
|
||||
|
||||
+365
-71
@@ -1,22 +1,300 @@
|
||||
defmodule Module do
|
||||
require :ets, as: ETS
|
||||
|
||||
defmacrop is_env(env) do
|
||||
quote do
|
||||
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
|
||||
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_string(body)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition { H, :on_def }
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_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
|
||||
@@ -24,14 +302,14 @@ defmodule Module do
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == ETS.info(table, :name)
|
||||
table == :ets.info(table, :name)
|
||||
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,11 +394,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.concat [Foo, Bar]
|
||||
iex> Module.concat([Foo, Bar])
|
||||
Foo.Bar
|
||||
iex> Module.concat [Foo, "Bar"]
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
iex> Module.concat [Foo, 'Bar']
|
||||
iex> Module.concat([Foo, 'Bar'])
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@@ -134,11 +412,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.concat Foo, Bar
|
||||
iex> Module.concat(Foo, Bar)
|
||||
Foo.Bar
|
||||
iex> Module.concat Foo, "Bar"
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
iex> Module.concat Foo, 'Bar'
|
||||
iex> Module.concat(Foo, 'Bar')
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@@ -154,10 +432,10 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.safe_concat [Unknown, Module]
|
||||
iex> Module.safe_concat([Unknown, Module])
|
||||
** (ArgumentError) argument error
|
||||
|
||||
iex> Module.safe_concat [List, Chars]
|
||||
iex> Module.safe_concat([List, Chars])
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@@ -173,10 +451,10 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.safe_concat Unknown, Module
|
||||
iex> Module.safe_concat(Unknown, Module)
|
||||
** (ArgumentError) argument error
|
||||
|
||||
iex> Module.safe_concat List, Chars
|
||||
iex> Module.safe_concat(List, Chars)
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@@ -212,21 +490,19 @@ defmodule Module do
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case ETS.lookup(table, tuple) do
|
||||
case :ets.lookup(table, tuple) do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
: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 ->
|
||||
ETS.insert(table, {
|
||||
[{ 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
|
||||
|
||||
@@ -299,7 +575,7 @@ defmodule Module do
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
ETS.lookup(table, tuple) != []
|
||||
:ets.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -319,8 +595,8 @@ defmodule Module do
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case ETS.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _, _ }] -> true
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _ }] -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
@@ -339,7 +615,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 """
|
||||
@@ -358,30 +634,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
|
||||
@@ -409,26 +688,39 @@ defmodule Module do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
new =
|
||||
if List.member?(acc, key) do
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
if :lists.member(key, acc) do
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
:ets.insert(table, { key, new })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked as accumulate with `Module.register_attribute`,
|
||||
was marked with `accumulate` with `Module.register_attribute`,
|
||||
a list is always returned.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
the following code:
|
||||
|
||||
@foo
|
||||
|
||||
Expands to:
|
||||
|
||||
Module.get_attribute(__MODULE__, @foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
This is true for `@foo` attributes but false for direct calls.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
@@ -441,15 +733,22 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def get_attribute(module, key) when is_atom(key) do
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> old
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
if List.member?(acc, key), do: [], else: nil
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
if :lists.member(key, acc) do
|
||||
[]
|
||||
else
|
||||
warn && IO.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access"
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -467,7 +766,7 @@ defmodule Module do
|
||||
def delete_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:delete_attribute, module)
|
||||
table = data_table_for(module)
|
||||
ETS.delete(table, key)
|
||||
:ets.delete(table, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -508,13 +807,13 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist, true) do
|
||||
old = ETS.lookup_element(table, :__persisted_attributes, 2)
|
||||
ETS.insert(table, { :__persisted_attributes, [new|old] })
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, { :__persisted_attributes, [new|old] })
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate, true) do
|
||||
old = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
ETS.insert(table, { :__acc_attributes, [new|old] })
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, { :__acc_attributes, [new|old] })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -528,7 +827,7 @@ 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 """
|
||||
@@ -537,13 +836,10 @@ defmodule Module do
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
end
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
@@ -558,9 +854,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
: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"
|
||||
IO.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -574,12 +868,12 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
new =
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
:ets.insert(table, { key, new })
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -616,11 +910,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
|
||||
@@ -164,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
|
||||
@@ -166,7 +166,7 @@ defmodule OptionParser do
|
||||
defp is_no?("no-" <> _), do: true
|
||||
defp is_no?(_), do: false
|
||||
|
||||
defp is_switch_a?(kind, list) when is_list(list), do: List.member?(list, kind)
|
||||
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
|
||||
|
||||
+52
-41
@@ -1,15 +1,15 @@
|
||||
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
|
||||
@@ -18,9 +18,8 @@ defmodule Path do
|
||||
@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
|
||||
|
||||
@@ -43,9 +42,8 @@ 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 `~`.
|
||||
@@ -64,8 +62,8 @@ 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
|
||||
|
||||
@@ -78,12 +76,21 @@ defmodule Path do
|
||||
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
|
||||
|
||||
# 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")
|
||||
@@ -91,7 +98,7 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
def expand(path, relative_to) do
|
||||
normalize FN.absname(FN.absname(expand_home(path), relative_to), get_cwd(path))
|
||||
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -102,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
|
||||
|
||||
@@ -185,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
|
||||
@@ -267,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
|
||||
@@ -280,7 +291,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the `extension` of the last component of `path`.
|
||||
Returns the extension of the last component of `path`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -326,8 +337,8 @@ 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
|
||||
|
||||
@@ -374,19 +385,16 @@ 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:
|
||||
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_join(<<?\\,rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/,rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/,rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/,rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
list_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
@@ -396,7 +404,7 @@ defmodule Path do
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char,rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
@@ -409,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
|
||||
|
||||
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
|
||||
@@ -449,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")
|
||||
|
||||
@@ -460,12 +471,12 @@ 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
|
||||
:elixir_glob.wildcard glob
|
||||
:filelib.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -520,4 +531,4 @@ defmodule Path do
|
||||
defp normalize([], acc) do
|
||||
join Enum.reverse(acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,13 +19,6 @@ defmodule Process do
|
||||
:erlang.is_process_alive(pid)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self do
|
||||
IO.puts "Process.self is deprecated, please use Kernel.self instead"
|
||||
Exception.print_stacktrace
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all key-values in the dictionary.
|
||||
"""
|
||||
@@ -83,9 +76,9 @@ defmodule Process do
|
||||
@doc """
|
||||
Sends an exit signal with the given reason to the pid.
|
||||
|
||||
The following behavior apply if reason is any term except `:normal` or `:kill`:
|
||||
The following behavior applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid itself will exist with the given reason;
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{'EXIT', from, reason} and delivered to the message queue of pid;
|
||||
@@ -108,6 +101,14 @@ defmodule Process do
|
||||
:erlang.exit(pid, reason)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid (process identifier) of the calling process.
|
||||
"""
|
||||
@spec self() :: pid
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
@@ -367,6 +368,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
|
||||
|
||||
+22
-18
@@ -34,6 +34,8 @@ defmodule Protocol do
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
@@ -81,14 +83,14 @@ defmodule Protocol do
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
_ -> raise ArgumentError, message: "#{module} is not loaded"
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, message: "#{module} is not a protocol"
|
||||
raise ArgumentError, message: "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -236,13 +238,13 @@ defmodule Protocol do
|
||||
|
||||
# Returns a quoted expression that allows to check
|
||||
# if the first item in the tuple is a built-in or not.
|
||||
defp is_builtin?([{h,_,_}]) do
|
||||
defp is_builtin?([{h, _, _}]) do
|
||||
quote do
|
||||
first == unquote(h)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_builtin?([{h,_,_}|t]) do
|
||||
defp is_builtin?([{h, _, _}|t]) do
|
||||
quote do
|
||||
first == unquote(h) or unquote(is_builtin?(t))
|
||||
end
|
||||
@@ -267,7 +269,7 @@ defmodule Protocol do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
@@ -349,36 +351,38 @@ defmodule Protocol.DSL do
|
||||
end
|
||||
|
||||
defp default_clause(name, args) do
|
||||
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
|
||||
{ [quote do: other],
|
||||
[],
|
||||
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
|
||||
end
|
||||
|
||||
defp nil_clause() do
|
||||
quote do
|
||||
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
|
||||
end
|
||||
{ [nil],
|
||||
[],
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, nil) do
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args)) }
|
||||
end
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, fallback) do
|
||||
arity = length(args)
|
||||
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
(target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote do
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(args))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
|
||||
name == unquote(name) and length(args) == unquote(arity) ->
|
||||
apply unquote(fallback), name, [unquote_splicing(args)]
|
||||
end) }
|
||||
end
|
||||
end
|
||||
end }
|
||||
end
|
||||
|
||||
defmacro def(expression) do
|
||||
|
||||
+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
-31
@@ -1,6 +1,162 @@
|
||||
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.
|
||||
Those types can be passed at the moment the record is defined:
|
||||
|
||||
defrecord User,
|
||||
name: "" :: string,
|
||||
age: 0 :: integer
|
||||
|
||||
All the fields without a specified type are assumed to have type `term`.
|
||||
|
||||
Assuming the `User` record defined above, it could be used in typespecs
|
||||
as follow:
|
||||
|
||||
@spec handle_user(User.t) :: boolean()
|
||||
|
||||
If the developer wants to define their own types to be used with the
|
||||
record, Elixir allows a more lengthy definition with the help of the
|
||||
`record_type` macro:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
## Importing records
|
||||
|
||||
It is also possible to import a public record (a record, defined using
|
||||
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
|
||||
|
||||
Record.import Config, as: :config
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -24,32 +180,55 @@ defmodule Record do
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
opts = Keyword.delete(opts, :do)
|
||||
{ fields, types } = record_split(values)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Record.DSL
|
||||
import Elixir.Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
@record_types unquote(types)
|
||||
|
||||
values = unquote(values)
|
||||
opts = unquote(opts)
|
||||
Record.deffunctions(values, __ENV__)
|
||||
# Reassign fields to inner scope to
|
||||
# avoid conflicts in nested records
|
||||
fields = unquoted_fields
|
||||
|
||||
Elixir.Record.deffunctions(fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.deftypes(values, @record_types, __ENV__)
|
||||
Elixir.Record.deftypes(fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_split(fields) when is_list(fields) do
|
||||
record_split(fields, [], [])
|
||||
end
|
||||
|
||||
defp record_split(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp record_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
|
||||
record_split t, [{ field, default }|defaults], [{ field, Macro.escape(type) }|types]
|
||||
end
|
||||
|
||||
defp record_split([other|t], defaults, types) do
|
||||
record_split t, [other|defaults], types
|
||||
end
|
||||
|
||||
defp record_split([], defaults, types) do
|
||||
{ :lists.reverse(defaults), types }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import public record definition as a set of private macros (as defined by defrecordp/2)
|
||||
|
||||
## Usage
|
||||
|
||||
Record.import Record.Module, as: macro_name
|
||||
Import public record definition as a set of private macros,
|
||||
as defined by `Kernel.defrecordp/2`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -62,7 +241,8 @@ defmodule Record do
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
Record.defmacros(unquote(name),
|
||||
unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,12 +252,31 @@ defmodule Record do
|
||||
in `values`. This is invoked directly by `Kernel.defrecordp`,
|
||||
so check it for more information and documentation.
|
||||
"""
|
||||
def defrecordp(name, fields) do
|
||||
def defrecordp(name, fields) when is_atom(name) and is_list(fields) do
|
||||
{ fields, types, def_type } = recordp_split(fields, [], [], false)
|
||||
type = :"#{name}_t"
|
||||
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__)
|
||||
|
||||
if unquote(def_type) do
|
||||
@typep unquote(type)() :: { unquote(name), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split t, [{ field, default }|defaults], [type|types], true
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split t, [other|defaults], [quote(do: term)|types], def_type
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the record
|
||||
@@ -157,7 +356,6 @@ defmodule Record do
|
||||
end
|
||||
|
||||
contents = quote do
|
||||
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
@@ -217,7 +415,7 @@ defmodule Record do
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -243,10 +441,11 @@ 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}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -266,11 +465,11 @@ defmodule Record do
|
||||
@doc false
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise "cannot invoke update style macro inside match context"
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
@@ -280,7 +479,7 @@ defmodule Record do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -296,7 +495,7 @@ defmodule Record do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -325,7 +524,7 @@ defmodule Record do
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(record))
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -417,6 +616,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
|
||||
@@ -459,27 +659,27 @@ 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
|
||||
# def atime(value, record) do
|
||||
# set_elem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# def mtime(record) do
|
||||
# set_elem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
# def :atime.(callback, record) do
|
||||
# def atime(callback, record) do
|
||||
# set_elem(record, 1, callback.(elem(record, 1)))
|
||||
# end
|
||||
#
|
||||
# def :mtime.(callback, record) do
|
||||
# def mtime(callback, record) do
|
||||
# set_elem(record, 2, callback.(elem(record, 2)))
|
||||
# end
|
||||
#
|
||||
@@ -526,7 +726,7 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
contents = { :{}, [], [(quote do: __MODULE__)|fields] }
|
||||
contents = quote do: { __MODULE__, unquote_splicing(fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -572,6 +772,8 @@ defmodule Record do
|
||||
@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
|
||||
|
||||
+40
-35
@@ -79,34 +79,14 @@ 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
|
||||
|
||||
iex> Regex.index %r/c(d)/, "abcd"
|
||||
2
|
||||
iex> Regex.index %r/e/, "abcd"
|
||||
nil
|
||||
|
||||
"""
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a boolean if there was a match or not.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match? %r/foo/, "foo"
|
||||
iex> Regex.match?(%r/foo/, "foo")
|
||||
true
|
||||
iex> Regex.match? %r/foo/, "bar"
|
||||
iex> Regex.match?(%r/foo/, "bar")
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -116,14 +96,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
|
||||
|
||||
iex> Regex.run %r/c(d)/, "abcd"
|
||||
iex> Regex.run(%r/c(d)/, "abcd")
|
||||
["cd", "d"]
|
||||
iex> Regex.run %r/e/, "abcd"
|
||||
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 // [])
|
||||
@@ -139,6 +122,7 @@ defmodule Regex do
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
@@ -149,7 +133,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.captures %r/c(?<foo>d)/g, "abcd"
|
||||
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
|
||||
"""
|
||||
@@ -178,7 +162,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.source %r(foo)
|
||||
iex> Regex.source(%r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@@ -191,7 +175,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.opts %r(foo)m
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
@@ -204,7 +188,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups %r/(?<foo>foo)/g
|
||||
iex> Regex.groups(%r/(?<foo>foo)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
@@ -220,11 +204,11 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan %r/c(d|e)/, "abcd abce"
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
[["d"], ["e"]]
|
||||
iex> Regex.scan %r/c(?:d|e)/, "abcd abce"
|
||||
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
|
||||
["cd", "ce"]
|
||||
iex> Regex.scan %r/e/, "abcd"
|
||||
iex> Regex.scan(%r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -289,6 +273,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
|
||||
@@ -311,7 +314,7 @@ defmodule Regex do
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
@@ -325,10 +328,12 @@ defmodule Regex do
|
||||
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)
|
||||
|
||||
+261
-24
@@ -62,7 +62,7 @@ defmodule String do
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
iex> String.codepoints "josé"
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
@@ -139,28 +139,32 @@ defmodule String do
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc """
|
||||
Divides a string into sub string 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
|
||||
Unicode whitespace occurrences with leading and trailing
|
||||
whitespace ignored.
|
||||
|
||||
It returns a list with the original string if the pattern
|
||||
can't be matched.
|
||||
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")
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
@@ -177,12 +181,8 @@ defmodule String do
|
||||
["a,b"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
def split(binary, pattern, options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
@@ -383,7 +383,7 @@ defmodule String do
|
||||
"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])
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@@ -399,7 +399,7 @@ defmodule String do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
opts = [{:insert_replaced,insert}|opts]
|
||||
opts = [{:insert_replaced, insert}|opts]
|
||||
end
|
||||
|
||||
opts
|
||||
@@ -458,6 +458,61 @@ defmodule String do
|
||||
@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.
|
||||
|
||||
@@ -470,7 +525,7 @@ defmodule String do
|
||||
true
|
||||
iex> String.valid_codepoint?("ø")
|
||||
true
|
||||
iex> String.valid_codepoint?("\xffff")
|
||||
iex> String.valid_codepoint?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid_codepoint?("asdf")
|
||||
false
|
||||
@@ -505,11 +560,12 @@ defmodule String do
|
||||
{ "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
|
||||
|
||||
@@ -528,7 +584,8 @@ 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
|
||||
|
||||
@@ -630,6 +687,12 @@ defmodule String do
|
||||
"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
|
||||
@@ -662,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
|
||||
|
||||
+29
-21
@@ -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,32 +57,36 @@ defmodule String.Unicode do
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(""), do: ""
|
||||
def downcase(string), do: do_downcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
|
||||
def downcase(unquote(codepoint) <> rest) do
|
||||
unquote(lower) <> downcase(rest)
|
||||
defp do_downcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(lower)) ++ downcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def downcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> downcase(rest)
|
||||
defp do_downcase(<< char, rest :: binary >>) do
|
||||
[char|do_downcase(rest)]
|
||||
end
|
||||
|
||||
defp do_downcase(""), do: []
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(""), do: ""
|
||||
def upcase(string), do: do_upcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
|
||||
def upcase(unquote(codepoint) <> rest) do
|
||||
unquote(upper) <> upcase(rest)
|
||||
defp do_upcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(upper)) ++ do_upcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def upcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> upcase(rest)
|
||||
defp do_upcase(<< char, rest :: binary >>) do
|
||||
[char|do_upcase(rest)]
|
||||
end
|
||||
|
||||
defp do_upcase(""), do: []
|
||||
|
||||
# Titlecase once
|
||||
|
||||
def titlecase_once(""), do: { "", "" }
|
||||
@@ -109,23 +113,27 @@ defmodule String.Unicode do
|
||||
|
||||
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 codepoint inlist whitespace do
|
||||
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
|
||||
do_rstrip(rest, unquote(codepoint) <> buffer)
|
||||
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, rest :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(rest, "") :: 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
|
||||
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R15|R16).*"}.
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
|
||||
+42
-23
@@ -1,11 +1,12 @@
|
||||
-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, file_type/1,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -20,8 +21,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([]).
|
||||
|
||||
@@ -43,40 +44,58 @@ start_cli() ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% Helpers
|
||||
|
||||
file_type(File) ->
|
||||
case file:read_file_info(File) of
|
||||
{ ok, #file_info{type=Type} } -> { ok, Type };
|
||||
{ error, _ } = Error -> Error
|
||||
end.
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
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,8 +1,23 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, expand/3]).
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% 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)
|
||||
},
|
||||
|
||||
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 an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
@@ -41,7 +56,7 @@ expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases) ->
|
||||
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
@@ -49,28 +64,31 @@ expand_one(H, Aliases) ->
|
||||
|
||||
%% 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 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.
|
||||
|
||||
@@ -99,12 +117,12 @@ nesting_alias(Prefix, Full) ->
|
||||
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])) };
|
||||
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case string:tokens(atom_to_list(Atom), "-") of
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
@@ -116,23 +134,18 @@ concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
|
||||
raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat([nil|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)].
|
||||
|
||||
dot_to_dash(List) ->
|
||||
[case X of
|
||||
$. -> $-;
|
||||
_ -> X
|
||||
end || X <- List].
|
||||
to_partial("Elixir." ++ Arg) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
|
||||
@@ -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,
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_splat_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.
|
||||
@@ -15,7 +14,7 @@ get_pairs(Meta, Key, Clauses, S) ->
|
||||
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, Left, Right } || { Left, Right } <- Pairs];
|
||||
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
@@ -26,9 +25,12 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
|
||||
% Function for translating assigns.
|
||||
|
||||
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, #elixir_scope{context=Context, temp_vars=TempVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
||||
temp_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
temp_vars=TempVars, backup_vars=BackupVars} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
@@ -78,13 +80,13 @@ extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, []
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args(_) -> error.
|
||||
|
||||
% Extract guards when it is in the last element of the args
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_last_guards([]) -> { [], [] };
|
||||
extract_last_guards(Args) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Bare, Guards } = extract_guards(Right),
|
||||
{ Left ++ [Bare], Guards }.
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Left, extract_or_clauses(Right, []) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
@@ -96,16 +98,16 @@ match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
do_match(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
|
||||
do_match(Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(Meta, DecoupledClause, S),
|
||||
do_match(_Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
{ [TDecoupledClause], TS };
|
||||
|
||||
do_match(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% 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, TAcc } = each_clause(X, Acc),
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
@@ -164,21 +166,21 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause(Meta, { do, [Condition], Expr }, S) ->
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
|
||||
each_clause(Meta, { else, [Condition], Expr }, S) ->
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
|
||||
each_clause(Meta, { 'after', [Condition], Expr }, S) ->
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
||||
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause(Meta, { Key, _, _ }, S) ->
|
||||
each_clause({ Key, Meta, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
@@ -245,4 +247,14 @@ generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
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
|
||||
}.
|
||||
@@ -5,11 +5,12 @@
|
||||
-behavior(gen_server).
|
||||
-record(elixir_code_server, {
|
||||
argv=[],
|
||||
compilation_status=ok,
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
compiler_options=[{docs,true},{debug_info,true}],
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
waiting=[]
|
||||
}).
|
||||
|
||||
@@ -58,6 +59,9 @@ handle_call(argv, _From, Config) ->
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
|
||||
handle_call(compilation_status, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compilation_status, Config };
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
[H|T] ->
|
||||
@@ -80,6 +84,15 @@ handle_cast({ compiler_options, Options }, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_cast(register_warning, Config) ->
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
|
||||
_ -> { noreply, Config }
|
||||
end;
|
||||
|
||||
handle_cast(reset_warnings, Config) ->
|
||||
{ noreply, Config#elixir_code_server{compilation_status=ok} };
|
||||
|
||||
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([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
|
||||
|
||||
@@ -209,11 +208,10 @@ core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/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",
|
||||
@@ -222,18 +220,14 @@ 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/process.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
|
||||
@@ -253,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).
|
||||
|
||||
+187
-129
@@ -1,36 +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/5,
|
||||
wrap_definition/7,
|
||||
store_definition/6,
|
||||
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.
|
||||
@@ -47,81 +71,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, Call, Expr, S) ->
|
||||
do_wrap_definition(Kind, Meta, [
|
||||
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, S) ->
|
||||
do_wrap_definition(Kind, Meta, [
|
||||
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, Extra) ->
|
||||
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, Module, Call, Body, RawS) ->
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
|
||||
Format = [Kind, 'Elixir.Macro':to_string(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, Module, Name, Args, Guards, Body, RawS).
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
|
||||
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, Module, Name, Args, Guards, Body, RawS) ->
|
||||
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
|
||||
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, DS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
Arity = length(Args),
|
||||
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),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
|
||||
|
||||
[store_each(false, Kind, File, Location, Table, 0,
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
[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
|
||||
|
||||
@@ -159,47 +180,53 @@ 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),
|
||||
|
||||
%% Add names to args
|
||||
NClause = case TS#elixir_scope.name_args of
|
||||
true ->
|
||||
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
|
||||
setelement(3, TClause, NArgs);
|
||||
false -> TClause
|
||||
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__' },
|
||||
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, NClause, [FBody|element(5, NClause)]);
|
||||
false -> NClause
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
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;
|
||||
@@ -210,58 +237,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 ->
|
||||
@@ -274,22 +302,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 and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
|
||||
[] ->
|
||||
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
|
||||
|
||||
@@ -298,33 +328,61 @@ 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:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < 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_to_binary(Atom, utf8)]);
|
||||
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 ~ts/~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 ~ts/~B does not match previous clause ~ts/~B", [Name, Arity, ElseName, ElseArity]);
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
|
||||
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity < A ->
|
||||
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
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("~ts ~ts/~B already defined as ~ts", [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 ~ts/~B is unused", [Name, Arity]).
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -1,9 +1,8 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
assign_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,25 +15,16 @@ 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.
|
||||
|
||||
%% Retrieve args defined for the given arity.
|
||||
|
||||
retrieve_args(Meta, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
{
|
||||
[ { var, Line, super_arg(X) } || X <- lists:seq(1, Arity) ],
|
||||
S#elixir_scope{name_args=true}
|
||||
}.
|
||||
|
||||
%% Assign pseudo variables to the given vars.
|
||||
|
||||
assign_args(Line, Args, S) when is_integer(Line) ->
|
||||
@@ -54,7 +44,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,31 +52,33 @@ 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 ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
@@ -8,25 +8,29 @@
|
||||
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, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } -> Receiver;
|
||||
{ macro, Receiver } -> Receiver;
|
||||
_ -> false
|
||||
{ function, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Function retrieval
|
||||
@@ -35,13 +39,14 @@ import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
|
||||
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.
|
||||
|
||||
@@ -50,7 +55,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
|
||||
@@ -62,20 +69,21 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
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);
|
||||
{ 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_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
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)
|
||||
@@ -87,8 +95,9 @@ 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_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 ->
|
||||
@@ -96,6 +105,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
{ 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, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
@@ -110,51 +120,54 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?BUILTIN } ->
|
||||
{ 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, S) }
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
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, Module),
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Module, Module),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
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, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
|
||||
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, S) ->
|
||||
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, Module),
|
||||
elixir_def_local:macro_for(Tuple, false, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
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, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
|
||||
end.
|
||||
|
||||
@@ -187,27 +200,10 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
{ TR, TS } = elixir_translator:translate_each(
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(?line(Meta), Tree),
|
||||
S#elixir_scope{
|
||||
check_clauses=false,
|
||||
macro_functions=[],
|
||||
macro_macros=[],
|
||||
macro_aliases=[]
|
||||
}
|
||||
),
|
||||
{ TR, TS#elixir_scope{
|
||||
check_clauses=S#elixir_scope.check_clauses,
|
||||
macro_functions=merge_imports(S#elixir_scope.macro_functions, TS#elixir_scope.macro_functions),
|
||||
macro_macros=merge_imports(S#elixir_scope.macro_macros, TS#elixir_scope.macro_macros),
|
||||
macro_aliases=merge_aliases(S#elixir_scope.macro_aliases, TS#elixir_scope.macro_aliases)
|
||||
} }.
|
||||
|
||||
merge_aliases(A1, A2) ->
|
||||
orddict:merge(fun(_K,_V1,V2) -> V2 end, A1, A2).
|
||||
|
||||
merge_imports(A1, A2) ->
|
||||
A2 ++ lists:foldl(fun({ Key, _ }, Acc) -> lists:keydelete(Key, 1, Acc) end, A1, A2).
|
||||
S
|
||||
).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -246,15 +242,25 @@ find_dispatch(Tuple, List) ->
|
||||
is_import(Meta, Tuple, S) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = Import ->
|
||||
not_an_import(Tuple, S#elixir_scope.macro_functions)
|
||||
andalso not_an_import(Tuple, S#elixir_scope.macro_macros)
|
||||
andalso 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, Pairs) ->
|
||||
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs).
|
||||
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];
|
||||
@@ -268,24 +274,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 ~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
|
||||
|
||||
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,
|
||||
@@ -296,6 +289,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) -> [];
|
||||
|
||||
@@ -310,20 +317,25 @@ get_optional_macros(Receiver) ->
|
||||
{ error, _ } -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_functions() ->
|
||||
elixir_imported_functions() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(functions)
|
||||
?builtin:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> []
|
||||
error:undef -> in_erlang_functions()
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> in_erlang_macros()
|
||||
end.
|
||||
|
||||
%% Macros imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(macros)
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -337,6 +349,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 },
|
||||
@@ -347,8 +363,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 },
|
||||
@@ -428,6 +447,9 @@ in_erlang_macros() ->
|
||||
{'and',2},
|
||||
{apply,2},
|
||||
{apply,3},
|
||||
{binding,0},
|
||||
{binding,1},
|
||||
{binding,2},
|
||||
{'case',2},
|
||||
{def,1},
|
||||
{def,2},
|
||||
@@ -443,7 +465,6 @@ in_erlang_macros() ->
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{function,1},
|
||||
{function,2},
|
||||
{function,3},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
|
||||
@@ -9,20 +9,11 @@
|
||||
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 >>,
|
||||
@@ -84,6 +75,11 @@ deprecation(Meta, File, Message, Args) ->
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% output warning based on warnings going to stdout or stderr.
|
||||
output_warning(Warning) ->
|
||||
elixir_code_server:cast(register_warning),
|
||||
io:format(standard_error, Warning, []).
|
||||
|
||||
%% Ignore on bootstrap
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
|
||||
@@ -94,19 +90,22 @@ 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 ~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));
|
||||
output_warning(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message))
|
||||
output_warning(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
@@ -114,16 +113,16 @@ 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));
|
||||
output_warning(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
|
||||
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
output_warning(file_format(Line, File, Message));
|
||||
|
||||
%% Default behavior
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
io:format(file_format(Line, File, Message)).
|
||||
output_warning(file_format(Line, File, Message)).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
@@ -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.
|
||||
@@ -60,9 +37,19 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
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
|
||||
|
||||
@@ -70,8 +57,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
|
||||
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 } },
|
||||
@@ -83,8 +69,7 @@ calculate(Meta, Key, Opts, Old, Temp, 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,OldImports} -> OldImports -- Except
|
||||
@@ -97,34 +82,24 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] -> { keydelete(Key, Old), keydelete(Key, Temp) };
|
||||
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
_ ->
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
{ [{ Key, Final }|keydelete(Key, Old)], [{ Key, Final }|keydelete(Key, Temp)] }
|
||||
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 :~ts, 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 :~ts, 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
|
||||
|
||||
@@ -165,58 +140,34 @@ 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.
|
||||
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({already_imported,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B already imported from ~ts", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({local_conflict,{_, Name, Arity}}) ->
|
||||
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
|
||||
@@ -243,30 +194,25 @@ intersection([H|T], All) ->
|
||||
|
||||
intersection([], _All) -> [].
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
remove_underscored(_, List) -> List.
|
||||
remove_underscored(default, List) ->
|
||||
lists:filter(fun({ Name, _ }) ->
|
||||
case atom_to_list(Name) of
|
||||
"_" ++ _ -> false;
|
||||
_ -> true
|
||||
end
|
||||
end, List);
|
||||
|
||||
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
|
||||
case atom_to_list(Name) of
|
||||
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
|
||||
"_" ++ _ -> remove_underscored(T);
|
||||
_ -> [H|remove_underscored(T)]
|
||||
end;
|
||||
|
||||
remove_underscored(T) ->
|
||||
T.
|
||||
remove_underscored(_, List) -> List.
|
||||
|
||||
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},
|
||||
@@ -275,7 +221,7 @@ non_overridable_macros() ->
|
||||
{'__ambiguousop__','*'},
|
||||
{'__scope__',2},
|
||||
{'__block__','*'},
|
||||
{'->','2'},
|
||||
{'->','*'},
|
||||
{'<<>>','*'},
|
||||
{'{}','*'},
|
||||
{'[]','*'},
|
||||
@@ -290,6 +236,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
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
%% 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
|
||||
@@ -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
|
||||
match ->
|
||||
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;
|
||||
@@ -175,8 +156,8 @@ extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) anda
|
||||
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
|
||||
|
||||
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, S);
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
|
||||
|
||||
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
@@ -217,11 +198,11 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
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 ~ts", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
|
||||
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
|
||||
{ H, ES } ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
|
||||
{ E, ES } ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
|
||||
@@ -6,12 +6,9 @@
|
||||
-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_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
|
||||
|
||||
@@ -63,13 +60,17 @@ translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
WrappedMeta =
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso lists:keyfind(import_fa, 1, MetaFA) of
|
||||
{ import_fa, Receiver } -> [{ import, Receiver }|Meta];
|
||||
case lists:keyfind(import_fa, 1, MetaFA) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
lists:keystore(context, 1,
|
||||
lists:keystore(import, 1, Meta, { import, Receiver }),
|
||||
{ context, Context }
|
||||
);
|
||||
false -> Meta
|
||||
end,
|
||||
|
||||
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
|
||||
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
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;
|
||||
|
||||
@@ -77,20 +78,6 @@ 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) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
case translate_args(Args, S) of
|
||||
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "function(:~ts, ~B) is deprecated, please use function(~ts/~B)", [Name, Arity, Name, Arity]),
|
||||
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/3 instead")
|
||||
end;
|
||||
|
||||
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
{ [A,B,C], SA } = translate_args(Args, S),
|
||||
@@ -132,17 +119,66 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', get_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name ]
|
||||
[ { '__MODULE__', Meta, false }, Name, true ]
|
||||
}, 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 @~ts, got: ~p", [Name, length(Args)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Binding
|
||||
|
||||
translate({ 'binding', Meta, [] }, S) ->
|
||||
translate({ 'binding', Meta, [false] }, S);
|
||||
|
||||
translate({ 'binding', Meta, [false] }, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Var) || { { Name, nil }, Var } <- S#elixir_scope.vars]), S };
|
||||
|
||||
translate({ 'binding', Meta, [true] }, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- S#elixir_scope.vars]), S };
|
||||
|
||||
translate({ 'binding', Meta, [List] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
|
||||
Line = ?line(Meta),
|
||||
Dict = lists:foldl(fun
|
||||
(Name, Acc) when is_atom(Name) ->
|
||||
case orddict:find({ Name, nil }, Vars) of
|
||||
{ ok, Var } ->
|
||||
orddict:store(Name, Var, Acc);
|
||||
error ->
|
||||
Acc
|
||||
end;
|
||||
(Name, _Acc) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/1 expects a list of atoms "
|
||||
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Name)])
|
||||
end, [], List),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Var) || { Name, Var } <- Dict]), S };
|
||||
|
||||
translate({ 'binding', Meta, [List, true] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
|
||||
Line = ?line(Meta),
|
||||
Dict = lists:foldl(fun
|
||||
(Tuple, Acc) when is_tuple(Tuple) ->
|
||||
case orddict:find(Tuple, Vars) of
|
||||
{ ok, Var } ->
|
||||
orddict:store(Tuple, Var, Acc);
|
||||
error ->
|
||||
Acc
|
||||
end;
|
||||
(Tuple, _Acc) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/2 expects a list of tuples "
|
||||
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Tuple)])
|
||||
end, [], List),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- Dict]), S };
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
@@ -160,12 +196,11 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
S = RawS#elixir_scope{noname=true},
|
||||
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)),
|
||||
@@ -176,7 +211,8 @@ 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
|
||||
|
||||
@@ -211,43 +247,41 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } ->
|
||||
S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)};
|
||||
false ->
|
||||
S
|
||||
{ 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,
|
||||
|
||||
MS = FS#elixir_scope{check_clauses=true,local=nil},
|
||||
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, SC } = elixir_quote:user_quote(Call, S),
|
||||
{ TExpr, SE } = elixir_quote:user_quote(Expr, SC),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, SE), SE };
|
||||
{ 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 };
|
||||
|
||||
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
|
||||
|
||||
@@ -281,21 +315,26 @@ translate_in(Meta, Left, Right, S) ->
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
Expr = case TRight of
|
||||
{ InGuard, TExpr } = case TRight of
|
||||
{ nil, _ } ->
|
||||
Expr = { atom, Line, false },
|
||||
{ Cache, Expr };
|
||||
{ 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',
|
||||
@@ -305,14 +344,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) ->
|
||||
@@ -325,16 +370,22 @@ decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
|
||||
[{do,[false],False},{do,[true],True}];
|
||||
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
|
||||
Raw;
|
||||
|
||||
expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elixir'] ->
|
||||
%% defmodule Hello
|
||||
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, H]);
|
||||
|
||||
%% 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;
|
||||
@@ -342,6 +393,7 @@ expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elix
|
||||
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]).
|
||||
|
||||
@@ -349,6 +401,19 @@ is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
to_var_value_tuple(Line, Name, Var) ->
|
||||
{ tuple, Line,
|
||||
[ { atom, Line, Name },
|
||||
{ var, Line, Var } ] }.
|
||||
|
||||
to_var_value_tuple(Line, Name, Kind, Var) ->
|
||||
{ tuple, Line,
|
||||
[ { tuple, Line, [
|
||||
{ atom, Line, Name },
|
||||
{ atom, Line, Kind }
|
||||
] },
|
||||
{ var, Line, Var } ] }.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(opaque) -> defopaque;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-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),
|
||||
@@ -15,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) ->
|
||||
@@ -32,7 +35,7 @@ data_table(Module) ->
|
||||
Module.
|
||||
|
||||
docs_table(Module) ->
|
||||
?atom_concat([o, Module]).
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
@@ -42,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],
|
||||
@@ -50,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),
|
||||
|
||||
@@ -60,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 } },
|
||||
@@ -77,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}) ->
|
||||
@@ -113,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.
|
||||
|
||||
@@ -140,7 +150,8 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
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(Module, RawFunctions)
|
||||
@@ -149,10 +160,8 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Ra
|
||||
{ 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
|
||||
] }.
|
||||
|
||||
@@ -191,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;
|
||||
_ ->
|
||||
@@ -289,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,
|
||||
@@ -305,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 }] }.
|
||||
@@ -323,8 +332,7 @@ 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)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
@@ -332,15 +340,20 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
lists:foreach(fun({M,F}) ->
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
apply(M, F, Args),
|
||||
{ { nil, 0 }, S }
|
||||
{ { atom, 0, nil }, S }
|
||||
end),
|
||||
|
||||
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).
|
||||
|
||||
|
||||
+200
-204
@@ -2,16 +2,18 @@ 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
|
||||
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
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
|
||||
when_op_eol in_op_eol inc_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens 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_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
|
||||
@@ -25,45 +27,40 @@ Terminals
|
||||
bracket_identifier paren_identifier do_identifier block_identifier
|
||||
fn 'end' aliases
|
||||
number signed_number atom bin_string list_string sigil
|
||||
dot_call_op comp_op op_identifier
|
||||
'not' 'and' 'or' 'xor' 'when' 'in' 'inlist' 'inbits' 'do'
|
||||
'true' 'false' 'nil'
|
||||
'=' '+' '-' '*' '/' '++' '--' '**' '//'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '/>' '=~'
|
||||
'&&' '||' '!' '...' '..'
|
||||
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&' '...'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
|
||||
Left 5 do.
|
||||
Right 10 stab_op.
|
||||
Left 20 ','. % Solve nested call_args conflicts
|
||||
Right 30 colon_colon_op.
|
||||
Right 40 when_op.
|
||||
Right 50 default_op.
|
||||
Left 60 pipe_op.
|
||||
Left 70 inc_op.
|
||||
Right 80 match_op.
|
||||
Right 110 send_op.
|
||||
Left 120 oror_op.
|
||||
Left 130 andand_op.
|
||||
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.
|
||||
Nonassoc 300 unary_op.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Right 30 type_op_eol. %% ::
|
||||
Right 40 when_op_eol. %% when
|
||||
Left 50 inc_op_eol. %% inlist, inbits
|
||||
Right 60 default_op_eol. %% //
|
||||
Left 70 tail_op_eol. %% |
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
|
||||
Left 170 in_op_eol. %% in
|
||||
Left 200 range_op_eol. %% ..
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Right 230 than_op_eol. %% < (op) > (e.g <>)
|
||||
Right 240 two_op_eol. %% ++, --, **
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
Nonassoc 320 at_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @ (op)
|
||||
Nonassoc 330 var.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
@@ -87,71 +84,65 @@ expr -> matched_expr : '$1'.
|
||||
expr -> unmatched_expr : '$1'.
|
||||
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> fn_expr : '$1'.
|
||||
|
||||
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> block_expr : '$1'.
|
||||
|
||||
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 -> andand_op expr : { '$1', '$2' }.
|
||||
op_expr -> three_op expr : { '$1', '$2' }.
|
||||
op_expr -> oror_op expr : { '$1', '$2' }.
|
||||
op_expr -> and_op expr : { '$1', '$2' }.
|
||||
op_expr -> or_op expr : { '$1', '$2' }.
|
||||
op_expr -> pipe_op expr : { '$1', '$2' }.
|
||||
op_expr -> bin_concat_op expr : { '$1', '$2' }.
|
||||
op_expr -> in_op expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op expr : { '$1', '$2' }.
|
||||
op_expr -> when_op expr : { '$1', '$2' }.
|
||||
op_expr -> send_op expr : { '$1', '$2' }.
|
||||
op_expr -> range_op expr : { '$1', '$2' }.
|
||||
op_expr -> default_op expr : { '$1', '$2' }.
|
||||
op_expr -> colon_colon_op expr : { '$1', '$2' }.
|
||||
op_expr -> comp_expr_op expr : { '$1', '$2' }.
|
||||
op_expr -> match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> add_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> exp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> two_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> and_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> tail_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> than_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> range_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> default_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
|
||||
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 -> andand_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipe_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> bin_concat_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> send_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> colon_colon_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens 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).
|
||||
@@ -167,24 +158,27 @@ max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol max_expr bracket_access :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access :
|
||||
build_access('$1', '$2').
|
||||
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> atom : build_atom('$1').
|
||||
base_expr -> list : '$1'.
|
||||
base_expr -> tuple : '$1'.
|
||||
base_expr -> 'true' : ?op('$1').
|
||||
base_expr -> 'false' : ?op('$1').
|
||||
base_expr -> 'nil' : ?op('$1').
|
||||
base_expr -> 'true' : ?id('$1').
|
||||
base_expr -> 'false' : ?id('$1').
|
||||
base_expr -> 'nil' : ?id('$1').
|
||||
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> bin_string : build_bin_string('$1').
|
||||
base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> '...' : { ?op('$1'), [{line,?line('$1')}], [] }.
|
||||
base_expr -> '...' : { ?id('$1'), [{line,?line('$1')}], [] }.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
|
||||
%% Blocks
|
||||
@@ -192,7 +186,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 +203,22 @@ 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 -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> call_args_parens_not_one when_op matched_expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
|
||||
stab_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'].
|
||||
@@ -232,7 +233,7 @@ open_paren -> '(' eol : '$1'.
|
||||
close_paren -> ')' : '$1'.
|
||||
close_paren -> eol ')' : '$2'.
|
||||
|
||||
empty_paren -> open_paren ')' : nil.
|
||||
empty_paren -> open_paren ')' : '$1'.
|
||||
|
||||
open_bracket -> '[' : '$1'.
|
||||
open_bracket -> '[' eol : '$1'.
|
||||
@@ -251,108 +252,69 @@ close_curly -> eol '}' : '$2'.
|
||||
|
||||
% Operators
|
||||
|
||||
add_op -> '+' : '$1'.
|
||||
add_op -> '-' : '$1'.
|
||||
add_op -> '+' eol : '$1'.
|
||||
add_op -> '-' eol : '$1'.
|
||||
add_op_eol -> add_op : '$1'.
|
||||
add_op_eol -> add_op eol : '$1'.
|
||||
add_op_eol -> dual_op : '$1'.
|
||||
add_op_eol -> dual_op eol : '$1'.
|
||||
|
||||
mult_op -> '*' : '$1'.
|
||||
mult_op -> '/' : '$1'.
|
||||
mult_op -> '*' eol : '$1'.
|
||||
mult_op -> '/' eol : '$1'.
|
||||
mult_op_eol -> mult_op : '$1'.
|
||||
mult_op_eol -> mult_op eol : '$1'.
|
||||
|
||||
two_op -> '++' : '$1'.
|
||||
two_op -> '--' : '$1'.
|
||||
two_op -> '++' eol : '$1'.
|
||||
two_op -> '--' eol : '$1'.
|
||||
two_op -> '**' : '$1'.
|
||||
two_op -> '**' eol : '$1'.
|
||||
exp_op_eol -> exp_op : '$1'.
|
||||
exp_op_eol -> exp_op eol : '$1'.
|
||||
|
||||
pipeline_op -> '=~' : '$1'.
|
||||
pipeline_op -> '=~' eol : '$1'.
|
||||
pipeline_op -> '|>' : '$1'.
|
||||
pipeline_op -> '|>' eol : '$1'.
|
||||
pipeline_op -> '/>' : '$1'.
|
||||
pipeline_op -> '/>' eol : '$1'.
|
||||
two_op_eol -> two_op : '$1'.
|
||||
two_op_eol -> two_op eol : '$1'.
|
||||
|
||||
three_op -> '&&&' : '$1'.
|
||||
three_op -> '&&&' eol : '$1'.
|
||||
three_op -> '|||' : '$1'.
|
||||
three_op -> '|||' eol : '$1'.
|
||||
three_op -> '<<<' : '$1'.
|
||||
three_op -> '<<<' eol : '$1'.
|
||||
three_op -> '>>>' : '$1'.
|
||||
three_op -> '>>>' eol : '$1'.
|
||||
three_op -> '^^^' : '$1'.
|
||||
three_op -> '^^^' eol : '$1'.
|
||||
default_op_eol -> default_op : '$1'.
|
||||
default_op_eol -> default_op eol : '$1'.
|
||||
|
||||
default_op -> '//' : '$1'.
|
||||
default_op -> '//' eol : '$1'.
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
|
||||
colon_colon_op -> '::' : '$1'.
|
||||
colon_colon_op -> '::' eol : '$1'.
|
||||
unary_op_eol -> unary_op : '$1'.
|
||||
unary_op_eol -> unary_op eol : '$1'.
|
||||
unary_op_eol -> dual_op : '$1'.
|
||||
unary_op_eol -> dual_op eol : '$1'.
|
||||
|
||||
unary_op -> '+' : '$1'.
|
||||
unary_op -> '+' eol : '$1'.
|
||||
unary_op -> '-' : '$1'.
|
||||
unary_op -> '-' eol : '$1'.
|
||||
unary_op -> '!' : '$1'.
|
||||
unary_op -> '!' eol : '$1'.
|
||||
unary_op -> '^' : '$1'.
|
||||
unary_op -> '^' eol : '$1'.
|
||||
unary_op -> 'not' : '$1'.
|
||||
unary_op -> 'not' eol : '$1'.
|
||||
unary_op -> '~~~' : '$1'.
|
||||
unary_op -> '~~~' eol : '$1'.
|
||||
match_op_eol -> match_op : '$1'.
|
||||
match_op_eol -> match_op eol : '$1'.
|
||||
|
||||
at_op -> '@' : '$1'.
|
||||
at_op -> '@' eol : '$1'.
|
||||
and_op_eol -> and_op : '$1'.
|
||||
and_op_eol -> and_op eol : '$1'.
|
||||
|
||||
match_op -> '=' : '$1'.
|
||||
match_op -> '=' eol : '$1'.
|
||||
or_op_eol -> or_op : '$1'.
|
||||
or_op_eol -> or_op eol : '$1'.
|
||||
|
||||
andand_op -> '&&' : '$1'.
|
||||
andand_op -> '&&' eol : '$1'.
|
||||
tail_op_eol -> tail_op : '$1'.
|
||||
tail_op_eol -> tail_op eol : '$1'.
|
||||
|
||||
oror_op -> '||' : '$1'.
|
||||
oror_op -> '||' eol : '$1'.
|
||||
than_op_eol -> than_op : '$1'.
|
||||
than_op_eol -> than_op eol : '$1'.
|
||||
|
||||
and_op -> 'and' : '$1'.
|
||||
and_op -> 'and' eol : '$1'.
|
||||
in_op_eol -> in_op : '$1'.
|
||||
in_op_eol -> in_op eol : '$1'.
|
||||
|
||||
or_op -> 'or' : '$1'.
|
||||
or_op -> 'or' eol : '$1'.
|
||||
or_op -> 'xor' : '$1'.
|
||||
or_op -> 'xor' eol : '$1'.
|
||||
inc_op_eol -> inc_op : '$1'.
|
||||
inc_op_eol -> inc_op eol : '$1'.
|
||||
|
||||
pipe_op -> '|' : '$1'.
|
||||
pipe_op -> '|' eol : '$1'.
|
||||
when_op_eol -> when_op : '$1'.
|
||||
when_op_eol -> when_op eol : '$1'.
|
||||
|
||||
bin_concat_op -> '<>' : '$1'.
|
||||
bin_concat_op -> '<>' eol : '$1'.
|
||||
stab_op_eol -> stab_op : '$1'.
|
||||
stab_op_eol -> stab_op eol : '$1'.
|
||||
|
||||
in_op -> 'in' : '$1'.
|
||||
in_op -> 'in' eol : '$1'.
|
||||
range_op_eol -> range_op : '$1'.
|
||||
range_op_eol -> range_op eol : '$1'.
|
||||
|
||||
inc_op -> 'inlist' : '$1'.
|
||||
inc_op -> 'inlist' eol : '$1'.
|
||||
inc_op -> 'inbits' : '$1'.
|
||||
inc_op -> 'inbits' eol : '$1'.
|
||||
at_op_eol -> at_op : '$1'.
|
||||
at_op_eol -> at_op eol : '$1'.
|
||||
|
||||
when_op -> 'when' : '$1'.
|
||||
when_op -> 'when' eol : '$1'.
|
||||
comp_op_eol -> comp_op : '$1'.
|
||||
comp_op_eol -> comp_op eol : '$1'.
|
||||
|
||||
stab_op -> '->' : '$1'.
|
||||
stab_op -> '->' eol : '$1'.
|
||||
|
||||
send_op -> '<-' : '$1'.
|
||||
send_op -> '<-' eol : '$1'.
|
||||
|
||||
range_op -> '..' : '$1'.
|
||||
range_op -> '..' eol : '$1'.
|
||||
|
||||
comp_expr_op -> comp_op : '$1'.
|
||||
comp_expr_op -> comp_op eol : '$1'.
|
||||
arrow_op_eol -> arrow_op : '$1'.
|
||||
arrow_op_eol -> arrow_op eol : '$1'.
|
||||
|
||||
% Dot operator
|
||||
|
||||
@@ -387,11 +349,16 @@ 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 -> empty_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
|
||||
|
||||
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
|
||||
call_args_parens_not_one -> open_paren ')' : [].
|
||||
call_args_parens_not_one -> empty_paren : [].
|
||||
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
|
||||
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
|
||||
|
||||
@@ -409,7 +376,7 @@ optional_comma_expr -> paren_expr ',' : '$1'.
|
||||
|
||||
call_args -> comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens -> open_paren ')' : [].
|
||||
call_args_parens -> empty_paren : [].
|
||||
call_args_parens -> open_paren call_args close_paren : '$2'.
|
||||
|
||||
% KV
|
||||
@@ -455,7 +422,7 @@ bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(op(Node), element(1, Node)).
|
||||
-define(id(Node), element(1, Node)).
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
|
||||
@@ -468,17 +435,14 @@ Erlang code.
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({ _, _, _ } = Op, Left, Right) ->
|
||||
{ ?exprs(Op), [{line,?line(Op)}], [Left, Right] };
|
||||
|
||||
build_op({ BOp, Line }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
|
||||
|
||||
build_op(Op, Left, Right) ->
|
||||
{ ?op(Op), [{line,?line(Op)}], [Left, Right] }.
|
||||
build_op({ _Kind, Line, Op }, Left, Right) ->
|
||||
{ Op, [{line,Line}], [Left, Right] }.
|
||||
|
||||
build_unary_op(Op, Expr) ->
|
||||
{ ?op(Op), [{line,?line(Op)}], [Expr] }.
|
||||
build_unary_op({ _Kind, Line, Op }, Expr) ->
|
||||
{ Op, [{line,Line}], [Expr] }.
|
||||
|
||||
build_tuple(_Marker, [Left, Right]) ->
|
||||
{ Left, Right };
|
||||
@@ -490,6 +454,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,13 +508,13 @@ 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, ?id(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ list_to_atom([$_,$_,Sigil,$_,$_]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
|
||||
@@ -566,18 +532,48 @@ build_atom({ atom, Line, Args }) ->
|
||||
%% Keywords
|
||||
|
||||
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
|
||||
{ '->', Meta, build_stab(T, Left, [Right], []) };
|
||||
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
|
||||
|
||||
build_stab(Else) ->
|
||||
build_block(Else).
|
||||
|
||||
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(T, Left, [Right], [H|Acc]);
|
||||
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, Old, build_block(lists:reverse(Temp)) },
|
||||
build_stab(New, T, Left, [Right], [H|Acc]);
|
||||
|
||||
build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab(T, Marker, [H|Temp], Acc);
|
||||
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
|
||||
build_stab(Meta, T, Marker, [H|Temp], Acc);
|
||||
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(Meta, [], Marker, Temp, Acc) ->
|
||||
H = { Marker, Meta, 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 clause,
|
||||
%% 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.
|
||||
|
||||
unwrap_when(Args) ->
|
||||
case elixir_tree_helpers:split_last(Args) of
|
||||
{ Start, { 'when', Meta, [_, _] = End } } ->
|
||||
[{ 'when', Meta, Start ++ End }];
|
||||
{ _, _ } ->
|
||||
Args
|
||||
end.
|
||||
|
||||
%% 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,7 +1,7 @@
|
||||
%% 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).
|
||||
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Block }], SC);
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
|
||||
+144
-133
@@ -1,5 +1,6 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([quote/3, linify/2, unquote/5, user_quote/2]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -22,48 +23,111 @@ do_linify(Line, List) when is_list(List) ->
|
||||
do_linify(_, Else) -> Else.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responisble for doing runtime unquoting.
|
||||
unquote(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(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;
|
||||
|
||||
unquote(Meta, Left, Right, Args, _File) when is_atom(Right) ->
|
||||
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(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").
|
||||
|
||||
%% Similar to quote, but the given code is meant to come directly from the user.
|
||||
%% Basically, lines are kept and hygiene mechanisms are disabled.
|
||||
user_quote(Expr, S) ->
|
||||
quote(Expr, #elixir_quote{
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
quote(Expr, nil, #elixir_quote{
|
||||
line=keep,
|
||||
vars_hygiene=nil,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=true
|
||||
}, S).
|
||||
unquote=Unquote,
|
||||
mark=false
|
||||
}, nil).
|
||||
|
||||
%% Quotes an expression into Erlang's AST
|
||||
erl_escape(Expr, Unquote, S) ->
|
||||
{ QExpr, TQ } = escape(Expr, Unquote),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote(Else, Q, S) ->
|
||||
do_quote(Else, Q, S).
|
||||
quote(Expr, nil, Q, S) ->
|
||||
do_quote(Expr, Q, S);
|
||||
|
||||
quote(Expr, Binding, Q, S) ->
|
||||
Context = Q#elixir_quote.context,
|
||||
|
||||
Vars = [ { '{}', [],
|
||||
[ '=', [], [
|
||||
{ '{}', [], [K, [], Context] },
|
||||
V
|
||||
] ]
|
||||
} || { K, V } <- Binding],
|
||||
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, S),
|
||||
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
|
||||
|
||||
erl_quote(Expr, Binding, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Binding, Q, S),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
%% Actual quoting and helpers
|
||||
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
|
||||
do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S);
|
||||
{ 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}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
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) ->
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S);
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, S);
|
||||
|
||||
%% Imports
|
||||
|
||||
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) ->
|
||||
@@ -73,43 +137,6 @@ do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}]
|
||||
#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({ 'alias!', _Meta, [Expr] }, Q, S) ->
|
||||
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
|
||||
|
||||
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,
|
||||
|
||||
Line = ?line(Meta),
|
||||
{ TAliases, SA } = do_quote([H|T], Q, S),
|
||||
|
||||
{ { tuple, Line, [
|
||||
{ atom, Line, '__aliases__' },
|
||||
meta([{alias,Annotation}|Meta], Q),
|
||||
TAliases
|
||||
] }, SA };
|
||||
|
||||
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, unquote, [meta(Meta, Q)|TAll]), TS };
|
||||
|
||||
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, unquote, [meta(Meta, Q)|TAll]), TS };
|
||||
|
||||
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.vars_hygiene }
|
||||
] },
|
||||
{ Tuple, 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;
|
||||
@@ -119,101 +146,85 @@ do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=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(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), 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 }.
|
||||
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_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
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
false -> Meta;
|
||||
Receiver -> [{ import_fa, Receiver }|Meta]
|
||||
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
|
||||
end,
|
||||
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
|
||||
% 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 };
|
||||
[H|T] ->
|
||||
Line = line(Q),
|
||||
{ lists:foldl(fun(L, R) -> { op, Line, '++', L, R } end, H, T), 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 }.
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
{ 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,5 +1,4 @@
|
||||
%% 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,
|
||||
@@ -9,11 +8,11 @@
|
||||
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),
|
||||
Vars = S#elixir_scope.vars,
|
||||
Tuple = { Name, Kind },
|
||||
|
||||
case Name of
|
||||
'_' ->
|
||||
@@ -22,9 +21,9 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
|
||||
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
|
||||
{ true, true } ->
|
||||
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
@@ -35,11 +34,14 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store({ Name, Kind }, RealName, Vars),
|
||||
temp_vars=orddict:store(Name, Kind, TempVars),
|
||||
vars=orddict:store(Tuple, RealName, Vars),
|
||||
temp_vars=if
|
||||
TempVars == nil -> TempVars;
|
||||
true -> ordsets:add_element(Tuple, TempVars)
|
||||
end,
|
||||
clause_vars=if
|
||||
ClauseVars == nil -> nil;
|
||||
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
|
||||
ClauseVars == nil; Kind == false -> ClauseVars;
|
||||
true -> orddict:store(Tuple, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
end;
|
||||
@@ -77,24 +79,28 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros }) ->
|
||||
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}.
|
||||
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 }.
|
||||
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,
|
||||
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.scheduled }
|
||||
S#elixir_scope.context_modules }
|
||||
).
|
||||
|
||||
serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
@@ -112,19 +118,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, MacroFunctions, MacroMacros, MacroAliases, 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,
|
||||
macro_functions=MacroFunctions,
|
||||
macro_macros=MacroMacros,
|
||||
macro_aliases=MacroAliases,
|
||||
scheduled=Scheduled,
|
||||
context_modules=FileModules,
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
@@ -138,26 +143,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).
|
||||
|
||||
|
||||
+298
-230
@@ -10,73 +10,93 @@
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
-define(container2(T1, T2),
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
).
|
||||
|
||||
-define(comp3(T1, T2, T3),
|
||||
T1 == $=, T2 == $=, T3 == $=;
|
||||
T1 == $!, T2 == $=, T3 == $=
|
||||
).
|
||||
%% New ops table
|
||||
|
||||
-define(op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>;
|
||||
T1 == $^, T2 == $^, T3 == $^;
|
||||
T1 == $~, T2 == $~, T3 == $~;
|
||||
T1 == $&, T2 == $&, T3 == $&;
|
||||
T1 == $|, T2 == $|, T3 == $|
|
||||
).
|
||||
|
||||
-define(comp2(T1, T2),
|
||||
T1 == $=, T2 == $=;
|
||||
T1 == $!, T2 == $=;
|
||||
T1 == $<, T2 == $=;
|
||||
T1 == $>, T2 == $=
|
||||
).
|
||||
|
||||
-define(op2(T1, T2),
|
||||
T1 == $&, T2 == $&;
|
||||
T1 == $|, T2 == $|;
|
||||
T1 == $<, T2 == $>;
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*;
|
||||
T1 == $/, T2 == $/;
|
||||
T1 == $:, T2 == $:;
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $-, T2 == $>;
|
||||
T1 == $., T2 == $.;
|
||||
T1 == $|, T2 == $>;
|
||||
T1 == $/, T2 == $>;
|
||||
T1 == $=, T2 == $~
|
||||
).
|
||||
|
||||
-define(comp1(T),
|
||||
T == $<;
|
||||
T == $>
|
||||
).
|
||||
|
||||
-define(op1(T),
|
||||
T == $+;
|
||||
T == $-;
|
||||
T == $*;
|
||||
T == $/;
|
||||
T == $=;
|
||||
T == $|;
|
||||
T == $!;
|
||||
T == $^;
|
||||
T == $@
|
||||
).
|
||||
-define(at_op(T),
|
||||
T == $@).
|
||||
|
||||
-define(unary_op(T),
|
||||
T == '+';
|
||||
T == '-';
|
||||
T == '@';
|
||||
T == '!';
|
||||
T == '^'
|
||||
).
|
||||
% T == $&;
|
||||
T == $!;
|
||||
T == $^).
|
||||
|
||||
-define(unary_op3(T1, T2, T3),
|
||||
T1 == $~, T2 == $~, T3 == $~).
|
||||
|
||||
-define(exp_op3(T1, T2, T3),
|
||||
T1 == $^, T2 == $^, T3 == $^).
|
||||
|
||||
-define(two_op(T1, T2),
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*).
|
||||
|
||||
-define(than_op(T1, T2),
|
||||
T1 == $<, T2 == $>).
|
||||
|
||||
-define(mult_op(T),
|
||||
T == $* orelse T == $/).
|
||||
|
||||
-define(dual_op(T),
|
||||
T == $+ orelse T == $-).
|
||||
|
||||
-define(range_op(T1, T2),
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
|
||||
-define(arrow_op(T1, T2),
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $|, T2 == $>).
|
||||
|
||||
-define(comp_op(T),
|
||||
T == $<;
|
||||
T == $>).
|
||||
|
||||
-define(comp_op2(T1, T2),
|
||||
T1 == $=, T2 == $=;
|
||||
T1 == $=, T2 == $~;
|
||||
T1 == $!, T2 == $=;
|
||||
T1 == $<, T2 == $=;
|
||||
T1 == $>, T2 == $=).
|
||||
|
||||
-define(comp_op3(T1, T2, T3),
|
||||
T1 == $=, T2 == $=, T3 == $=;
|
||||
T1 == $!, T2 == $=, T3 == $=).
|
||||
|
||||
-define(and_op(T1, T2),
|
||||
T1 == $&, T2 == $&).
|
||||
|
||||
-define(or_op(T1, T2),
|
||||
T1 == $|, T2 == $|).
|
||||
|
||||
-define(and_op3(T1, T2, T3),
|
||||
T1 == $&, T2 == $&, T3 == $&).
|
||||
|
||||
-define(or_op3(T1, T2, T3),
|
||||
T1 == $|, T2 == $|, T3 == $|).
|
||||
|
||||
-define(match_op(T),
|
||||
T == $=).
|
||||
|
||||
-define(tail_op(T),
|
||||
T == $|).
|
||||
|
||||
-define(default_op(T1, T2),
|
||||
T1 == $/, T2 == $/).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
T1 == $-, T2 == $>).
|
||||
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
tokenize(String, Line, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
@@ -141,7 +161,7 @@ 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, []),
|
||||
@@ -205,13 +225,10 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|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);
|
||||
@@ -219,22 +236,30 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
% ## Containers
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $& ->
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $& ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
@@ -247,7 +272,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])
|
||||
@@ -270,10 +295,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 } ->
|
||||
@@ -286,6 +307,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) ->
|
||||
@@ -295,19 +320,26 @@ tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
|
||||
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2,T3]) }|Tokens]);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $&; T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -336,45 +368,87 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
|
||||
|
||||
% ## Comparison three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?and_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, and_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?or_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?arrow_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?exp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, exp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## 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;
|
||||
|
||||
% ## Comparison two token operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?op2(T1, T2) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
|
||||
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Comparison single token operators
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp1(T) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
|
||||
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
|
||||
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp_op2(T1, T2) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
|
||||
handle_op(Rest, Line, and_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
|
||||
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
|
||||
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?op1(T) ->
|
||||
handle_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp_op(T) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?dual_op(T) ->
|
||||
handle_nonl_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
handle_op(Rest, Line, mult_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
|
||||
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
@@ -388,10 +462,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])
|
||||
@@ -400,34 +474,33 @@ 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
|
||||
nomatch ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
{ ok, [Check|T] } ->
|
||||
case handle_terminator(Check, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Check|T])
|
||||
end;
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
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;
|
||||
|
||||
% Ambiguous unary/binary operators tokens
|
||||
|
||||
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 /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
?dual_op(Sign),
|
||||
?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]);
|
||||
tokenize(Rest, Line, Scope, [{ dual_op, Line, list_to_atom([Sign]) }, 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) ->
|
||||
@@ -454,7 +527,7 @@ 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),
|
||||
@@ -469,25 +542,20 @@ 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_nonl_op([$:|Rest], Line, _Kind, 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_nonl_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, 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, _Kind, 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) ->
|
||||
tokenize(Rest, Line, Scope, [{ Op, Line }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
|
||||
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, 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
|
||||
@@ -511,7 +579,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
|
||||
@@ -553,7 +621,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) ->
|
||||
@@ -595,7 +663,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) ->
|
||||
@@ -607,9 +675,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.
|
||||
|
||||
@@ -625,121 +693,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 ~ts: 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|_]) ->
|
||||
@@ -751,21 +811,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("~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;
|
||||
@@ -808,6 +865,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('(') -> ')';
|
||||
@@ -815,50 +881,52 @@ terminator('[') -> ']';
|
||||
terminator('{') -> '}';
|
||||
terminator('<<') -> '>>'.
|
||||
|
||||
% Keywords check
|
||||
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
%% Keywords checking
|
||||
|
||||
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
{ ok, [{ Identifier, Line, Atom }|Tokens] };
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when
|
||||
check_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
do ->
|
||||
do ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
false -> nomatch
|
||||
false -> nomatch;
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
unary_op -> { ok, [{ unary_op, Line, Atom }|Tokens] };
|
||||
Kind -> { ok, add_token_with_nl({ Kind, Line, Atom }, Tokens) }
|
||||
end;
|
||||
|
||||
handle_keyword(_, _, _, _) -> nomatch.
|
||||
check_keyword(_, _, _, _) -> nomatch.
|
||||
|
||||
do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
is_boolean(keyword(Atom));
|
||||
do_keyword_valid(_) -> true.
|
||||
|
||||
% Keywords
|
||||
% Regular keywords
|
||||
keyword('fn') -> 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;
|
||||
keyword('xor') -> op;
|
||||
keyword('when') -> op;
|
||||
keyword('in') -> op;
|
||||
keyword('inlist') -> op;
|
||||
keyword('inbits') -> op;
|
||||
% Operators keywords
|
||||
keyword('not') -> unary_op;
|
||||
keyword('and') -> and_op;
|
||||
keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
keyword('inlist') -> inc_op;
|
||||
keyword('inbits') -> inc_op;
|
||||
|
||||
% Block keywords
|
||||
keyword('after') -> block;
|
||||
|
||||
@@ -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) -> unlink(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.
|
||||
@@ -9,14 +9,15 @@
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_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
|
||||
@@ -35,13 +36,10 @@ translate(Forms, S) ->
|
||||
%% Those macros are "low-level". They are the basic mechanism
|
||||
%% that makes the language work and cannot be partially applied
|
||||
%% nor overwritten.
|
||||
%%
|
||||
%% =, ^, import, require and alias could be made non-special
|
||||
%% forms without causing any side effects.
|
||||
|
||||
%% 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),
|
||||
@@ -98,43 +96,12 @@ translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
validate_opts(Meta, alias, [as], KV, 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(no_alias_expansion(Other),
|
||||
SR#elixir_scope{aliases=[],macro_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),
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_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;
|
||||
@@ -143,25 +110,15 @@ translate_each({ require, Meta, [Ref] }, S) ->
|
||||
translate_each({ require, Meta, [Ref, []] }, S);
|
||||
|
||||
translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_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;
|
||||
@@ -172,34 +129,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_match_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
|
||||
|
||||
@@ -209,9 +166,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) ->
|
||||
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
|
||||
{ elixir_tree_helpers:abstract_syntax(Env), 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} };
|
||||
@@ -229,7 +189,7 @@ 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;
|
||||
@@ -239,103 +199,96 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
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,
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, true } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: true is deprecated, please set it to [] instead", []),
|
||||
[];
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
{ hygiene, false } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: false is deprecated, please set it to [vars: false] instead", []),
|
||||
[{vars,false}, {imports,false}, {aliases,false}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Vars = 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);
|
||||
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 lists:keyfind(vars, 1, Hygiene) of
|
||||
{ vars, false } -> nil;
|
||||
_ ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
GetExpansion = fun(Key, New) ->
|
||||
case lists:keyfind(Key, 1, T) of
|
||||
{ Key, Bool } when is_boolean(Bool) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the ~s option for quote is deprecated, please use hygiene: [~s: ~s] instead", [Key, New, Bool]),
|
||||
Bool;
|
||||
false ->
|
||||
not(lists:keyfind(New, 1, Hygiene) == { New, false })
|
||||
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(aliases_hygiene, aliases),
|
||||
Imports = GetExpansion(imports_hygiene, 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,
|
||||
|
||||
TFile = case File of
|
||||
Scope = case File of
|
||||
keep -> S#elixir_scope.file;
|
||||
_ -> File
|
||||
end,
|
||||
|
||||
TExprs = if
|
||||
is_binary(TFile) ->
|
||||
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
|
||||
QExprs = if
|
||||
is_binary(Scope) ->
|
||||
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
|
||||
File == nil ->
|
||||
Exprs;
|
||||
true ->
|
||||
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, false } -> false;
|
||||
_ -> true
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
|
||||
{ binding, B } when is_list(B) -> { B, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=TLine, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports},
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
elixir_quote:quote(TExprs, Q, SL);
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
|
||||
{ TExprs, TS };
|
||||
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
|
||||
@@ -348,7 +301,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);
|
||||
@@ -369,7 +322,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
|
||||
|
||||
%% Super (exceptionally supports partial application)
|
||||
|
||||
translate_each({ super, Meta, Args } = Original, S) ->
|
||||
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
@@ -380,8 +333,6 @@ translate_each({ super, Meta, Args } = Original, S) ->
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) ->
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
@@ -402,7 +353,7 @@ translate_each({ 'super?', Meta, [] }, S) ->
|
||||
%% Variables
|
||||
|
||||
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
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, Meta, Value }, S };
|
||||
error ->
|
||||
@@ -415,7 +366,7 @@ translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_a
|
||||
|
||||
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)]);
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
|
||||
@@ -433,10 +384,12 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
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 local ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
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;
|
||||
_ ->
|
||||
@@ -467,9 +420,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;
|
||||
@@ -482,14 +449,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
case atom_to_list(Atom) of
|
||||
[H|_] when (H >= $a andalso H =< $z); H == $_ ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]);
|
||||
_ ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated", [Atom])
|
||||
end,
|
||||
|
||||
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 ->
|
||||
@@ -503,7 +463,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
|
||||
translate_each({ Invalid, Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
|
||||
['Elixir.Macro':to_binary(Invalid)]);
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
%% Literals
|
||||
|
||||
@@ -512,6 +472,25 @@ translate_each(Literal, S) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
|
||||
true -> { Opts, S };
|
||||
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
|
||||
end,
|
||||
validate_opts(Meta, Kind, Allowed, Expanded, TS),
|
||||
{ Expanded, TS }.
|
||||
|
||||
skip_expansion('Elixir.Kernel') -> true;
|
||||
skip_expansion('Elixir.Kernel.Typespec') -> true;
|
||||
skip_expansion(_) -> false.
|
||||
|
||||
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
|
||||
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
|
||||
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
|
||||
literal_opts(_) -> false.
|
||||
|
||||
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])
|
||||
@@ -520,34 +499,10 @@ 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]).
|
||||
|
||||
translate_fn(Meta, Clauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
%% Quote
|
||||
|
||||
Transformer = fun({ ArgsWithGuards, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(Line, fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
|
||||
_ ->
|
||||
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) ->
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
translate_local(Meta, Name, Args, S) ->
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
|
||||
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
expand_var_context(Meta, Alias, Msg, S) ->
|
||||
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;
|
||||
@@ -555,12 +510,85 @@ expand_var_context(Meta, Alias, Msg, S) ->
|
||||
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) ->
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
%% 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) ->
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
|
||||
@@ -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, 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,41 @@ 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.
|
||||
|
||||
%% Others
|
||||
|
||||
@@ -93,7 +78,7 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
|
||||
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
|
||||
Fun == function_exported -> true;
|
||||
|
||||
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
|
||||
returns_boolean({ atom, _, Bool }) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({ 'case', _, _, Clauses }) ->
|
||||
lists:all(fun
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
-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),
|
||||
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
|
||||
Transformer = fun(X, Acc) -> each_clause(Meta, X, umergec(S, Acc)) end,
|
||||
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
|
||||
each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
|
||||
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, { '_', Meta, nil }];
|
||||
@@ -24,19 +23,19 @@ each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
||||
Condition = { '{}', Meta, Final },
|
||||
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
|
||||
|
||||
each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
|
||||
case normalize_rescue(Meta, Condition, S) of
|
||||
{ Left, Right } ->
|
||||
case Left of
|
||||
{ '_', _, _ } ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, CS);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, S);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
@@ -45,18 +44,17 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause(Meta, { 'catch', [error, FinalClause|T], FinalExpr }, CS)
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
validate_rescue_access(Meta, Condition, S),
|
||||
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
|
||||
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
|
||||
end;
|
||||
|
||||
each_clause(Meta, {rescue,_,_}, S) ->
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause(Meta, {Key,_,_}, S) ->
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
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]
|
||||
@@ -77,34 +79,25 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
_ when is_list(Right) ->
|
||||
is_valid_rescue_list(Right, S) andalso { Left, Right };
|
||||
_ ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
||||
case is_valid_rescue_list(Expanded, S) of
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
case is_valid_rescue_list(Expanded, ES) of
|
||||
true -> { Left, Expanded };
|
||||
false ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
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=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.
|
||||
rescue_guards(_, Var, nil, _) -> { Var, false };
|
||||
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
|
||||
|
||||
rescue_guards(Meta, Var, Guards, S) ->
|
||||
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
|
||||
@@ -121,7 +114,7 @@ rescue_guards(Meta, Var, Guards, S) ->
|
||||
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
|
||||
end,
|
||||
{
|
||||
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
|
||||
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
|
||||
Safe
|
||||
}.
|
||||
|
||||
@@ -242,29 +235,11 @@ 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),
|
||||
VarBinary = 'Elixir.Macro':to_string(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_string(Alias),
|
||||
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
|
||||
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
||||
|
||||
@@ -294,4 +269,4 @@ prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
|
||||
{ '__block__', Meta, [Expr|Args] };
|
||||
|
||||
prepend_to_block(Meta, Expr, Args) ->
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
|
||||
@@ -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,21 +13,16 @@ 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.
|
||||
@config nil
|
||||
nil = @config[:foo]
|
||||
|
||||
@config [foo: :bar]
|
||||
:bar = @config[:foo]
|
||||
|
||||
@mod :lists
|
||||
[1,2,3] = @mod.flatten([1,[2],3])
|
||||
[1, 2, 3] = @mod.flatten([1, [2], 3])
|
||||
|
||||
test :atom do
|
||||
exception = assert_raise RuntimeError, fn ->
|
||||
|
||||
@@ -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
|
||||
@@ -16,8 +16,8 @@ defmodule BehaviourTest do
|
||||
test :docs do
|
||||
docs = Enum.sort(Sample.__behaviour__(:docs))
|
||||
assert docs == [
|
||||
{{:bar,2},13,"Bar"},
|
||||
{{:foo,2},10,"Foo"}
|
||||
{{:bar, 2}, 13, "Bar"},
|
||||
{{:foo, 2}, 10, "Foo"}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -61,7 +60,7 @@ defmodule Binary.Chars.ListTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :basic do
|
||||
assert to_binary([ 1, "b", 3 ]) == <<1,98,3>>
|
||||
assert to_binary([ 1, "b", 3 ]) == <<1, 98, 3>>
|
||||
end
|
||||
|
||||
test :printable do
|
||||
@@ -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
|
||||
@@ -88,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
|
||||
|
||||
@@ -131,6 +130,18 @@ 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
|
||||
|
||||
defrecord Rec, value: 1
|
||||
|
||||
test :two_items_record do
|
||||
assert inspect({ Rec[value: 1], 1 }) == "{Binary.Inspect.TupleTest.Rec[value: 1],1}"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert inspect({}) == "{}"
|
||||
end
|
||||
@@ -167,7 +178,7 @@ defmodule Binary.Inspect.ListTest do
|
||||
end
|
||||
|
||||
test :non_printable do
|
||||
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
|
||||
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
|
||||
end
|
||||
|
||||
test :unproper 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
|
||||
@@ -53,7 +53,7 @@ bar
|
||||
assert "\123" == "S"
|
||||
assert "\377" == "ÿ"
|
||||
assert "\128" == "\n8"
|
||||
assert "\18" == <<1,?8>>
|
||||
assert "\18" == <<1, ?8>>
|
||||
end
|
||||
|
||||
test :hex do
|
||||
@@ -62,10 +62,10 @@ bar
|
||||
assert "\xFF" == "ÿ"
|
||||
assert "\x{A}"== "\n"
|
||||
assert "\x{E9}"== "é"
|
||||
assert "\x{10F}" == <<196,143>>
|
||||
assert "\x{10FF}" == <<225,131,191>>
|
||||
assert "\x{10FFF}" == <<240,144,191,191>>
|
||||
assert "\x{10FFFF}" == <<244,143,191,191>>
|
||||
assert "\x{10F}" == <<196, 143>>
|
||||
assert "\x{10FF}" == <<225, 131, 191>>
|
||||
assert "\x{10FFF}" == <<240, 144, 191, 191>>
|
||||
assert "\x{10FFFF}" == <<244, 143, 191, 191>>
|
||||
end
|
||||
|
||||
test :match do
|
||||
@@ -85,17 +85,25 @@ bar
|
||||
end
|
||||
|
||||
test :bitsyntax_with_utf do
|
||||
assert <<106,111,115,101>> == << "jose" :: utf8 >>
|
||||
assert <<106,111,115,101>> == << 'jose' :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << 'jose' :: utf8 >>
|
||||
|
||||
assert <<0,106,0,111,0,115,0,101>> == << "jose" :: utf16 >>
|
||||
assert <<0,106,0,111,0,115,0,101>> == << 'jose' :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << "jose" :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << 'jose' :: utf16 >>
|
||||
|
||||
assert <<106,0,111,0,115,0,101,0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106,0,111,0,115,0,101,0>> == << 'jose' :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << 'jose' :: [utf16, little] >>
|
||||
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << "jose" :: utf32 >>
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << "jose" :: utf32 >>
|
||||
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
|
||||
@@ -140,4 +148,4 @@ bar
|
||||
defp is_match?(_, _) do
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -38,11 +38,11 @@ defmodule Bitwise.OperatorsTest do
|
||||
end
|
||||
|
||||
test :band do
|
||||
assert 1 &&& 1 == 1
|
||||
assert (1 &&& 1) == 1
|
||||
end
|
||||
|
||||
test :bor do
|
||||
assert 0 ||| 1 == 1
|
||||
assert (0 ||| 1) == 1
|
||||
end
|
||||
|
||||
test :bxor do
|
||||
@@ -50,10 +50,10 @@ defmodule Bitwise.OperatorsTest do
|
||||
end
|
||||
|
||||
test :bsl do
|
||||
assert 1 <<< 1 == 2
|
||||
assert (1 <<< 1) == 2
|
||||
end
|
||||
|
||||
test :bsr do
|
||||
assert 1 >>> 1 == 0
|
||||
assert (1 >>> 1) == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule CharListTest 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 CodeTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -14,17 +14,30 @@ defmodule CodeTest do
|
||||
|
||||
Code.eval_quoted contents, [], file: "sample.ex", line: 13
|
||||
|
||||
test :eval do
|
||||
assert Code.eval("1 + 2") == { 3, [] }
|
||||
assert { 3, _ } = Code.eval("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
test :eval_string do
|
||||
assert Code.eval_string("1 + 2") == { 3, [] }
|
||||
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
end
|
||||
|
||||
test :eval_with_unnamed_scopes do
|
||||
assert { RuntimeError[], [a: RuntimeError[]] } =
|
||||
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
||||
end
|
||||
|
||||
test :eval_with_scope do
|
||||
assert Code.eval("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
end
|
||||
|
||||
test :eval_options do
|
||||
assert Code.eval_string("is_atom(:foo) and is_record(1..2, Range) and K.is_list([])", [],
|
||||
functions: [{ Kernel, [is_atom: 1] }],
|
||||
macros: [{ Kernel, [..: 2, and: 2, is_record: 2]}],
|
||||
aliases: [{K, Kernel}],
|
||||
requires: [Kernel]) == { true, [] }
|
||||
end
|
||||
|
||||
test :eval_with_requires do
|
||||
assert Code.eval("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
|
||||
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
|
||||
end
|
||||
|
||||
test :eval_quoted do
|
||||
@@ -34,67 +47,56 @@ defmodule CodeTest do
|
||||
|
||||
test :eval_quoted_with_env do
|
||||
alias :lists, as: MyList
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1,2,3]]), [], __ENV__) == { [1,2,3],[] }
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1, 2, 3]]), [], __ENV__) == { [1, 2, 3],[] }
|
||||
end
|
||||
|
||||
test :require do
|
||||
Code.require_file fixture_path("code_sample.exs")
|
||||
assert fixture_path("code_sample.exs") inlist Code.loaded_files
|
||||
assert fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) == nil
|
||||
|
||||
Code.unload_files [fixture_path("code_sample.exs")]
|
||||
refute fixture_path("code_sample.exs") inlist Code.loaded_files
|
||||
refute fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :path_manipulation do
|
||||
path = Path.expand("../binary", __FILE__)
|
||||
path = Path.join(__DIR__, "binary")
|
||||
Code.prepend_path path
|
||||
assert binary_to_list(path) inlist :code.get_path
|
||||
assert binary_to_list(path) in :code.get_path
|
||||
|
||||
Code.delete_path path
|
||||
refute binary_to_list(path) inlist :code.get_path
|
||||
refute binary_to_list(path) in :code.get_path
|
||||
end
|
||||
|
||||
test :file do
|
||||
assert :filename.absname(__FILE__) == __FILE__
|
||||
end
|
||||
|
||||
test :string_to_ast do
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: [imports: false], line: 1, do: 1 + 2 }
|
||||
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: [imports: false], line: 1, do: (1 + 2; 3 + 4) }
|
||||
assert { :error, _ } = Code.string_to_ast("a.1")
|
||||
test :string_to_quoted do
|
||||
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_quoted("a.1")
|
||||
end
|
||||
|
||||
test :string_to_ast_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_ast(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_ast!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
test :string_to_quoted_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_quoted(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_quoted!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
end
|
||||
|
||||
test :string_to_ast! do
|
||||
assert Code.string_to_ast!("1 + 2") == quote hygiene: [imports: false], line: 1, do: 1 + 2
|
||||
test :string_to_quoted! do
|
||||
assert Code.string_to_quoted!("1 + 2") == { :+, [line: 1], [1, 2] }
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
Code.string_to_ast!("a.1")
|
||||
Code.string_to_quoted!("a.1")
|
||||
end
|
||||
|
||||
assert_raise TokenMissingError, fn ->
|
||||
Code.string_to_ast!("1 +")
|
||||
Code.string_to_quoted!("1 +")
|
||||
end
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
compile = __MODULE__.__info__(:compile)
|
||||
|
||||
# Erlang has a bug that does not allow us to set the source
|
||||
# when compiling forms. In such cases, the source will be
|
||||
# under compile option. This is fixed and future Erlang
|
||||
# version will return the proper source always (source2).
|
||||
options = :proplists.get_value(:options, compile, [])
|
||||
source1 = :proplists.get_value(:source, options, nil)
|
||||
source2 = :proplists.get_value(:source, compile, nil)
|
||||
|
||||
assert (source1 || source2) == binary_to_list(__FILE__)
|
||||
assert __MODULE__.__info__(:compile)[:source] == binary_to_list(__FILE__)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
@@ -102,6 +104,20 @@ defmodule CodeTest do
|
||||
assert Keyword.get(compile, :source) != nil
|
||||
end
|
||||
|
||||
test :compile_string do
|
||||
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
|
||||
after
|
||||
:code.purge CompileSimpleSample
|
||||
:code.delete CompileSimpleSample
|
||||
end
|
||||
|
||||
test :compile_quoted do
|
||||
assert [{ CompileQuotedSample, _ }] = Code.compile_string("defmodule CompileQuotedSample, do: :ok")
|
||||
after
|
||||
:code.purge CompileQuotedSample
|
||||
:code.delete CompileQuotedSample
|
||||
end
|
||||
|
||||
test :ensure_loaded? do
|
||||
assert Code.ensure_loaded?(__MODULE__)
|
||||
refute Code.ensure_loaded?(Unknown.Module)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user