Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a24767137 | ||
|
|
ad0818da9b | ||
|
|
c0debf66ae | ||
|
|
e15c8545c3 | ||
|
|
29313e8dd5 | ||
|
|
b0032e8df5 | ||
|
|
698d1f6440 | ||
|
|
609fca162c | ||
|
|
23b0c8c5b1 | ||
|
|
eb7c5415db | ||
|
|
80b31833a4 | ||
|
|
d5f35d4905 | ||
|
|
0908e55971 | ||
|
|
164d1d0049 | ||
|
|
e8c53a8e72 | ||
|
|
e7cd2511cf | ||
|
|
7a977307c2 | ||
|
|
0eccb76930 | ||
|
|
dad0f3fb5a | ||
|
|
7ca57afbba | ||
|
|
0c6c72e8f6 | ||
|
|
41c8f83c6b | ||
|
|
1f61ba454c | ||
|
|
55d0f568da | ||
|
|
8bfe32448e | ||
|
|
19ad387943 | ||
|
|
8010e44bd4 | ||
|
|
8b203a8379 | ||
|
|
de26484bda | ||
|
|
cbef7e9629 | ||
|
|
a184b4061e | ||
|
|
f3e26c28b0 | ||
|
|
b5ad462f27 | ||
|
|
a9b2a95011 | ||
|
|
86bafe865c | ||
|
|
fc5050d122 | ||
|
|
011ad07d7d | ||
|
|
e9d13d5858 | ||
|
|
25a5dbc08b | ||
|
|
b76387a394 | ||
|
|
64c5acb923 | ||
|
|
9ed4d91f94 | ||
|
|
45df7c4ae4 | ||
|
|
73eb206c86 | ||
|
|
d08e2bb2fd | ||
|
|
9bc8925018 | ||
|
|
30d7114922 | ||
|
|
1e5633c4a1 | ||
|
|
6d44180ec2 | ||
|
|
c2ceeb9ea4 | ||
|
|
b0a5197f42 | ||
|
|
235c4d61b9 | ||
|
|
c5ffa76663 | ||
|
|
31e639f34a | ||
|
|
f1effcb485 | ||
|
|
237c485411 | ||
|
|
78ba84612c | ||
|
|
f8da2fb9c5 | ||
|
|
7665a50a2d | ||
|
|
dfb8f38ce3 | ||
|
|
cd99add62d | ||
|
|
615a54e8bf | ||
|
|
ad00d0ad6d | ||
|
|
5b53459f86 | ||
|
|
82238a8d20 | ||
|
|
66e70ac72f | ||
|
|
2d48d2ed5b | ||
|
|
8b54e28086 | ||
|
|
b2004b8e86 | ||
|
|
52ad74cdfc | ||
|
|
5f83cf9bb3 | ||
|
|
b2608e4fe7 | ||
|
|
aed52e44d1 | ||
|
|
91497b9f15 | ||
|
|
a3d85c0bad | ||
|
|
839afc4f3e | ||
|
|
abc293bff8 | ||
|
|
cbdfe4a14a | ||
|
|
46b0f7b29e | ||
|
|
52ebeb1e7d | ||
|
|
8797280b9e | ||
|
|
8a8b641271 | ||
|
|
e67ada5cfb | ||
|
|
07d7d538dc | ||
|
|
939af3b459 | ||
|
|
da784ebc1a | ||
|
|
b670450b4c | ||
|
|
03b1c70433 | ||
|
|
e04bab1d5e | ||
|
|
a97e1fabce | ||
|
|
c86ecfdf7e | ||
|
|
a90580b0d1 | ||
|
|
60e663f26c | ||
|
|
bf81156dbc | ||
|
|
a67fe46ca5 | ||
|
|
043f6c74b1 | ||
|
|
2b1f561d91 | ||
|
|
87cd24651e | ||
|
|
61f28c796a | ||
|
|
1555b79121 | ||
|
|
452a33b085 | ||
|
|
2d3a3b1ca5 | ||
|
|
ba40cb8a3e | ||
|
|
917a8ecd99 | ||
|
|
5539ad4536 | ||
|
|
a5fb3c9e0b | ||
|
|
14ead54ac6 | ||
|
|
c74a7781a8 | ||
|
|
fe66565765 | ||
|
|
0966aea8a5 | ||
|
|
ea6272f59e | ||
|
|
9d3b7bb4d2 | ||
|
|
39e5581105 | ||
|
|
3b8ac0194b | ||
|
|
21f2f0f112 | ||
|
|
92ccf4bf1d | ||
|
|
fe042c5463 | ||
|
|
910810360d | ||
|
|
90378ab53f | ||
|
|
3c6e63a962 | ||
|
|
27a787e531 | ||
|
|
f75666515a | ||
|
|
02a1538a3c | ||
|
|
b6991100f5 | ||
|
|
fdf53787cf | ||
|
|
c435dbe38d | ||
|
|
e5c13e6dd2 | ||
|
|
b1abcdcc41 | ||
|
|
d53a6a3491 | ||
|
|
37d05da0ac | ||
|
|
9407c7eedb | ||
|
|
dc0fd7675b | ||
|
|
9c0258b605 | ||
|
|
bb1dc00de4 | ||
|
|
be0564b536 | ||
|
|
97d4c02a8f | ||
|
|
4049a79b28 | ||
|
|
d7df6d20a7 | ||
|
|
1679f6b4d7 | ||
|
|
9caab33613 | ||
|
|
cae8f0933b | ||
|
|
92c3bddf7b | ||
|
|
a49c539a9d | ||
|
|
68d1bd9308 | ||
|
|
7f8a52c264 | ||
|
|
565a7656c4 | ||
|
|
dc768eb501 | ||
|
|
ee78e10089 | ||
|
|
6a5348b780 | ||
|
|
0814d0b442 | ||
|
|
c57978ba30 | ||
|
|
8e4a8e9ed6 | ||
|
|
7415382c72 | ||
|
|
41f8c4edfe | ||
|
|
09244d3d79 | ||
|
|
b6b58ffae2 | ||
|
|
e343f210f6 | ||
|
|
caaef2fb8f | ||
|
|
5cadeeea67 | ||
|
|
199ff77acc | ||
|
|
0117ea15ed | ||
|
|
3a1857c5ba | ||
|
|
eec0211521 | ||
|
|
93182b53cf | ||
|
|
0a9b58eb8f | ||
|
|
5e7b57669e | ||
|
|
50fdf47d05 | ||
|
|
6309ffb366 | ||
|
|
bc6975cdae | ||
|
|
76a1602c5f | ||
|
|
9c14d3bd10 | ||
|
|
025a31900a | ||
|
|
39d1ab741a | ||
|
|
5f9fa7014a | ||
|
|
09ea0d8e6f | ||
|
|
33792e6972 | ||
|
|
107055d4d6 | ||
|
|
532c31c009 | ||
|
|
7d426641c6 | ||
|
|
7fc5d979e0 | ||
|
|
70386a8fb9 | ||
|
|
f3bbb1ea43 | ||
|
|
d868b4d066 | ||
|
|
32b8371759 | ||
|
|
29e0fadec1 | ||
|
|
c91723b444 | ||
|
|
a4b6770174 | ||
|
|
c03d42b0cc | ||
|
|
450250704a | ||
|
|
e8d7b267b5 | ||
|
|
d525b0f189 | ||
|
|
a1ff5b6440 | ||
|
|
c2c1ebde39 | ||
|
|
6f509808e5 | ||
|
|
e667e2ec45 | ||
|
|
1d12f3a1d0 | ||
|
|
26d2df6c5f | ||
|
|
00c2db54a6 | ||
|
|
76d0adfe3c | ||
|
|
91b7eb56ab | ||
|
|
c4de5f1849 | ||
|
|
651417a424 | ||
|
|
31c34dc04e | ||
|
|
ad8587a853 | ||
|
|
0ddcba788d | ||
|
|
55451fdcdc | ||
|
|
659a4794ed | ||
|
|
d8a24ba05a | ||
|
|
3f5423837e | ||
|
|
4df91f14ee | ||
|
|
45427c3b33 | ||
|
|
665c9b3117 | ||
|
|
a04df8c543 | ||
|
|
89ab7a2bdf | ||
|
|
6c934c6f7f | ||
|
|
87ee9995d0 | ||
|
|
4e0e174063 | ||
|
|
611bdbd0bc | ||
|
|
0243bf1a40 | ||
|
|
05b223f322 | ||
|
|
c6dc65c418 | ||
|
|
ca2ea6c0bd | ||
|
|
38aaeebfbc | ||
|
|
89a6ad3a46 | ||
|
|
f35dd567a1 | ||
|
|
74c4b951a4 | ||
|
|
f932937928 | ||
|
|
56f15d9f4e | ||
|
|
7ccdb2ab29 | ||
|
|
c7d96af2de | ||
|
|
ec776c7d68 | ||
|
|
ea9b686b28 | ||
|
|
915b8d60d1 | ||
|
|
f6a24985b2 | ||
|
|
76bee630a2 | ||
|
|
66214e97b2 | ||
|
|
832405c2cf | ||
|
|
440f696f9b | ||
|
|
8a7435e571 | ||
|
|
7178b80ad1 | ||
|
|
7b719fdf61 | ||
|
|
e8f976a007 | ||
|
|
0a2f184e0e | ||
|
|
c40a37f677 | ||
|
|
20042a51ba | ||
|
|
fc249de987 | ||
|
|
6624f31472 | ||
|
|
cd900764d2 | ||
|
|
1eb1704219 | ||
|
|
ada53524ca | ||
|
|
e6e3570961 | ||
|
|
db5adc1b35 | ||
|
|
5dd7a2ba3a | ||
|
|
edfb650c94 | ||
|
|
c012456573 | ||
|
|
860c056cbe | ||
|
|
897424b185 | ||
|
|
f4c61dae09 | ||
|
|
73a76c6f7c | ||
|
|
002bf16e2e | ||
|
|
47c922e571 | ||
|
|
60edb8489c | ||
|
|
d539d606de | ||
|
|
bb02b25e49 | ||
|
|
6ce51612e0 | ||
|
|
6243bc8dc8 | ||
|
|
e39b32f235 | ||
|
|
0843ea0fbb | ||
|
|
bbd6abb168 | ||
|
|
a29d2913f7 | ||
|
|
d1edad39c9 | ||
|
|
74584ad921 | ||
|
|
47f57d65e0 | ||
|
|
ed71b57c00 | ||
|
|
0246226487 | ||
|
|
7da2a3ccc6 | ||
|
|
d41f39a53a | ||
|
|
44c81ae0ed | ||
|
|
c6c6a27baf | ||
|
|
97e70e6de1 | ||
|
|
980dbc96d7 | ||
|
|
0883c75335 | ||
|
|
5761460bd6 | ||
|
|
d881d899a4 | ||
|
|
166685caad | ||
|
|
f60bdb8d5f | ||
|
|
c3ef484d14 | ||
|
|
3cb586a672 | ||
|
|
68828523a0 | ||
|
|
3139168007 | ||
|
|
03189d05b3 | ||
|
|
aa09359def | ||
|
|
9595fb18dd | ||
|
|
d2530e44eb | ||
|
|
21b1e51909 | ||
|
|
9e96966622 | ||
|
|
845a3d1fea | ||
|
|
8b70e07b2e | ||
|
|
f6ca23c1a5 | ||
|
|
cc03382831 | ||
|
|
09e7dbf3d5 | ||
|
|
238eda25e3 | ||
|
|
a7c6b8da5c | ||
|
|
1a26ea2c19 | ||
|
|
6d6e7d7137 | ||
|
|
f7d0c8c58e | ||
|
|
4e93dab9a8 | ||
|
|
4847214766 | ||
|
|
e56df8ad70 | ||
|
|
325c98ccbd | ||
|
|
2daf136a53 | ||
|
|
70b4c060e6 | ||
|
|
620e452f9f | ||
|
|
c373edff9b | ||
|
|
1bd50f4d36 | ||
|
|
be79bbf55a | ||
|
|
89f651f628 | ||
|
|
f3f2481e71 | ||
|
|
d33463dcd5 | ||
|
|
2de2322402 | ||
|
|
f13ae1839c | ||
|
|
29ebae3739 | ||
|
|
59dc733dfc | ||
|
|
0c9d5f16e9 | ||
|
|
b18ddd440f | ||
|
|
f4d852d292 | ||
|
|
c37a244d66 | ||
|
|
bdab8045b9 | ||
|
|
e8ea1969e4 | ||
|
|
f669e2ba96 | ||
|
|
17ddb2539d | ||
|
|
9cbe498a02 | ||
|
|
edf55a8424 | ||
|
|
41bce3e068 | ||
|
|
914a8cdcd9 | ||
|
|
a333f646d4 | ||
|
|
d73c108b89 | ||
|
|
580b9292d6 | ||
|
|
07648bd075 | ||
|
|
149091a1a1 | ||
|
|
34a3b41543 | ||
|
|
ad87292fa0 | ||
|
|
2d9dcf860a | ||
|
|
e2357d4b95 | ||
|
|
8fac8cc25f | ||
|
|
ada913c7ef | ||
|
|
df04a3788e | ||
|
|
65543f0766 | ||
|
|
a73d70176a | ||
|
|
14d2ce460e | ||
|
|
363b2b9352 | ||
|
|
2ce02084ee | ||
|
|
6ab07f6f5b | ||
|
|
7d2babe272 | ||
|
|
913327749a | ||
|
|
9be2f7f3d3 | ||
|
|
09e9917fc5 | ||
|
|
4af5f2b12f | ||
|
|
14fb6d7337 | ||
|
|
ecd5b3d8ef | ||
|
|
a52bbfce8e | ||
|
|
6696184fc9 | ||
|
|
eda13d1e41 | ||
|
|
f69c2182f7 | ||
|
|
b5d48ada53 | ||
|
|
27c9cc5bbb | ||
|
|
fb211b0c44 | ||
|
|
c7c5d3af24 | ||
|
|
6e9a3fd24d | ||
|
|
34c2fb773f | ||
|
|
9bdfa41c4d | ||
|
|
85576f8fba | ||
|
|
b07fbcf8b7 | ||
|
|
453c731f8a | ||
|
|
d4dd48d2fb | ||
|
|
0f9f6152cf | ||
|
|
eb998862d7 | ||
|
|
4ab8225cfb | ||
|
|
187e1b4a49 | ||
|
|
318e068587 | ||
|
|
3d6dd50866 | ||
|
|
b3ebf924a9 | ||
|
|
cd3e6d9238 | ||
|
|
56db9fb043 | ||
|
|
455eb4c4ac | ||
|
|
897396447f | ||
|
|
d79f3ddffd | ||
|
|
a6f048b3de | ||
|
|
aa87d44cba | ||
|
|
9ba58c50ab | ||
|
|
deedadb528 | ||
|
|
96e5d8bb41 | ||
|
|
8c5eaa973b | ||
|
|
42ab7a5370 | ||
|
|
e73fce96d1 | ||
|
|
d8e414032f | ||
|
|
7362ca8fab | ||
|
|
705bf0cd90 | ||
|
|
71ca5672cc | ||
|
|
7494106933 | ||
|
|
cbc03036cb | ||
|
|
eaa52f1db3 | ||
|
|
a05d0d3b26 | ||
|
|
9fc7a1c43f | ||
|
|
d161986e93 | ||
|
|
28367d9073 | ||
|
|
04da85d19e | ||
|
|
866acc4f82 | ||
|
|
cf730f2dbf | ||
|
|
012256791d | ||
|
|
5c410d9c34 | ||
|
|
efb67a8416 | ||
|
|
126a5a61df | ||
|
|
26f5f640c9 | ||
|
|
7f3a9ed9c3 | ||
|
|
04e774a4ba | ||
|
|
38d1badb64 | ||
|
|
bb3cc518cf | ||
|
|
eaa2db1809 | ||
|
|
a38636ac08 | ||
|
|
6268dc3d22 | ||
|
|
d50405135a | ||
|
|
e5fed25efd | ||
|
|
293711ff46 | ||
|
|
1a17634de9 | ||
|
|
ae32bf1fe0 | ||
|
|
433a46a0ad | ||
|
|
b2fb6c9bb1 | ||
|
|
75de306c46 | ||
|
|
598648da1a | ||
|
|
accf75b118 | ||
|
|
0cbbefe11b | ||
|
|
17b4dbbdbb | ||
|
|
9e068fb0a2 | ||
|
|
59ca4728d1 | ||
|
|
7af4364121 | ||
|
|
e38173602a | ||
|
|
9d149a3070 | ||
|
|
9158cad331 | ||
|
|
00b1aadd76 | ||
|
|
c1fedcc643 | ||
|
|
2f7972c57a | ||
|
|
ef1b2ea49c | ||
|
|
69f2d5083f | ||
|
|
10b1f56ad9 | ||
|
|
a6f171d183 | ||
|
|
21a231a914 | ||
|
|
9e9583c29c | ||
|
|
f6a513c71c | ||
|
|
e1f9b3f291 | ||
|
|
3f2458f7e3 | ||
|
|
fb916430f5 | ||
|
|
c9402713c9 | ||
|
|
85b8fcf2d9 | ||
|
|
13d0a4d3cf | ||
|
|
9c1c424ed2 | ||
|
|
2121ce1875 | ||
|
|
deba7e39a3 | ||
|
|
b1a6049622 | ||
|
|
4f0e1c7ef9 | ||
|
|
024edf3d40 | ||
|
|
6a725ab91f | ||
|
|
323a7eef1f | ||
|
|
233a3ed4ec | ||
|
|
26480d5755 | ||
|
|
ab4f1013d3 | ||
|
|
5913097c17 | ||
|
|
fd8d1f1e4c | ||
|
|
9ebe851b38 | ||
|
|
af26e9fd69 | ||
|
|
c6e8a905f5 | ||
|
|
a67411324b | ||
|
|
cb81c6b5c7 | ||
|
|
ccf5182e73 | ||
|
|
97e044cff5 | ||
|
|
e47417912d | ||
|
|
128d82fde6 | ||
|
|
61170bda23 | ||
|
|
89cc6feea0 | ||
|
|
409fec08d3 | ||
|
|
2cc4fd2eb5 | ||
|
|
d94e7b595e | ||
|
|
1339462c82 | ||
|
|
7f8265f85c | ||
|
|
2e8c53c85b | ||
|
|
933834aedd | ||
|
|
013303a44e | ||
|
|
bab252f1e8 | ||
|
|
655e54b551 | ||
|
|
24bf9d3f9a | ||
|
|
dab33cbddc | ||
|
|
ce73493cc5 | ||
|
|
3984ed9da3 | ||
|
|
a42a0188a1 | ||
|
|
f5660dad92 | ||
|
|
440bfdc3b4 | ||
|
|
d66a3aea39 | ||
|
|
db76866309 | ||
|
|
d0dca93fb2 | ||
|
|
0d66824965 | ||
|
|
fd8859abdd | ||
|
|
bf3852b7bd | ||
|
|
3512c1a860 | ||
|
|
8e38540470 | ||
|
|
33985326b1 | ||
|
|
cbe041bdda | ||
|
|
20b14b3221 | ||
|
|
dd4ede4c1d | ||
|
|
9f89ac920c | ||
|
|
d7f8f28ed9 | ||
|
|
4637b57977 | ||
|
|
ac61ddb09a | ||
|
|
141f0a3ebf | ||
|
|
207c626a6c | ||
|
|
7308d81729 | ||
|
|
177a900be3 | ||
|
|
cb48e7670e | ||
|
|
a5889cffc0 | ||
|
|
933b3af54c | ||
|
|
8fcb832edc | ||
|
|
16c90ef97d | ||
|
|
8084f0a6bf | ||
|
|
335d82e607 | ||
|
|
c8813445c2 | ||
|
|
c51a2a5462 | ||
|
|
d0b28f4828 | ||
|
|
1e9cd551ce | ||
|
|
8e5fec95fa | ||
|
|
969d48ccaf | ||
|
|
7715a09374 | ||
|
|
24203f8d9d | ||
|
|
2b04f540e2 | ||
|
|
f2c914926d | ||
|
|
11c4c2791d | ||
|
|
4051cf53dc | ||
|
|
543dfdeac8 | ||
|
|
45c99aee82 | ||
|
|
2d25a6fb25 | ||
|
|
aaf63a73b1 | ||
|
|
bacb1a530a | ||
|
|
c151739415 | ||
|
|
d44e8bd1f6 | ||
|
|
2a742e69e6 | ||
|
|
5bda5d503b | ||
|
|
e871f7a9ca | ||
|
|
0aa4be5086 | ||
|
|
6fed5068ce | ||
|
|
b1b1cd3263 | ||
|
|
55563dac1c | ||
|
|
548f24bbff | ||
|
|
b478462fe2 | ||
|
|
ec350645a4 | ||
|
|
e4aa6e1bdf | ||
|
|
f7f06010ae | ||
|
|
ea34b7a302 | ||
|
|
73dc4536b0 | ||
|
|
2b298ab9fc | ||
|
|
66ff417183 | ||
|
|
c28b9b0ab4 | ||
|
|
8058afe9c6 | ||
|
|
b6f80fc05f | ||
|
|
920e056465 | ||
|
|
6346029cf9 | ||
|
|
822dcd0557 | ||
|
|
5ab85c7621 | ||
|
|
dbb6f358cc | ||
|
|
3a36c2e6ae | ||
|
|
7e021d9bbb | ||
|
|
6448de834c | ||
|
|
4a4a407644 | ||
|
|
f4d145c3a0 | ||
|
|
0bacbaeff7 | ||
|
|
1844aec63e | ||
|
|
baabbdec28 | ||
|
|
2137c28e4e | ||
|
|
79d8dd0020 | ||
|
|
9bce9e4fde | ||
|
|
232a545d19 | ||
|
|
7a96e8131c | ||
|
|
675a2a60c8 | ||
|
|
b8c25bc258 | ||
|
|
f76a867c75 | ||
|
|
c1deb99b70 | ||
|
|
1625bdb454 | ||
|
|
0776cc5a64 | ||
|
|
371e96e2a8 | ||
|
|
9aa41361a6 | ||
|
|
f17dec2c74 | ||
|
|
b982822fdc | ||
|
|
50820dfc9c | ||
|
|
ee0fb39822 | ||
|
|
9760baff9c | ||
|
|
7641a5eded | ||
|
|
f7cd14d0f6 | ||
|
|
f3aad51922 | ||
|
|
b29603f1b7 | ||
|
|
afb04a19f9 | ||
|
|
848218cdac | ||
|
|
6a1cef052f | ||
|
|
455076446b | ||
|
|
50e9ae6ca7 | ||
|
|
6e83cb8d82 | ||
|
|
1f0861771a | ||
|
|
4917bfca29 | ||
|
|
b46481e12f | ||
|
|
e4c2fc2ac1 | ||
|
|
99e69bd6c5 | ||
|
|
4d366f4688 | ||
|
|
daa0049b32 | ||
|
|
bc06c5e43f | ||
|
|
da1541c791 | ||
|
|
1ce2433212 | ||
|
|
8cecbf5e9d | ||
|
|
a1b53d35ea | ||
|
|
de71ecf195 | ||
|
|
05a85a8e9c | ||
|
|
5a618258a3 | ||
|
|
ba54afbf45 | ||
|
|
2af50f7b78 | ||
|
|
1ea44670ce | ||
|
|
ff0df96cfc | ||
|
|
3476f224b7 | ||
|
|
e612592e21 | ||
|
|
cfbd5bbb7f | ||
|
|
46e000394b | ||
|
|
875b95f609 | ||
|
|
ffdf3d32b2 | ||
|
|
1fa74df6c7 | ||
|
|
cb9c17c025 | ||
|
|
923eeb9602 | ||
|
|
c5ff2d1dcf | ||
|
|
c65dcc9b19 | ||
|
|
15dd4d3c07 | ||
|
|
058d55b68c | ||
|
|
22ebd5f2d6 | ||
|
|
8744abc39f | ||
|
|
1574b9380b | ||
|
|
66441fb2cb | ||
|
|
2466307b74 | ||
|
|
6a5b25045c | ||
|
|
742a922b90 | ||
|
|
2e2d534257 | ||
|
|
271700692a | ||
|
|
6bc699672b | ||
|
|
c07b0d0912 | ||
|
|
61cac4eebc | ||
|
|
b723da6574 | ||
|
|
f87e5d3c12 |
+1
-3
@@ -6,6 +6,4 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- R16B02
|
||||
- R16B01
|
||||
- R16B
|
||||
- 17.0
|
||||
|
||||
+164
-2
@@ -1,3 +1,165 @@
|
||||
# v0.13.2 (2014-05-11)
|
||||
|
||||
* Enhancements
|
||||
* [Application] Add an Application module with common functions to work with OTP applications
|
||||
* [Exception] Add `Exception.message/1`, `Exception.format_banner/1`, `Exception.format_exit/1` and `Exception.format/1`
|
||||
* [File] Add `File.ln_s/1`
|
||||
* [Mix] `mix deps.clean` now works accross environments
|
||||
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
|
||||
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
|
||||
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
|
||||
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
|
||||
* [Mix] `mix help` now uses ANSI formatting to print guides
|
||||
* [Regex] Support functions in `Regex.replace/4`
|
||||
* [String] Support `:parts` in `String.split/3`
|
||||
|
||||
* Bug fixes
|
||||
* [Code] Ensure we don't lose the caller stacktrace on code evaluation
|
||||
* [IEx] Exit signals now exits the IEx evaluator and a new one is spawned on its place
|
||||
* [IEx] Ensure we don't prune too much stacktrace when reporting failures
|
||||
* [IEx] Fix an issue where `iex.bat` on Windows was not passing the proper parameters forward
|
||||
* [Kernel] Ensure modules defined on root respect defined aliases
|
||||
* [Kernel] Do not wrap single lists in `:__block__`
|
||||
* [Kernel] Ensure emitted beam code works nicely with dialyzer
|
||||
* [Kernel] Do not allow a module named `Elixir` to be defined
|
||||
* [Kernel] Create remote funs even if mod is a variable in capture `&mod.fun/arity`
|
||||
* [Kernel] Improve compiler message when duplicated modules are detected
|
||||
* [Mix] Generate `.gitignore` for `--umbrella` projects
|
||||
* [Mix] Verify if a git dependency in deps has a proper git checkout and clean it automatically when it doesn't
|
||||
* [Mix] Ensure `mix test` works with `IEx.pry/0`
|
||||
* [System] Convert remaining functions in System to rely on char data
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Application] `use Application.Behaviour` is deprecated in favor of `use Application`
|
||||
* [Exception] `exception.message` is deprecated in favor `Exception.message/1` for retrieving exception messages
|
||||
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
|
||||
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
|
||||
* [Process] `Process.spawn/1`, `Process.spawn/3`, `Process.spawn_link/1`, `Process.spawn_link/3`, `Process.spawn_monitor/1`, `Process.spawn_monitor/3`, `Process.send/2` and `Process.self/0` are deprecated in favor of the ones in `Kernel`
|
||||
|
||||
* Deprecations
|
||||
* [IEx] IEx.Options is deprecated in favor of `IEx.configure/1` and `IEx.configuration/0`
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for`
|
||||
* [Macro] `Macro.safe_terms/1` is deprecated
|
||||
* [Process] `Process.delete/0` is deprecated
|
||||
* [Regex] Deprecate `:global` option in `Regex.split/3` in favor of `parts: :infinity`
|
||||
* [String] Deprecate `:global` option in `String.split/3` in favor of `parts: :infinity`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] `ExUnit.Test` and `ExUnit.TestCase` has been converted to structs
|
||||
* [ExUnit] The test and callback context has been converted to maps
|
||||
* [Kernel] `File.Stat`, `HashDict`, `HashSet`, `Inspect.Opts`, `Macro.Env`, `Range`, `Regex` and `Version.Requirement` have been converted to structs. This means `is_record/2` checks will no longer work, instead, you can pattern match on them using `%Range{}` and similar
|
||||
* [URI] The `URI.Info` record has now become the `URI` struct
|
||||
* [Version] The `Version.Schema` record has now become the `Version` struct
|
||||
|
||||
# v0.13.1 (2014-04-27)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
|
||||
* [Mix] Support Hex out of the box. This means users do not need to install Hex directly, instead, Mix will prompt whenever there is a need to have Hex installed
|
||||
|
||||
* Bug fixes
|
||||
* [ExUnit] Ensure doctest failures are properly reported
|
||||
* [Kernel] Fix a bug where comprehensions arguments were not properly take into account in the variable scope
|
||||
* [Mix] Fix issue on rebar install when the endpoint was redirecting to a relative uri
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `iolist_size` and `iolist_to_binary` are deprecated in favor of `iodata_length` and `iodata_to_binary`
|
||||
* [String] `String.to_char_list/1` is deprecated in favor of `List.from_char_data/1`
|
||||
* [String] `String.from_char_list/1` is deprecated in favor of `String.from_char_data/1`
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `:env` key in project configuration is deprecated
|
||||
* [Regex] `Regex.groups/1` is deprecated in favor of `Regex.names/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Macro] `Macro.unpipe/1` now returns tuples and `Macro.pipe/2` was removed in favor of `Macro.pipe/3` which explicitly expects the second element of the tuple returned by the new `Macro.unpipe/1`
|
||||
* [Path] The functions in Path now only emit strings as result, regardless if the input was a char list or a string
|
||||
* [Path] Atoms are no longer supported in Path functions
|
||||
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
|
||||
|
||||
# v0.13.0 (2014-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
|
||||
* [Code] Add `Code.eval_file/2`
|
||||
* [Collectable] Add the `Collectable` protocol that empowers `Enum.into/2` and `Stream.into/2` and the `:into` option in comprehensions
|
||||
* [Collectable] Implement `Collectable` for lists, dicts, bitstrings, functions and provide both `File.Stream` and `IO.Stream`
|
||||
* [EEx] Add `handle_body/1` callback to `EEx.Engine`
|
||||
* [Enum] Add `Enum.group_by/2`, `Enum.into/2`, `Enum.into/3`, `Enum.traverse/2` and `Enum.sum/2`
|
||||
* [ExUnit] Randomize cases and tests suite runs, allow seed configuration and the `--seed` flag via `mix test`
|
||||
* [ExUnit] Support `--only` for filtering when running tests with `mix test`
|
||||
* [ExUnit] Raise an error if another `capture_io` process already captured the device
|
||||
* [ExUnit] Improve formatter to show source code and rely on lhs and rhs (instead of expected and actual)
|
||||
* [IEx] Allow prompt configuration with the `:prompt` option
|
||||
* [IEx] Use werl on Windows
|
||||
* [Kernel] Support `ERL_PATH` in `bin/elixir`
|
||||
* [Kernel] Support interpolation in keyword syntax
|
||||
* [Map] Add a Map module and support 17.0 maps and structs
|
||||
* [Mix] Add dependency option `:only` to specify the dependency environment. `mix deps.get` and `mix deps.update` works accross all environment unless `--only` is specified
|
||||
* [Mix] Add `Mix.Shell.prompt/1`
|
||||
* [Mix] Ensure the project is compiled in case Mix' CLI cannot find a task
|
||||
* [Node] Add `Node.ping/1`
|
||||
* [Process] Include `Process.send/3` and support the `--gen-debug` option
|
||||
* [Regex] Regexes no longer need the "g" option when there is a need to use named captures
|
||||
* [Stream] Add `Stream.into/2` and `Stream.into/3`
|
||||
* [StringIO] Add a `StringIO` module that allows a String to be used as IO device
|
||||
* [System] Add `System.delete_env/1` to remove a variable from the environment
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Ensure `--app` is handled as an atom before processing
|
||||
* [ExUnit] Ensure `ExUnit.Assertions` does not emit compiler warnings for `assert_receive`
|
||||
* [Kernel] Ensure the same pid is not queued twice in the parallel compiler
|
||||
* [Macro] `Macro.to_string/2` considers proper precedence when translating `!(foo > bar)` into a string
|
||||
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
|
||||
* [Mix] Ensure generated `.app` file includes core dependencies
|
||||
* [Mix] Allow a dependency with no SCM to be overridden
|
||||
* [Mix] Allow queries in `mix local.install` URL
|
||||
* [OptionParser] Do not recognize undefined aliases as switches
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for`
|
||||
* [ListDict] `ListDict` is deprecated in favor of `Map`
|
||||
* [Record] `defrecord/2`, `defrecordp/3`, `is_record/1` and `is_record/2` macros in Kernel are deprecated. Instead, use the new macros and API defined in the `Record` module
|
||||
|
||||
* Deprecations
|
||||
* [Dict] `Dict.empty/1`, `Dict.new/1` and `Dict.new/2` are deprecated
|
||||
* [Exception] `Exception.normalize/1` is deprecated in favor of `Exception.normalize/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
|
||||
# v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
|
||||
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
|
||||
|
||||
* Deprecations
|
||||
* [File] `File.stream_to!/3` is deprecated
|
||||
* [GenFSM] `GenFSM` is deprecated
|
||||
* [Kernel] `%` for sigils is deprecated in favor of `~`
|
||||
* [Kernel] `is_range/1` and `is_regex/1` are deprecated in favor of `Range.range?/1` and `Regex.regex?/1`
|
||||
* [Stream] `Stream.after/1` is deprecated
|
||||
* [URI] `URI.decode_query/1` is deprecated in favor of `URI.decode_query/2` with explicit dict argument
|
||||
* [URI] Passing lists as key or values in `URI.encode_query/1` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
|
||||
|
||||
# v0.12.4 (2014-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `//` for default arguments is deprecated in favor of `\\`
|
||||
* [Kernel] Using `%` for sigils is deprecated in favor of `~`. This is a soft deprecation, no warnings will be emitted for it in this release
|
||||
* [Kernel] Using `^` inside function clause heads is deprecated, please use a guard instead
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
|
||||
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
|
||||
|
||||
# v0.12.3 (2014-02-02)
|
||||
|
||||
* Enhancements
|
||||
@@ -710,7 +872,7 @@
|
||||
* [ExUnit] Support context data in `setup_all`, `setup`, `teardown` and `teardown_all` callbacks
|
||||
* [IEx] Support `after_spawn` callbacks which are invoked after each process is spawned
|
||||
* [Kernel] Better error messages when invalid options are given to `import`, `alias` or `require`
|
||||
* [Kernel] Allow partial application on literals, for example: `{ &1, &2 }` to build tuples or `[&1|&2]` to build cons cells
|
||||
* [Kernel] Allow partial application on literals, for example: `{&1, &2}` to build tuples or `[&1|&2]` to build cons cells
|
||||
* [Kernel] Added `integer_to_binary` and `binary_to_integer`
|
||||
* [Kernel] Added `float_to_binary` and `binary_to_float`
|
||||
* [Kernel] Many improvements to `unquote` and `unquote_splicing`. For example, `unquote(foo).unquote(bar)(args)` is supported and no longer need to be written via `apply`
|
||||
@@ -763,7 +925,7 @@
|
||||
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
|
||||
* [Kernel] Deprecate `in_guard` in favor of `__CALLER__.in_guard?`
|
||||
* [Kernel] `refer` is deprecated in favor of `alias`
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, { target, callback })`
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, {target, callback})`
|
||||
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
|
||||
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
|
||||
|
||||
|
||||
+28
-17
@@ -34,16 +34,15 @@ Guidelines for bug reports:
|
||||
reported.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `master` or development branch in the repository.
|
||||
`master` branch in the repository.
|
||||
|
||||
3. **Isolate the problem** — ideally create a reduced test
|
||||
3. **Isolate and report 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.
|
||||
Please try to be as detailed as possible in your report. Include information about
|
||||
your Operating System, your Erlang and Elixir versions. Please provide steps to
|
||||
reproduce the issue as well as the outcome you were expecting! All these details
|
||||
will help developers to fix any potential bugs.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -67,8 +66,8 @@ Example:
|
||||
|
||||
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
|
||||
out whether your idea fits with the scope and aims of the project. It's up to *you*
|
||||
to make a strong case to convince the project's developers of the merits of this
|
||||
feature. Please provide as much detail and context as possible.
|
||||
to make a strong case to convince the community of the merits of this feature.
|
||||
Please provide as much detail and context as possible.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -91,6 +90,16 @@ 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`.
|
||||
|
||||
In case you are changing a single file, you can compile and run tests only
|
||||
for that particular file for fast development cycles. For example, if you
|
||||
are changing the String module, you can compile it and run its tests as:
|
||||
|
||||
$ bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
|
||||
$ bin/elixir lib/elixir/test/elixir/string_test.exs
|
||||
|
||||
After your changes are done, please remember to run the full suite with
|
||||
`make test`.
|
||||
|
||||
From time to time, your tests may fail in an existing Elixir checkout and
|
||||
may require a clean start by running `make clean compile`. You can always
|
||||
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
|
||||
@@ -108,23 +117,25 @@ something like:
|
||||
|
||||
```elixir
|
||||
@doc """
|
||||
Return only those elements for which `fun` is true.
|
||||
Returns only those elements for which `fun` is true.
|
||||
|
||||
...
|
||||
"""
|
||||
def filter(collection, fun) ...
|
||||
```
|
||||
|
||||
For modules, records, protocols and types say what it is. For example write
|
||||
For modules, protocols and types say what it is. For example write
|
||||
something like:
|
||||
|
||||
```elixir
|
||||
defrecord File.Stat, [...] do
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
Information about a file.
|
||||
|
||||
...
|
||||
"""
|
||||
|
||||
defstruct [...]
|
||||
end
|
||||
```
|
||||
|
||||
@@ -154,7 +165,7 @@ def filter(collection, fun) ...
|
||||
```
|
||||
|
||||
This makes it easy to test the examples so that they don't go stale and examples
|
||||
are often a great help in explaining what a function does.
|
||||
are often a great help in explaining what a function does.
|
||||
|
||||
## Pull requests
|
||||
|
||||
@@ -165,10 +176,10 @@ 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.
|
||||
If you have any large pull request in mind (e.g. implementing features,
|
||||
refactoring code, etc), **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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
REBAR := rebar
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
@@ -11,10 +11,20 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Templates
|
||||
#==> Functions
|
||||
|
||||
# This check should work for older versions like R16B
|
||||
# as well as new verions like 17.1 and 18
|
||||
define CHECK_ERLANG_RELEASE
|
||||
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release)])' -s erlang halt | grep -q '^1[789]'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang 17.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
endef
|
||||
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
@@ -46,6 +56,7 @@ default: compile
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) $(call CHECK_ERLANG_RELEASE)
|
||||
$(Q) rm -rf lib/elixir/src/elixir.app.src
|
||||
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
|
||||
>lib/elixir/src/elixir.app.src
|
||||
@@ -61,7 +72,7 @@ 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
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@@ -85,14 +96,14 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
|
||||
install: compile
|
||||
@ echo "==> elixir (install)"
|
||||
for dir in lib/*; do \
|
||||
$(Q) for dir in lib/*; do \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
done
|
||||
|
||||
@@ -127,17 +138,13 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
|
||||
release_erl: compile
|
||||
$(Q) rm -rf rel/elixir
|
||||
$(Q) cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
test: test_erlang test_elixir
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
=========
|
||||
[](http://travis-ci.org/elixir-lang/elixir)
|
||||
[](http://travis-ci.org/elixir-lang/elixir)
|
||||
|
||||
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
|
||||
|
||||
@@ -16,9 +16,9 @@ If Elixir fails to build (specifically when pulling in a new version via git), b
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
|
||||
+7
-9
@@ -4,25 +4,23 @@ This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4) Commit changes above with title "Release vVERSION"
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5) Push master and create tag vVERSION from master branch
|
||||
5) Push master and tags
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
8) Merge master into stable branch and push it
|
||||
|
||||
9) Merge master into stable branch and push it
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
11) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+21
-7
@@ -17,6 +17,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -34,6 +35,7 @@ readlink_f () {
|
||||
fi
|
||||
}
|
||||
|
||||
MODE="elixir"
|
||||
ERL=""
|
||||
I=1
|
||||
|
||||
@@ -41,7 +43,13 @@ while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
+iex)
|
||||
MODE="iex"
|
||||
;;
|
||||
+elixirc)
|
||||
MODE="elixirc"
|
||||
;;
|
||||
-v|--compile|--no-halt)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--app)
|
||||
S=2
|
||||
@@ -64,6 +72,9 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -73,11 +84,14 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
|
||||
else
|
||||
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
ERL_PATH="$SCRIPT_PATH"/erl
|
||||
else
|
||||
ERL_PATH=erl
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
|
||||
+20
-7
@@ -1,11 +1,14 @@
|
||||
@echo off
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc%== 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto parseopts
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
if "%%A"=="-h" goto documentation
|
||||
if "%%A"=="/h" goto documentation
|
||||
set /A argc+=1
|
||||
)
|
||||
if %argc%==0 goto documentation
|
||||
goto parseopts
|
||||
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
@@ -24,6 +27,7 @@ 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 --gen-debug Turns on default debugging for all GenServers
|
||||
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
|
||||
@@ -44,6 +48,9 @@ set originPath=%~dp0
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Flag which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
@@ -56,6 +63,8 @@ if "%par%"=="""" (
|
||||
rem if no parameters defined - special case for parameter that is already quoted
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
@@ -63,6 +72,7 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
@@ -84,5 +94,8 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
|
||||
+1
-1
@@ -26,4 +26,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir +compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir +elixirc "$@"
|
||||
|
||||
+9
-6
@@ -1,11 +1,14 @@
|
||||
@echo off
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
if "%%A"=="-h" goto documentation
|
||||
if "%%A"=="/h" goto documentation
|
||||
set /A argc+=1
|
||||
)
|
||||
if %argc%==0 goto documentation
|
||||
goto run
|
||||
|
||||
:documentation
|
||||
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
|
||||
echo.
|
||||
@@ -20,4 +23,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +compile %*
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -16,13 +16,14 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -38,4 +39,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
|
||||
|
||||
+19
-21
@@ -1,12 +1,12 @@
|
||||
defexception EEx.SyntaxError, message: nil
|
||||
|
||||
defmodule EEx do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way:
|
||||
|
||||
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
|
||||
## API
|
||||
|
||||
@@ -31,14 +31,13 @@ defmodule EEx do
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
Defaults to the given file the template is read from
|
||||
or to "nofile" when compiling from a string;
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
Defaults to `EEx.Engine`;
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -79,12 +78,12 @@ defmodule EEx do
|
||||
An example is the `@` macro which allows easy data access
|
||||
in a template:
|
||||
|
||||
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
#=> 1
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
|
||||
<%= Keyword.get assigns, :foo %>
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
The assigns extension is useful when the number of variables
|
||||
required by the template is not specified at compilation time.
|
||||
@@ -97,18 +96,18 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Sample do
|
||||
require EEx
|
||||
EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
end
|
||||
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
iex> defmodule Sample do
|
||||
...> require EEx
|
||||
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
...> end
|
||||
iex> Sample.sample(1, 2)
|
||||
"3"
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
|
||||
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
|
||||
args = Enum.map args, fn arg -> {arg, [line: info[:line]], nil} end
|
||||
compiled = EEx.compile_string(source, info)
|
||||
|
||||
case kind do
|
||||
@@ -144,7 +143,7 @@ defmodule EEx do
|
||||
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge options, [file: file, line: 1]
|
||||
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@file file
|
||||
@@ -177,8 +176,8 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
|
||||
"""
|
||||
def eval_string(source, bindings \\ [], options \\ []) do
|
||||
@@ -195,8 +194,7 @@ defmodule EEx do
|
||||
foo <%= bar %>
|
||||
|
||||
# iex
|
||||
EEx.eval_file "sample.ex", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
EEx.eval_file "sample.ex", [bar: "baz"] #=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_file(filename, bindings \\ [], options \\ []) do
|
||||
@@ -208,7 +206,7 @@ defmodule EEx do
|
||||
### Helpers
|
||||
|
||||
defp do_eval(compiled, bindings, options) do
|
||||
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
|
||||
{result, _} = Code.eval_quoted(compiled, bindings, options)
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
+49
-44
@@ -1,57 +1,62 @@
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
|
||||
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
# when changing this setting, don't forget to update the docs for EEx
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
@doc """
|
||||
This is the compilation entry point. It glues the tokenizer
|
||||
and the engine together by handling the tokens and invoking
|
||||
the engine every time a full expression or text is received.
|
||||
"""
|
||||
def compile(source, options) do
|
||||
line = Keyword.get(options, :line, 1)
|
||||
def compile(source, opts) do
|
||||
file = opts[:file] || "nofile"
|
||||
line = opts[:line] || 1
|
||||
tokens = EEx.Tokenizer.tokenize(source, line)
|
||||
state = EEx.State.new(options)
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
end
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
|
||||
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, chars)
|
||||
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
defp generate_buffer([{:expr, line, mark, chars}|t], buffer, scope, state) do
|
||||
expr = Code.string_to_quoted!(chars, [line: line, file: state.file])
|
||||
buffer = state.engine.handle_expr(buffer, mark, expr)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
|
||||
defp generate_buffer([{:start_expr, start_line, mark, chars}|t], buffer, scope, state) do
|
||||
{contents, line, t} = look_ahead_text(t, start_line, chars)
|
||||
{contents, t} = generate_buffer(t, "", [contents|scope],
|
||||
%{state | quoted: [], line: line, start_line: start_line})
|
||||
buffer = state.engine.handle_expr(buffer, mark, contents)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
generate_buffer(t, "", [wrapped|scope], state.line(line))
|
||||
defp generate_buffer([{:middle_expr, line, _, chars}|t], buffer, [current|scope], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
generate_buffer(t, "", [wrapped|scope], %{state | line: line})
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|t], buffer, [current|_], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
|
||||
buffer = insert_quotes(tuples, state.dict)
|
||||
{ buffer, t }
|
||||
buffer = insert_quoted(tuples, state.quoted)
|
||||
{buffer, t}
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|_], _buffer, [], _state) do
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], _state) do
|
||||
buffer
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
state.engine.handle_body(buffer)
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, _state) do
|
||||
@@ -62,50 +67,50 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.quoted)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{current ++ placeholder ++ new_lines ++ chars,
|
||||
%{state | quoted: [{key, buffer}|state.quoted]}}
|
||||
end
|
||||
|
||||
if state.dict == [] and empty?(buffer) do
|
||||
{ current ++ new_lines ++ chars, state }
|
||||
# Look text ahead on expressions
|
||||
|
||||
defp look_ahead_text([{:text, text}, {:middle_expr, line, _, chars}|t]=list, start, contents) do
|
||||
if only_spaces?(text) do
|
||||
{contents ++ text ++ chars, line, t}
|
||||
else
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
{contents, start, list}
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
defp look_ahead_text(t, start, contents) do
|
||||
{contents, start, t}
|
||||
end
|
||||
|
||||
defp empty?({ :<>, _, [left, right] }) do
|
||||
empty?(left) and empty?(right)
|
||||
end
|
||||
|
||||
defp empty?(_) do
|
||||
false
|
||||
defp only_spaces?(chars) do
|
||||
Enum.all?(chars, &(&1 in [?\s, ?\t, ?\r, ?\n]))
|
||||
end
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
|
||||
{ ^key, value } = List.keyfind dict, key, 0
|
||||
defp insert_quoted({:__EEX__, _, [key]}, quoted) do
|
||||
{^key, value} = List.keyfind quoted, key, 0
|
||||
value
|
||||
end
|
||||
|
||||
defp insert_quotes({ left, line, right }, dict) do
|
||||
{ insert_quotes(left, dict), line, insert_quotes(right, dict) }
|
||||
defp insert_quoted({left, line, right}, quoted) do
|
||||
{insert_quoted(left, quoted), line, insert_quoted(right, quoted)}
|
||||
end
|
||||
|
||||
defp insert_quotes({ left, right }, dict) do
|
||||
{ insert_quotes(left, dict), insert_quotes(right, dict) }
|
||||
defp insert_quoted({left, right}, quoted) do
|
||||
{insert_quoted(left, quoted), insert_quoted(right, quoted)}
|
||||
end
|
||||
|
||||
defp insert_quotes(list, dict) when is_list(list) do
|
||||
Enum.map list, &insert_quotes(&1, dict)
|
||||
defp insert_quoted(list, quoted) when is_list(list) do
|
||||
Enum.map list, &insert_quoted(&1, quoted)
|
||||
end
|
||||
|
||||
defp insert_quotes(other, _dict) do
|
||||
defp insert_quoted(other, _quoted) do
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
An engine needs to implement two functions:
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression;
|
||||
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression;
|
||||
@@ -10,11 +14,11 @@ defmodule EEx.Engine do
|
||||
the marker, the expr and must return a new quoted expression;
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `'='`
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
|
||||
* `''`
|
||||
* `'='`
|
||||
* `""`
|
||||
* `"="`
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
@@ -22,9 +26,18 @@ defmodule EEx.Engine do
|
||||
|
||||
use Behaviour
|
||||
|
||||
defcallback handle_body(Macro.t) :: Macro.t
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
"""
|
||||
def handle_body(quoted) do
|
||||
quoted
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation simply concatenates text to the buffer.
|
||||
"""
|
||||
@@ -54,4 +67,4 @@ defmodule EEx.Engine do
|
||||
tmp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,10 @@ defmodule EEx.TransformerEngine do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
@@ -23,23 +27,23 @@ defmodule EEx.TransformerEngine do
|
||||
EEx.Engine.handle_expr(buffer, mark, transform(expr))
|
||||
end
|
||||
|
||||
defp transform({ a, b, c }) do
|
||||
{ transform(a), b, transform(c) }
|
||||
defp transform({a, b, c}) do
|
||||
{transform(a), b, transform(c)}
|
||||
end
|
||||
|
||||
defp transform({ a, b }) do
|
||||
{ transform(a), transform(b) }
|
||||
defp transform({a, b}) do
|
||||
{transform(a), transform(b)}
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
lc i inlist list, do: transform(i)
|
||||
for i <- list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defoverridable [transform: 1, handle_expr: 3, handle_text: 2]
|
||||
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,8 +63,8 @@ defmodule EEx.AssignsEngine do
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
#=> 1
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
In the example above, we can access the value `foo` under
|
||||
the binding `assigns` using `@foo`. This is useful when
|
||||
@@ -87,7 +91,7 @@ defmodule EEx.AssignsEngine do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
|
||||
@@ -5,66 +5,67 @@ defmodule EEx.Tokenizer do
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* { :text, line, contents }
|
||||
* { :expr, line, marker, contents }
|
||||
* { :start_expr, line, marker, contents }
|
||||
* { :end_expr, line, marker, contents }
|
||||
* {:text, contents}
|
||||
* {:expr, line, marker, contents}
|
||||
* {:start_expr, line, marker, contents}
|
||||
* {:middle_expr, line, marker, contents}
|
||||
* {:end_expr, line, marker, contents}
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(String.to_char_list!(bin), line)
|
||||
tokenize(List.from_char_data!(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
Enum.reverse(tokenize(list, line, line, [], []))
|
||||
Enum.reverse(tokenize(list, line, [], []))
|
||||
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
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
{ _, new_line, rest } = tokenize_expr t, line, []
|
||||
tokenize rest, current_line, new_line, buffer, acc
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{_, new_line, rest} = tokenize_expr t, line, []
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, current_line, line, buffer, acc) do
|
||||
{ marker, t } = retrieve_marker(t)
|
||||
{ expr, new_line, rest } = tokenize_expr t, line, []
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{marker, t} = retrieve_marker(t)
|
||||
{expr, new_line, rest} = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(current_line, buffer, acc)
|
||||
final = { token, line, marker, Enum.reverse(expr) }
|
||||
tokenize rest, new_line, new_line, [], [final | acc]
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line + 1, [?\n|buffer], acc
|
||||
defp tokenize('\n' ++ t, line, buffer, acc) do
|
||||
tokenize t, line + 1, [?\n|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([h|t], current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line, [h|buffer], acc
|
||||
defp tokenize([h|t], line, buffer, acc) do
|
||||
tokenize t, line, [h|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([], current_line, _line, buffer, acc) do
|
||||
tokenize_text(current_line, buffer, acc)
|
||||
defp tokenize([], _line, buffer, acc) do
|
||||
tokenize_text(buffer, acc)
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
|
||||
defp retrieve_marker('=' ++ t) do
|
||||
{ "=", t }
|
||||
{"=", t}
|
||||
end
|
||||
|
||||
defp retrieve_marker(t) do
|
||||
{ "", t }
|
||||
{"", t}
|
||||
end
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
{buffer, line, t}
|
||||
end
|
||||
|
||||
defp tokenize_expr('\n' ++ t, line, buffer) do
|
||||
@@ -103,7 +104,7 @@ defmodule EEx.Tokenizer do
|
||||
# token and, if so, it is not followed by an "end"
|
||||
# token. If this is the case, we are on a start expr.
|
||||
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
|
||||
{ :ok, _line, tokens } ->
|
||||
{:ok, _line, tokens} ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
@@ -129,31 +130,32 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
defp fn_index(tokens) do
|
||||
Enum.find_index tokens, fn
|
||||
{ :fn_paren, _ } -> true
|
||||
{ :fn, _ } -> true
|
||||
{:fn_paren, _} -> true
|
||||
{:fn, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp end_index(tokens) do
|
||||
Enum.find_index(tokens, &match?({ :end, _ }, &1)) || :infinity
|
||||
Enum.find_index(tokens, &match?({:end, _}, &1)) || :infinity
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
if only_spaces?(string), do: token, else: :expr
|
||||
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
|
||||
token
|
||||
else
|
||||
:expr
|
||||
end
|
||||
end
|
||||
|
||||
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
|
||||
defp only_spaces?(other), do: other == []
|
||||
|
||||
# Tokenize the buffered text by appending
|
||||
# it to the given accumulator.
|
||||
|
||||
defp tokenize_text(_line, [], acc) do
|
||||
defp tokenize_text([], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
|
||||
defp tokenize_text(buffer, acc) do
|
||||
[{:text, Enum.reverse(buffer)} | acc]
|
||||
end
|
||||
end
|
||||
|
||||
+3
-1
@@ -2,6 +2,8 @@ defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version, build_per_environment: false]
|
||||
[app: :eex,
|
||||
version: System.version,
|
||||
build_per_environment: false]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,17 +7,16 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a ListDict" do
|
||||
test "evaluates with assigns as keywords" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a HashDict" do
|
||||
assigns = HashDict.new [foo: 1]
|
||||
assert_eval "1", "<%= @foo %>", assigns: assigns
|
||||
test "evaluates with assigns as a map" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
|
||||
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", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ { :text, 1, "foo" } ]
|
||||
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ { :text, 1, "foo" } ]
|
||||
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -33,67 +33,67 @@ baz %>
|
||||
'''
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
{:text, 1, "foo "},
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, 3, "\n"},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, 4, "\n"}
|
||||
{:text, '\n'}
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
{ :text, 1, "foo <% true %>" }
|
||||
{:text, 'foo <% true %>'}
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
{ :text, 1, "foo <% true do %>bar<% end %>" }
|
||||
{:text, 'foo <% true do %>bar<% end %>'}
|
||||
]
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
{ :text, 1, "foo " }
|
||||
{:text, 'foo '}
|
||||
]
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
{ :text, 1, "foo bar" }
|
||||
{:text, 'foo bar'}
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, "", ' cond do ' },
|
||||
{ :middle_expr, 1, "", ' false -> ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :middle_expr, 1, "", ' true -> ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' cond do '},
|
||||
{:middle_expr, 1, "", ' false -> '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :middle_expr, 1, "", ' else ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' else '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
+51
-19
@@ -5,9 +5,9 @@ require EEx
|
||||
defmodule EExText.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
{ :erlang, 1, 2 }.tuple_to_list
|
||||
{:erlang, 1, 2}.tuple_to_list
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
@@ -16,20 +16,20 @@ defmodule EExText.Compiled do
|
||||
|
||||
def after_compile do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
@file "unknown"
|
||||
def unknown do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
try do
|
||||
:erlang.error "failed"
|
||||
catch
|
||||
:error, _, stack -> stack
|
||||
:error, _ -> System.stacktrace
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,6 +45,9 @@ end
|
||||
defmodule EExTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest EEx
|
||||
doctest EEx.AssignsEngine
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
@@ -279,6 +282,15 @@ foo
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "for comprehensions" do
|
||||
string = """
|
||||
<%= for _name <- packages || [] do %>
|
||||
<% end %>
|
||||
<%= all || :done %>
|
||||
"""
|
||||
assert_eval "\ndone\n", string, packages: nil, all: nil
|
||||
end
|
||||
|
||||
test "unicode" do
|
||||
template = """
|
||||
• <%= "•" %> •
|
||||
@@ -317,35 +329,55 @@ foo
|
||||
|
||||
test "defined from file do not affect backtrace" do
|
||||
assert EExText.Compiled.before_compile ==
|
||||
{ 8,
|
||||
{ EExText.Compiled,
|
||||
{8,
|
||||
{EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.after_compile ==
|
||||
{ 19,
|
||||
{ EExText.Compiled,
|
||||
{19,
|
||||
{EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.unknown ==
|
||||
{ 25,
|
||||
{ EExText.Compiled,
|
||||
{25,
|
||||
{EExText.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
defmodule TestEngine do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
{:wrapped, body}
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
test "calls handle_body" do
|
||||
assert {:wrapped, "foo"} = EEx.eval_string("foo", [], engine: TestEngine)
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
-define(wrap_call(Line, Module, Function, Args),
|
||||
{ call, Line,
|
||||
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
|
||||
Args
|
||||
}).
|
||||
|
||||
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
|
||||
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
|
||||
-define(m(M, K), maps:get(K, M)).
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and do_match
|
||||
extra=nil, %% extra information about the context, like fn_match and map_key
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
return=true, %% when true, the return value is used
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
@@ -25,24 +19,6 @@
|
||||
file=(<<"nofile">>) %% the current scope filename
|
||||
}).
|
||||
|
||||
-record(elixir_env, {
|
||||
module=nil, %% the current module
|
||||
file=(<<"nofile">>), %% the current filename
|
||||
line=1, %% the current line
|
||||
function=nil, %% the current function
|
||||
context=nil, %% can be match_vars, guards or nil
|
||||
requires=[], %% a set with modules required
|
||||
aliases=[], %% an orddict with aliases by new -> old names
|
||||
functions=[], %% a list with functions imported from module
|
||||
macros=[], %% a list with macros imported from module
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
context_modules=[], %% modules defined in the current context
|
||||
vars=[], %% a set of defined variables
|
||||
export_vars=nil, %% a set of variables to be exported in certain constructs, may be nil
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
local=nil %% the module to delegate local functions to
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=false,
|
||||
keep=false,
|
||||
|
||||
+33
-22
@@ -7,6 +7,22 @@ defprotocol Access do
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes the `Access.access` protocol.
|
||||
|
||||
This protocol is implemented by default for Lists, Maps
|
||||
and dictionary like types:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
1
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> map[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = %{1.0 => "★", 1.5 => "★☆", 2.0 => "★★"}
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
The key access must be implemented using the `===` operator.
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
@@ -18,33 +34,28 @@ defprotocol Access do
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a tuple list.
|
||||
The key is found via the `===` operator.
|
||||
def access(dict, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{^key, value} -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
## Examples
|
||||
def access(_dict, key) do
|
||||
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
...> keywords[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
...> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access(dict, key)
|
||||
def access([{ key, value }|_], key), do: value
|
||||
def access([{ _, _ }|t], key), do: access(t, key)
|
||||
def access([], _key), do: nil
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
@doc """
|
||||
The access protocol can only be accessed by atoms
|
||||
at compilation time. If we reach this, we should raise
|
||||
an exception.
|
||||
"""
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
defmodule Application do
|
||||
@moduledoc """
|
||||
A module for working with applications and defining application callbacks.
|
||||
|
||||
In Elixir (actually, in Erlang/OTP), an application is a component
|
||||
implementing some specific functionality, that can be started and stopped
|
||||
as a unit, and which can be re-used in other systems as well.
|
||||
|
||||
Applications are defined with an application file named `APP.app` where
|
||||
`APP` is the APP name, usually in `underscore_case` convention. The
|
||||
application file must reside in the same `ebin` directory as the
|
||||
application's modules bytecode.
|
||||
|
||||
In Elixir, Mix is responsible for compiling your source code and
|
||||
generating your application `.app` file. Furthermore, Mix is also
|
||||
responsible for configuring, starting and stoping your application
|
||||
and its dependencies. For this reason, this documentation will focus
|
||||
on the remaining aspects of your application: the application environment,
|
||||
and the application callback module.
|
||||
|
||||
You can learn more about Mix compilation of `.app` files by typing
|
||||
`mix help compile.app`.
|
||||
|
||||
## Application environment
|
||||
|
||||
Once an application is started, OTP provides an application environment
|
||||
that can be used to configure applications.
|
||||
|
||||
Assuming you are inside a Mix project, you can edit your application
|
||||
function in the `mix.exs` file to the following:
|
||||
|
||||
def application do
|
||||
[env: [hello: :world]]
|
||||
end
|
||||
|
||||
In the application function, we can define the default environment values
|
||||
for our application. By starting your application with `iex -S mix`, you
|
||||
can access the default value:
|
||||
|
||||
Application.get_env(:APP_NAME, :hello)
|
||||
#=> {:ok, :hello}
|
||||
|
||||
It is also possible to put and delete values from the application value,
|
||||
including new values that are not defined in the environment file (although
|
||||
those should be avoided).
|
||||
|
||||
In the future, we plan to support configuration files which allows
|
||||
developers to configure the environment of their dependencies.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the fucntions in this module for directly access or modify
|
||||
the environment of other application (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
## Application module callback
|
||||
|
||||
Often times, an application defines a supervision tree that must be started
|
||||
and stopped when the application starts and stops. For such, we need to
|
||||
define an application module callback. The first step is to define the
|
||||
module callback in the application definition in the `mix.exs` file:
|
||||
|
||||
def application do
|
||||
[mod: {MyApp, []}]
|
||||
end
|
||||
|
||||
Our application now requires the `MyApp` module to provide an application
|
||||
callback. This can be done by invoking `use Application` in that module
|
||||
and defining a `start/2` callback, for example:
|
||||
|
||||
defmodule MyApp do
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
MyApp.Supervisor.start_link()
|
||||
end
|
||||
end
|
||||
|
||||
`start/2` most commonly returns `{:ok, pid}` or `{:ok, pid, state}` where
|
||||
`pid` identifies the supervision tree and the state is the application state.
|
||||
`args` is second element of the tuple given to the `:mod` option.
|
||||
|
||||
The `type` passed into `start/2` is usually `:normal` unless in a distributed
|
||||
setup where applications takeover and failovers are configured. This particular
|
||||
aspect of applications can be read with more detail in the OTP documentation:
|
||||
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
application state and can return any value. Notice that shutting down the
|
||||
supervisor is automatically handled by the VM;
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
|
||||
@type app :: atom
|
||||
@type key :: atom
|
||||
@type value :: term
|
||||
@type start_type :: :permanent | :transient | :temporary
|
||||
|
||||
@doc """
|
||||
Returns all key-value pairs for `app`.
|
||||
"""
|
||||
@spec get_all_env(app) :: [{key,value}]
|
||||
def get_all_env(app) do
|
||||
:application.get_all_env(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for `key` in `app`'s environment.
|
||||
|
||||
If the specified application is not loaded, or the configuration parameter
|
||||
does not exist, the function returns the `default` value.
|
||||
"""
|
||||
@spec get_env(app, key, value) :: value
|
||||
def get_env(app, key, default \\ nil) do
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, value} -> value
|
||||
:undefined -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for `key` in `app`'s environment in a tuple.
|
||||
|
||||
If the specified application is not loaded, or the configuration parameter
|
||||
does not exist, the function returns `:error`.
|
||||
"""
|
||||
@spec fetch_env(app, key) :: {:ok, value} | :error
|
||||
def fetch_env(app, key) do
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, value} -> {:ok, value}
|
||||
:undefined -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the `value` in `key` for the given `app`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms);
|
||||
|
||||
* `:persistent` - persists the given value on application load and reloads;
|
||||
|
||||
If `put_env/4` is called before the application is loaded, the application
|
||||
environment values specified in the `.app` file will override the ones
|
||||
previously set.
|
||||
|
||||
The persistent option can be set to true when there is a need to guarantee
|
||||
parameters set with this function will not be overridden by the ones defined
|
||||
in the application resource file on load. This means persistent values will
|
||||
stick after the application is loaded and also on application reload.
|
||||
"""
|
||||
@spec put_env(app, key, value, [timeout: timeout, persistent: boolean]) :: :ok
|
||||
def put_env(app, key, value, opts \\ []) do
|
||||
:application.set_env(app, key, value, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the `key` from the given `app` environment.
|
||||
|
||||
See `put_env/4` for a description of the options.
|
||||
"""
|
||||
@spec delete_env(app, key, [timeout: timeout, persistent: boolean]) :: :ok
|
||||
def delete_env(app, key, opts \\ []) do
|
||||
:application.unset_env(app, key, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given `app` is started.
|
||||
|
||||
Same as `start/2` but returns `:ok` if the application was already
|
||||
started. This is useful in scripts and in test setup, where test
|
||||
applications need to be explicitly started:
|
||||
|
||||
:ok = Application.ensure_started(:my_test_dep)
|
||||
|
||||
"""
|
||||
@spec ensure_started(app, start_type) :: :ok | {:error, term}
|
||||
def ensure_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_started(app, type)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given `app` and its applications are started.
|
||||
|
||||
Same as `start/2` but also starts the applications listed under
|
||||
`:applications` in the `.app` file in case they were not previously
|
||||
started.
|
||||
"""
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
|
||||
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_all_started(app, type)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts the given `app`.
|
||||
|
||||
If the `app` is not loaded, the application will first be loaded using `load/1`.
|
||||
Any included application, defined in the `:included_applications` key of the
|
||||
`.app` file will also be loaded, but they won't be started.
|
||||
|
||||
Furthermore, all applications listed in the `:applications` key must be explicitly
|
||||
started before this application is. If not, `{:error, {:not_started, app}}` is
|
||||
returned, where `app` is the name of the missing application.
|
||||
|
||||
In case you want to automatically load **and start** all of `app`'s dependencies,
|
||||
see `ensure_all_started/2`.
|
||||
|
||||
The `type` argument specifies the type of the application:
|
||||
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated;
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application terminates
|
||||
abnormally, all other applications and the entire node are also terminated;
|
||||
* `:temporary` - if `app` termiantes, it is reported but no other applications
|
||||
are terminated (the default);
|
||||
|
||||
Note that it is always possible to stop an application explicitly by calling
|
||||
`stop/1`. Regardless of the type of the application, no other applications will
|
||||
be affected.
|
||||
|
||||
Note also that the `:transient` type is of little practical use, since when a
|
||||
supervision tree terminates, the reason is set to `:shutdown`, not `:normal`.
|
||||
"""
|
||||
@spec start(app, start_type) :: :ok | {:error, term}
|
||||
def start(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.start(app, type)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the given `app`.
|
||||
|
||||
When stopped, the application is still loaded.
|
||||
"""
|
||||
@spec stop(app) :: :ok | {:error, term}
|
||||
def stop(app) do
|
||||
:application.stop(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the given `app`.
|
||||
|
||||
In order to be loaded, an `.app` file must be in the load paths.
|
||||
All `:included_applications` will also be loaded.
|
||||
|
||||
Loading the application does not start it nor load its modules, but
|
||||
it does load its environment.
|
||||
"""
|
||||
@spec load(app) :: :ok | {:error, term}
|
||||
def load(app) when is_atom(app) do
|
||||
:application.load(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unloads the given `app`.
|
||||
|
||||
It will also unload all `:included_applications`.
|
||||
Note that the function does not purge the application modules.
|
||||
"""
|
||||
@spec unload(app) :: :ok | {:error, term}
|
||||
def unload(app) when is_atom(app) do
|
||||
:application.unload(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the directory for app.
|
||||
|
||||
This information is returned based on the code path. Here is an
|
||||
example:
|
||||
|
||||
File.mkdir_p!("foo/ebin")
|
||||
Code.prepend_path("foo/ebin")
|
||||
Application.app_dir(:foo)
|
||||
#=> "foo"
|
||||
|
||||
Even though the directory is empty and there is no `.app` file
|
||||
it is considered the application directory based on the name
|
||||
"foo/ebin". The name may contain a dash `-` which is considered
|
||||
to be the app version and it is removed for the lookup purposes:
|
||||
|
||||
File.mkdir_p!("bar-123/ebin")
|
||||
Code.prepend_path("bar-123/ebin")
|
||||
Application.app_dir(:bar)
|
||||
#=> "bar-123"
|
||||
|
||||
For more information on code paths, check the `Code` module in
|
||||
Elixir and also Erlang's `:code` module.
|
||||
"""
|
||||
@spec app_dir(app) :: String.t
|
||||
def app_dir(app) when is_atom(app) do
|
||||
case :code.lib_dir(app) do
|
||||
lib when is_list(lib) -> String.from_char_data!(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, message: "unknown application: #{inspect app}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given path inside `app_dir/1`.
|
||||
"""
|
||||
@spec app_dir(app, String.t) :: String.t
|
||||
def app_dir(app, path) when is_binary(path) do
|
||||
Path.join(app_dir(app), path)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the error reason returned by `start/2`,
|
||||
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
|
||||
returns a string.
|
||||
"""
|
||||
@spec format_error(any) :: String.t
|
||||
def format_error(reason) do
|
||||
try do
|
||||
impl_format_error(reason)
|
||||
catch
|
||||
# A user could create an error that looks like a builtin one
|
||||
# causing an error.
|
||||
:error, _ ->
|
||||
inspect(reason)
|
||||
end
|
||||
end
|
||||
|
||||
# exit(:normal) call is special cased, undo the special case.
|
||||
defp impl_format_error({{:EXIT, :normal}, {mod, :start, args}}) do
|
||||
Exception.format_exit({:normal, {mod, :start, args}})
|
||||
end
|
||||
|
||||
# {:error, reason} return value
|
||||
defp impl_format_error({reason, {mod, :start, args}}) do
|
||||
Exception.format_mfa(mod, :start, args) <> " returned an error: " <>
|
||||
Exception.format_exit(reason)
|
||||
end
|
||||
|
||||
# error or exit(reason) call, use exit reason as reason.
|
||||
defp impl_format_error({:bad_return, {{mod, :start, args}, {:EXIT, reason}}}) do
|
||||
Exception.format_exit({reason, {mod, :start, args}})
|
||||
end
|
||||
|
||||
# bad return value
|
||||
defp impl_format_error({:bad_return, {{mod, :start, args}, return}}) do
|
||||
Exception.format_mfa(mod, :start, args) <>
|
||||
" returned a bad value: " <> inspect(return)
|
||||
end
|
||||
|
||||
defp impl_format_error({:already_started, app}) when is_atom(app) do
|
||||
"already started application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:not_started, app}) when is_atom(app) do
|
||||
"not started application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:bad_application, app}) do
|
||||
"bad application: #{inspect(app)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:already_loaded, app}) when is_atom(app) do
|
||||
"already loaded application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:not_loaded, app}) when is_atom(app) do
|
||||
"not loaded application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:invalid_restart_type, restart}) do
|
||||
"invalid application restart type: #{inspect(restart)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:invalid_name, name}) do
|
||||
"invalid application name: #{inspect(name)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:invalid_options, opts}) do
|
||||
"invalid application options: #{inspect(opts)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:badstartspec, spec}) do
|
||||
"bad application start specs: #{inspect(spec)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({'no such file or directory', file}) do
|
||||
"could not find application file: #{file}"
|
||||
end
|
||||
|
||||
defp impl_format_error(reason) do
|
||||
Exception.format_exit(reason)
|
||||
end
|
||||
end
|
||||
@@ -1,96 +1,8 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc """
|
||||
Default callbacks for applications.
|
||||
@moduledoc false
|
||||
|
||||
In Erlang/OTP, an application is a component that can be started
|
||||
and stopped as a unit, and which can be reused in other systems.
|
||||
|
||||
The first step in creating an application is to define an application specification.
|
||||
For example, if your application is named `:my_app`, an app specification
|
||||
should exist at `ebin/my_app.app`. This file is usually defined by
|
||||
build tools like Mix.
|
||||
|
||||
With the app specification in hand, we must define
|
||||
application module callbacks that control how to start and stop
|
||||
instances of the application. This module is about defining such callbacks.
|
||||
|
||||
There are two callbacks which must be implemented:
|
||||
|
||||
1. `start(type, args)` - must return `{ :ok, pid }` or
|
||||
`{ :ok, pid, state }`, where `pid` is the process identifier
|
||||
of the supervisor tree root and `state` is application defined
|
||||
state information;
|
||||
|
||||
2. `stop(state)` - receives the `state` returned by `start` and should
|
||||
do any necessary clean up. Notice that shutting down the supervisor
|
||||
is automatically handled by the VM;
|
||||
|
||||
When using this module, it tags the module behaviour as
|
||||
`:application` and provides a default `stop/1` callback. The `start/2` callback
|
||||
still needs to be implemented by the user.
|
||||
|
||||
You can learn more about the `:application` module, the application
|
||||
specification and the application module callbacks from these sources:
|
||||
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://learnyousomeerlang.com/building-otp-applications
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyApp do
|
||||
use Application.Behaviour
|
||||
|
||||
def start(_type, args) do
|
||||
MyApp.Sup.start_link(args)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
# Starts the given application and all of its dependencies that
|
||||
# have not been started yet recursively.
|
||||
#
|
||||
# ## Supported types
|
||||
#
|
||||
# When starting an application, a type can be given:
|
||||
#
|
||||
# * `:permanent` - If a permanent application terminates, all other
|
||||
# applications and the runtime system are also terminated;
|
||||
# * `:transient` - If a transient application terminates with reason
|
||||
# `:normal`, this is reported but no other applications are terminated.
|
||||
# If a transient application terminates abnormally, all other
|
||||
# applications and the runtime system are also terminated;
|
||||
# * `:temporary` - If a temporary application terminates, this is reported
|
||||
# but no other applications are terminated.
|
||||
#
|
||||
# The type only applies to the application being started. Its dependencies
|
||||
# are all started with default type (which is :temporary).
|
||||
#
|
||||
# Note that transient mode is of little practical use, since when a
|
||||
# supervision tree terminates, the reason is set to shutdown, not normal.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# Application.Behaviour.start(:my_app)
|
||||
#
|
||||
@doc false
|
||||
def start(app, type \\ :temporary) do
|
||||
case :application.start(app, type) do
|
||||
{ :error, { :not_started, dep } } ->
|
||||
case start(dep) do
|
||||
:ok -> start(app, type)
|
||||
other -> other
|
||||
end
|
||||
{ :error, { :already_started, _ } } ->
|
||||
:ok
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
# IO.write :stderr, "use Application.Behaviour is deprecated, please use Application instead\n#{Exception.format_stacktrace}"
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
defmodule Base do
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
This module provides data encoding and decoding functions
|
||||
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
|
||||
|
||||
This document defines the commonly used base 64, base 32, and base
|
||||
16 encoding schemes.
|
||||
"""
|
||||
|
||||
b16_alphabet = Enum.with_index '0123456789ABCDEF'
|
||||
b64_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
b64url_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
|
||||
Enum.each [ {:enc16, :dec16, b16_alphabet},
|
||||
{:enc64, :dec64, b64_alphabet},
|
||||
{:enc32, :dec32, b32_alphabet},
|
||||
{:enc64url, :dec64url, b64url_alphabet},
|
||||
{:enc32hex, :dec32hex, b32hex_alphabet} ], fn({enc, dec, alphabet}) ->
|
||||
for {encoding, value} <- alphabet do
|
||||
defp unquote(enc)(unquote(value)), do: unquote(encoding)
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
end
|
||||
defp unquote(dec)(c) do
|
||||
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 16 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode16("foobar")
|
||||
"666F6F626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary) :: binary
|
||||
def encode16(data) when is_binary(data) do
|
||||
do_encode16(data, &enc16/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16("666F6F626172")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: {:ok, binary} | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{:ok, decode16!(string)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16!("666F6F626172")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
def decode16!(string) when is_binary(string) do
|
||||
do_decode16(string, &dec16/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 64 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode64("foobar")
|
||||
"Zm9vYmFy"
|
||||
|
||||
"""
|
||||
@spec encode64(binary) :: binary
|
||||
def encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64("Zm9vYmFy")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode64(binary) :: {:ok, binary} | :error
|
||||
def decode64(string) when is_binary(string) do
|
||||
{:ok, do_decode64(string, &dec64/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64!("Zm9vYmFy")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode64!(binary) :: binary
|
||||
def decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 64 encoded string with URL and filename
|
||||
safe alphabet.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_encode64(<<255,127,254,252>>)
|
||||
"_3_-_A=="
|
||||
|
||||
"""
|
||||
@spec url_encode64(binary) :: binary
|
||||
def url_encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64url/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64("_3_-_A==")
|
||||
{:ok, <<255,127,254,252>>}
|
||||
|
||||
"""
|
||||
@spec url_decode64(binary) :: {:ok, binary} | :error
|
||||
def url_decode64(string) when is_binary(string) do
|
||||
{:ok, do_decode64(string, &dec64url/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64!("_3_-_A==")
|
||||
<<255,127,254,252>>
|
||||
|
||||
"""
|
||||
@spec url_decode64!(binary) :: binary
|
||||
def url_decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64url/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode32("foobar")
|
||||
"MZXW6YTBOI======"
|
||||
|
||||
"""
|
||||
@spec encode32(binary) :: binary
|
||||
def encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32("MZXW6YTBOI======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: {:ok, binary} | :error
|
||||
def decode32(string) do
|
||||
{:ok, do_decode32(string, &dec32/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32!("MZXW6YTBOI======")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
def decode32!(string) do
|
||||
do_decode32(string, &dec32/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string with an
|
||||
extended hexadecimal alphabet.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_encode32("foobar")
|
||||
"CPNMUOJ1E8======"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary) :: binary
|
||||
def hex_encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32hex/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32("CPNMUOJ1E8======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: {:ok, binary} | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{:ok, do_decode32(string, &dec32hex/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32!("CPNMUOJ1E8======")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
def hex_decode32!(string) when is_binary(string) do
|
||||
do_decode32(string, &dec32hex/1)
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
defp do_encode16(data, enc) do
|
||||
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
end
|
||||
|
||||
defp do_decode16(<<>>, _), do: <<>>
|
||||
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec.(c1)::4, dec.(c2)::4>>
|
||||
end
|
||||
end
|
||||
defp do_decode16(_, _) do
|
||||
raise ArgumentError, message: "odd-length string"
|
||||
end
|
||||
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
|
||||
<<c1::6, c2::2>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
split = byte_size(string) - 4
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode64(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::1>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::3>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
bsr(dec.(c5), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode32(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
end
|
||||
|
||||
end
|
||||
+28
-89
@@ -1,6 +1,6 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
Utilities for defining behaviour intefaces.
|
||||
Utilities for defining behaviour interfaces.
|
||||
|
||||
Behaviours can be referenced by other modules
|
||||
to ensure they implement required callbacks.
|
||||
@@ -12,7 +12,7 @@ defmodule Behaviour do
|
||||
use Behaviour
|
||||
|
||||
@doc "Parses the given URL"
|
||||
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
|
||||
defcallback parse(uri_info :: URI.t) :: URI.t
|
||||
|
||||
@doc "Defines a default port"
|
||||
defcallback default_port() :: integer
|
||||
@@ -46,62 +46,55 @@ defmodule Behaviour do
|
||||
@doc """
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defcallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback(fun) do
|
||||
do_defcallback(fun, quote(do: term), [], __CALLER__)
|
||||
defmacro defcallback(spec) do
|
||||
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defmacrocallback(fun, return, guard, __CALLER__)
|
||||
defmacro defmacrocallback(spec) do
|
||||
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
|
||||
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
|
||||
{spec, return, guard}
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
defp split_spec({:when, _, [spec, guard]}, default) do
|
||||
{spec, default, guard}
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
defp split_spec({:::, _, [spec, return]}, _default) do
|
||||
{spec, return, []}
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
defp split_spec(spec, default) do
|
||||
{spec, default, []}
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
defp do_defcallback({spec, return, guards}, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{name, args} ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
defp do_defmacrocallback({spec, return, guards}, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{name, args} ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
{:::, _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
@@ -111,79 +104,25 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
quote do
|
||||
docs_args = unquote(Macro.escape(docs_args))
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
|
||||
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
|
||||
unquote(docs_name), docs_args, unquote(docs_arity))
|
||||
unquote(docs_name), unquote(docs_arity))
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default({ :\\, _, [_, _] }) do
|
||||
defp ensure_not_default({:\\, _, [_, _]}) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, kind, name, args, arity) do
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
|
||||
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
|
||||
{ simplify_signature(arg, ix + 1), [line: line], nil }
|
||||
end)
|
||||
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
|
||||
Module.put_attribute module, :behaviour_docs, {{name, arity}, line, kind, doc}
|
||||
end
|
||||
|
||||
defp simplify_signature({ :::, _, [var, _] }, i) do
|
||||
simplify_signature(var, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :|, _, [first, _] }, i) do
|
||||
simplify_signature(first, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :., _, [_, name] }, _i) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
|
||||
:"fun#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
|
||||
:"range#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
|
||||
:"bitstring#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
|
||||
:"tuple#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ name, _, args }, _i)
|
||||
when is_atom(name) and (is_atom(args) or is_list(args)) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
|
||||
simplify_signature(ast, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
|
||||
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
|
||||
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
|
||||
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
|
||||
defp simplify_signature(_, i), do: :"arg#{i}"
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
This module provide macros and operators for bitwise operators.
|
||||
This module provides macros and operators for bitwise operators.
|
||||
These macros can be used in guards.
|
||||
|
||||
The easiest way to use is to simply import them into
|
||||
@@ -15,7 +15,7 @@ defmodule Bitwise do
|
||||
You can select to include only or skip operators by passing options:
|
||||
|
||||
iex> use Bitwise, only_operators: true
|
||||
...> 1 &&& 1
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
"""
|
||||
|
||||
+108
-110
@@ -28,7 +28,7 @@ defmodule Code do
|
||||
allowing them to be required again.
|
||||
"""
|
||||
def unload_files(files) do
|
||||
:elixir_code_server.cast { :unload_files, files }
|
||||
:elixir_code_server.cast {:unload_files, files}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -37,7 +37,7 @@ defmodule Code do
|
||||
The path is expanded with `Path.expand/1` before being appended.
|
||||
"""
|
||||
def append_path(path) do
|
||||
:code.add_pathz(Path.expand to_char_list(path))
|
||||
:code.add_pathz(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -46,7 +46,7 @@ defmodule Code do
|
||||
The path is expanded with `Path.expand/1` before being prepended.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
:code.add_patha(Path.expand to_char_list(path))
|
||||
:code.add_patha(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -55,15 +55,15 @@ defmodule Code do
|
||||
The path is expanded with `Path.expand/1` before being deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
:code.del_path(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluate the contents given by `string`.
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
@@ -88,8 +88,8 @@ defmodule Code do
|
||||
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
|
||||
etc.
|
||||
|
||||
Returns a tuple of the form `{ value, binding }`,
|
||||
where `value` is the the value returned from evaluating `string`.
|
||||
Returns a tuple of the form `{value, binding}`,
|
||||
where `value` is the value returned from evaluating `string`.
|
||||
If an error occurs while evaluating `string` an exception will be raised.
|
||||
|
||||
`binding` is a keyword list with the value of all variable bindings
|
||||
@@ -117,18 +117,15 @@ defmodule Code do
|
||||
"""
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
def eval_string(string, binding, %Macro.Env{} = env) do
|
||||
{value, binding, _env, _scope} = :elixir.eval to_char_list(string), binding, Map.to_list(env)
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
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, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
{value, binding, _env, _scope} = :elixir.eval to_char_list(string), binding, opts
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -139,31 +136,28 @@ defmodule Code do
|
||||
## 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> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all options will be automatically extracted from the current environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
iex> 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
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
def eval_quoted(quoted, binding, %Macro.Env{} = env) do
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, Map.to_list(env)
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
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, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, opts
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
defp validate_eval_opts(opts) do
|
||||
@@ -182,32 +176,32 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp validate_aliases(kind, aliases) do
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn {k, v} ->
|
||||
is_atom(k) and is_atom(v)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
end
|
||||
end
|
||||
|
||||
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 } ->
|
||||
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, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{:ok, quoted_form}`
|
||||
if it succeeds, `{:error, {line, error, token}}` otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -228,17 +222,12 @@ defmodule Code do
|
||||
def string_to_quoted(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, forms }
|
||||
_ -> res
|
||||
end
|
||||
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a `TokenMissingError`
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
@@ -253,33 +242,46 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. If the file was already required/loaded, loads it again.
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
|
||||
module defined in the file.
|
||||
Evals the given file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file/2` if you don't want a file to be loaded concurrently.
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evalutes the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string File.read!(file), [], []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
If the file was already required/loaded, loads it again.
|
||||
|
||||
It returns a list of tuples `{ModuleName, <<byte_code>>}`, one tuple for
|
||||
each module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes concurrently,
|
||||
the target file will be loaded concurrently many times. Check `require_file/2`
|
||||
if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call { :acquire, file }
|
||||
:elixir_code_server.call {:acquire, file}
|
||||
loaded = :elixir_compiler.file file
|
||||
:elixir_code_server.cast { :loaded, file }
|
||||
:elixir_code_server.cast {:loaded, file}
|
||||
loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
Require the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file/2`. If
|
||||
the file was already required/loaded, doesn't do anything and returns `nil`.
|
||||
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/2`. If the file was already
|
||||
required/loaded, doesn't do anything and returns `nil`.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
@@ -287,25 +289,25 @@ defmodule Code do
|
||||
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/2` if you want a file to be loaded concurrently.
|
||||
Check `load_file/2` if you want a file to be loaded multiple times.
|
||||
"""
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case :elixir_code_server.call({ :acquire, file }) do
|
||||
case :elixir_code_server.call({:acquire, file}) do
|
||||
:loaded ->
|
||||
nil
|
||||
{ :queued, ref } ->
|
||||
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
|
||||
{:queued, ref} ->
|
||||
receive do {:elixir_code_server, ^ref, :loaded} -> nil end
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file file
|
||||
:elixir_code_server.cast { :loaded, file }
|
||||
:elixir_code_server.cast {:loaded, file}
|
||||
loaded
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the compilation options from the code server.
|
||||
Gets the compilation options from the code server.
|
||||
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
@@ -314,7 +316,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set compilation options.
|
||||
Sets compilation options.
|
||||
|
||||
These options are global since they are stored by Elixir's Code Server.
|
||||
|
||||
@@ -334,15 +336,14 @@ defmodule Code do
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
:elixir_code_server.cast { :compiler_options, opts }
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compile the given string.
|
||||
Compiles the given string.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
Returns a list of tuples where the first element is the module name
|
||||
and the second one is its byte code (as a binary).
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
@@ -351,26 +352,23 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compile the quoted expression.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
Compiles the quoted expression.
|
||||
|
||||
Returns a list of tuples where the first element is the module name and
|
||||
the second one is its byte code (as a binary).
|
||||
"""
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is loaded.
|
||||
|
||||
If the module is already
|
||||
loaded, this works as no-op. If the module was not yet loaded,
|
||||
it tries to load it.
|
||||
Ensures the given module is loaded.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
If the module is already loaded, this works as no-op. If the module
|
||||
was not yet loaded, it tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
## Code loading on the Erlang VM
|
||||
|
||||
@@ -385,60 +383,59 @@ defmodule Code do
|
||||
module uses this function to check if a specific parser exists for a given
|
||||
URI scheme.
|
||||
|
||||
## Code.ensure_compiled
|
||||
## `Code.ensure_compiled/1`
|
||||
|
||||
Elixir also contains an `ensure_compiled/1` function that is a
|
||||
superset of `ensure_loaded/1`.
|
||||
|
||||
Since Elixir's compilation happens in parallel, in some situations
|
||||
you may need to use a module but that was not yet compiled, therefore
|
||||
you may need to use a module that was not yet compiled, therefore
|
||||
it can't even be loaded.
|
||||
|
||||
`ensure_compiled/1` halts the current process until the
|
||||
module we are depending on is available.
|
||||
|
||||
In most cases, `ensure_loaded` is enough. `ensure_compiled`
|
||||
must be used in some rare cases, usually involving macros
|
||||
that need to invoke a module for callback information.
|
||||
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
|
||||
must be used in rare cases, usually involving macros that need to
|
||||
invoke a module for callback information.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is loaded.
|
||||
Ensures the given module is loaded.
|
||||
|
||||
Similar to `ensure_loaded/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded. Returns `false` otherwise.
|
||||
is already loaded or was successfully loaded. Returns `false`
|
||||
otherwise.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
match?({:module, ^module}, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
If the module
|
||||
is already loaded, it works as no-op. If the module was not
|
||||
loaded yet, it checks if it needs to be compiled first and
|
||||
then tries to load it.
|
||||
Ensures the given module is compiled and loaded.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
If the module is already loaded, it works as no-op. If the module was
|
||||
not loaded yet, it checks if it needs to be compiled first and then
|
||||
tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
"""
|
||||
def ensure_compiled(module) when is_atom(module) do
|
||||
case :code.ensure_loaded(module) do
|
||||
{ :error, :nofile } = error ->
|
||||
{:error, :nofile} = error ->
|
||||
case :erlang.get(:elixir_ensure_compiled) do
|
||||
:undefined -> error
|
||||
_ ->
|
||||
try do
|
||||
module.__info__(:module)
|
||||
{ :module, module }
|
||||
{:module, module}
|
||||
rescue
|
||||
UndefinedFunctionError -> error
|
||||
end
|
||||
@@ -448,19 +445,20 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is compiled and loaded.
|
||||
Ensures the given module is compiled and loaded.
|
||||
|
||||
Similar to `ensure_compiled/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
match?({ :module, ^module }, ensure_compiled(module))
|
||||
match?({:module, ^module}, ensure_compiled(module))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
#
|
||||
# If the file is found, returns its path in binary, fails otherwise.
|
||||
defp find_file(file, relative_to) do
|
||||
file = if relative_to do
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
defprotocol Collectable do
|
||||
@moduledoc """
|
||||
A protocol to traverse data structures.
|
||||
|
||||
The `Enum.into/2` function uses this protocol to insert an
|
||||
enumerable into a collection:
|
||||
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
In order to support a wide range of values, the functions provided by
|
||||
the `Enumerable` protocol do not keep shape. For example, passing a
|
||||
dictionary to `Enum.map/2` always returns a list.
|
||||
|
||||
This design is intentional. `Enumerable` was designed to support infinite
|
||||
collections, resources and other structures with fixed shape. For example,
|
||||
it doesn't make sense to insert values into a range, as it has a fixed
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
|
||||
The returned function receives a collectable and injects a given
|
||||
value into it for every `{:cont, term}` instruction.
|
||||
|
||||
`:done` is passed when no further values will be injected, useful
|
||||
for closing resources and normalizing values. A collectable must
|
||||
be returned on `:done`.
|
||||
|
||||
If injection is suddenly interrupted, `:halt` is passed and it can
|
||||
return any value, as it won't be used.
|
||||
"""
|
||||
@spec into(t) :: {term, (term, command -> t | term)}
|
||||
def into(collectable)
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x|list]
|
||||
list, :done -> original ++ :lists.reverse(list)
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
|
||||
bitstring, :done -> bitstring
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
map, :done -> map
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
+100
-117
@@ -1,5 +1,5 @@
|
||||
defmodule Dict do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict, allowing
|
||||
@@ -12,7 +12,7 @@ defmodule Dict do
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
In the examples below, `dict_impl` means a specific
|
||||
`Dict` implementation, for example `HashDict` or `ListDict`.
|
||||
`Dict` implementation, for example `HashDict` or `Map`.
|
||||
|
||||
## Protocols
|
||||
|
||||
@@ -21,15 +21,11 @@ defmodule Dict do
|
||||
protocol:
|
||||
|
||||
iex> dict = dict_impl.new
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
iex> dict = Dict.put(dict, :hello, :world)
|
||||
iex> dict[:hello]
|
||||
:world
|
||||
|
||||
And the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end)
|
||||
As well as the `Enumerable` and `Collectable` protocols.
|
||||
|
||||
## Match
|
||||
|
||||
@@ -41,18 +37,15 @@ defmodule Dict do
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type t :: tuple | list
|
||||
@type t :: list | map
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> { key, value })) :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, Enum.t) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback fetch(t, key) :: { :ok, value } | :error
|
||||
defcallback fetch(t, key) :: {:ok, value} | :error
|
||||
defcallback fetch!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: [key]
|
||||
@@ -62,7 +55,6 @@ defmodule Dict do
|
||||
defcallback pop(t, key, value) :: {value, t}
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback split(t, Enum.t) :: {t, t}
|
||||
defcallback take(t, Enum.t) :: t
|
||||
@@ -73,13 +65,18 @@ defmodule Dict do
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
cond do
|
||||
is_tuple(unquote(dict)) ->
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
ListDict
|
||||
true ->
|
||||
unsupported_dict(unquote(dict))
|
||||
case unquote(dict) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
%{} ->
|
||||
Map
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Dict module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x when is_list(x) ->
|
||||
Keyword
|
||||
x ->
|
||||
unsupported_dict(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -90,8 +87,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -106,8 +103,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -121,8 +118,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(d)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -136,7 +133,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
@@ -154,7 +151,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
@@ -168,14 +165,14 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `{ :ok, value }` associated with `key` in `dict`.
|
||||
Returns `{:ok, value}` associated with `key` in `dict`.
|
||||
If `dict` does not contain `key`, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
{:ok, 1}
|
||||
iex> Dict.fetch(d, :b)
|
||||
:error
|
||||
|
||||
@@ -191,11 +188,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
@@ -209,9 +204,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -225,9 +220,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put_new(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -242,13 +237,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.delete(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
iex> d = dict_impl.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
iex> d = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -270,18 +265,18 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -293,8 +288,8 @@ defmodule Dict do
|
||||
if target1 == target2 do
|
||||
target1.merge(dict1, dict2, fun)
|
||||
else
|
||||
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
|
||||
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
|
||||
Enumerable.reduce(dict2, {:cont, dict1}, fn({k, v}, acc) ->
|
||||
{:cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -305,19 +300,19 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :a
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(d)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(d)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b, 3
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(d)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@@ -332,9 +327,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -350,9 +345,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -370,20 +365,20 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = dict_impl.new([])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
iex> d = Enum.into([], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c])
|
||||
iex> {Dict.to_list(d1), Dict.to_list(d2)}
|
||||
{[], []}
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
|
||||
{[a: 1, b: 2], []}
|
||||
|
||||
"""
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
@@ -397,14 +392,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[b: 2]
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d) |> Enum.sort
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:c, :d])
|
||||
iex> Dict.to_list(d) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -420,14 +415,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.take(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[a: 1]
|
||||
...>
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Dict.take(d, [:c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -436,17 +429,7 @@ defmodule Dict do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> e = Dict.empty(d)
|
||||
...> Dict.to_list(e)
|
||||
[]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
target(dict).empty(dict)
|
||||
@@ -461,14 +444,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
...> Dict.equal?(a, b)
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = []
|
||||
...> Dict.equal?(a, b)
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = []
|
||||
iex> Dict.equal?(a, b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -482,10 +465,10 @@ defmodule Dict do
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
@@ -12,7 +12,7 @@ defmodule Dict.Behaviour do
|
||||
|
||||
# implement required functions (see below)
|
||||
|
||||
# override default implementations if needed
|
||||
# override default implementations if optimization is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
@@ -22,10 +22,9 @@ defmodule Dict.Behaviour do
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All of which are part of the `Dict` behaviour, so no extra functions are
|
||||
actually required.
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for the following functions:
|
||||
@@ -39,10 +38,15 @@ defmodule Dict.Behaviour do
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
@@ -63,25 +67,28 @@ defmodule Dict.Behaviour do
|
||||
|
||||
"""
|
||||
defmacro __using__(_) do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise KeyError, key: key
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
@@ -95,53 +102,98 @@ defmodule Dict.Behaviour do
|
||||
def take(dict, keys) do
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> put(acc, key, value)
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
kv, acc -> { :cont, [kv|acc] }
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{k, _}, acc -> { :cont, [k|acc] }
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{_, v}, acc -> { :cont, [v|acc] }
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case fetch(dict2, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &fun.(k, v1, &1)) }
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
{ value, delete(dict, key) }
|
||||
|
||||
:error ->
|
||||
{ default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
case fetch(exc, key) do
|
||||
{ :ok, value } ->
|
||||
{ put(inc, key, value), delete(exc, key) }
|
||||
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
|
||||
split: 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+400
-212
File diff suppressed because it is too large
Load Diff
+367
-78
@@ -28,27 +28,33 @@ defexception CompileError, [file: nil, line: nil, description: "compile error"]
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
defexception BadFunctionError, [term: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
defexception BadStructError, [struct: nil, term: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
defexception MatchError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [actual: nil] do
|
||||
defexception CaseClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.actual)}"
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,7 +63,7 @@ defexception BadArityError, [function: nil, args: nil] do
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{ :arity, arity } = :erlang.fun_info(fun, :arity)
|
||||
{:arity, arity} = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
@@ -105,9 +111,26 @@ defexception ErlangError, [original: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
defexception KeyError, key: nil, term: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -117,14 +140,14 @@ defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
@@ -133,31 +156,54 @@ end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Convenience functions to work with and pretty print
|
||||
exceptions and stacktraces.
|
||||
Functions to work with and pretty print exceptions.
|
||||
|
||||
Notice that stacktraces in Elixir are updated on errors.
|
||||
For example, at any given moement, `System.stacktrace`
|
||||
will return the stacktrace for the last error that ocurred
|
||||
in the current process.
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
|
||||
That said, many of the functions in this module will
|
||||
automatically calculate the stacktrace based on the caller,
|
||||
when invoked without arguments, changing the value of
|
||||
`System.stacktrace`. If instead you want to format the
|
||||
stacktrace of the latest error, you should instead explicitly
|
||||
pass the `System.stacktrace` as argument.
|
||||
Finally note developers should not rely on the particular
|
||||
format of the `format` functions provided by this module.
|
||||
They may be changed in future releases in order to better
|
||||
suit Elixir's tool chain. In other words, by using the
|
||||
functions in this module it is guarantee you will format
|
||||
exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
|
||||
@type process ::
|
||||
pid | atom | {atom, node} | {:global, term} | {:via, module, term}
|
||||
@type stacktrace_entry ::
|
||||
{ module, function, arity_or_args, location } |
|
||||
{ function, arity_or_args, location }
|
||||
{module, function, arity_or_args, location} |
|
||||
{function, arity_or_args, location}
|
||||
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is an exception.
|
||||
"""
|
||||
def exception?(exception) do
|
||||
is_tuple(exception) and tuple_size(exception) > 1 and
|
||||
:erlang.element(2, exception) == :__exception__
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the message for an exception.
|
||||
"""
|
||||
def message(exception) do
|
||||
try do
|
||||
exception.message
|
||||
rescue
|
||||
e ->
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
@@ -165,88 +211,329 @@ defmodule Exception do
|
||||
It takes the `kind` spilled by `catch` as an argument and
|
||||
normalizes only `:error`, returning the untouched payload
|
||||
for others.
|
||||
"""
|
||||
def normalize(:error, exception), do: normalize(exception)
|
||||
def normalize(_kind, other), do: other
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
|
||||
Useful when interfacing Erlang code with Elixir code.
|
||||
The third argument, a stacktrace, is optional. If it is
|
||||
not supplied `System.stacktrace/0` will sometimes be used
|
||||
to get additional information for the `kind` `:error`. If
|
||||
the stacktrace is unknown and `System.stacktrace/0` would
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
def normalize(exception) when is_exception(exception) do
|
||||
exception
|
||||
@spec normalize(:error, any, stacktrace) :: t
|
||||
@spec normalize(:exit | :throw, any, stacktrace) :: any
|
||||
# Generating a stacktrace is expensive, default to nil to only fetch it when
|
||||
# needed.
|
||||
def normalize(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def normalize(:error, exception, stacktrace) do
|
||||
if exception?(exception) do
|
||||
exception
|
||||
else
|
||||
normalize_error(exception, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
def normalize(:badarg) do
|
||||
def normalize(_kind, payload, _stacktrace) do
|
||||
payload
|
||||
end
|
||||
|
||||
defp normalize_error(:badarg, _stacktrace) do
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
def normalize(:badarith) do
|
||||
defp normalize_error(:badarith, _stacktrace) do
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
def normalize(:system_limit) do
|
||||
defp normalize_error(:system_limit, _stacktrace) do
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
def normalize({ :badarity, { fun, args } }) do
|
||||
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
def normalize({ :badfun, actual }) do
|
||||
BadFunctionError[actual: actual]
|
||||
defp normalize_error({:badfun, term}, _stacktrace) do
|
||||
BadFunctionError[term: term]
|
||||
end
|
||||
|
||||
def normalize({ :badmatch, actual }) do
|
||||
MatchError[actual: actual]
|
||||
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
|
||||
BadStructError[struct: struct, term: term]
|
||||
end
|
||||
|
||||
def normalize({ :case_clause, actual }) do
|
||||
CaseClauseError[actual: actual]
|
||||
defp normalize_error({:badmatch, term}, _stacktrace) do
|
||||
MatchError[term: term]
|
||||
end
|
||||
|
||||
def normalize({ :try_clause, actual }) do
|
||||
TryClauseError[actual: actual]
|
||||
defp normalize_error({:case_clause, term}, _stacktrace) do
|
||||
CaseClauseError[term: term]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
defp normalize_error({:try_clause, term}, _stacktrace) do
|
||||
TryClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize(:function_clause) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
defp normalize_error(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize({ :badarg, payload }) do
|
||||
defp normalize_error({:badarg, payload}, _stacktrace) do
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
def normalize(other) do
|
||||
defp normalize_error(other, _stacktrace) do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an stacktrace entry and formats it into a string.
|
||||
Normalizes and formats any throw, error and exit.
|
||||
|
||||
The message is formatted and displayed in the same
|
||||
format as used by Elixir's CLI.
|
||||
|
||||
The third argument, a stacktrace, is optional. If it is
|
||||
not supplied `System.stacktrace/0` will sometimes be used
|
||||
to get additional information for the `kind` `:error`. If
|
||||
the stacktrace is unknown and `System.stacktrace/0` would
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
def format_banner(kind, exception, stacktrace \\ nil)
|
||||
|
||||
def format_banner(:error, exception, stacktrace) do
|
||||
exception = normalize(:error, exception, stacktrace)
|
||||
"** (" <> inspect(exception.__record__(:name)) <> ") " <> message(exception)
|
||||
end
|
||||
|
||||
def format_banner(:throw, reason, _stacktrace) do
|
||||
"** (throw) " <> inspect(reason)
|
||||
end
|
||||
|
||||
def format_banner(:exit, reason, _stacktrace) do
|
||||
"** (exit) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats an exception and stacktrace.
|
||||
|
||||
Behaves the same as `format_banner/3` but also includes
|
||||
the formatted stacktrace.
|
||||
"""
|
||||
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
|
||||
message = format_banner(kind, exception, stacktrace)
|
||||
case stacktrace do
|
||||
[] -> message
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats an exit, returns a string.
|
||||
|
||||
Often there are errors/exceptions inside exits. Exits are often
|
||||
wrapped by the caller and provide stacktraces too. This function
|
||||
formats exits in a way to nicely show the exit reason, caller
|
||||
and stacktrace.
|
||||
"""
|
||||
@spec format_exit(any) :: String.t
|
||||
def format_exit(reason) do
|
||||
format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
# 2-Tuple could be caused by an error if the second element is a stacktrace.
|
||||
defp format_exit({exception, maybe_stacktrace} = reason, joiner)
|
||||
when is_list(maybe_stacktrace) and maybe_stacktrace !== [] do
|
||||
try do
|
||||
Enum.map(maybe_stacktrace, &format_stacktrace_entry/1)
|
||||
else
|
||||
formatted_stacktrace ->
|
||||
# Assume a non-empty list formattable as stacktrace is a
|
||||
# stacktrace, so exit was caused by an error.
|
||||
message = "an exception was raised:" <> joiner <>
|
||||
format_banner(:error, exception, maybe_stacktrace)
|
||||
Enum.join([message | formatted_stacktrace], joiner <> <<" ">>)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not a stacktrace, was an exit.
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
end
|
||||
|
||||
# :supervisor.start_link returns this error reason when it fails to init
|
||||
# because a child's start_link raises.
|
||||
defp format_exit({:shutdown,
|
||||
{:failed_to_start_child, child, {:EXIT, reason}}}, joiner) do
|
||||
format_start_child(child, reason, joiner)
|
||||
end
|
||||
|
||||
# :supervisor.start_link returns this error reason when it fails to init
|
||||
# because a child's start_link returns {:error, reason}.
|
||||
defp format_exit({:shutdown, {:failed_to_start_child, child, reason}},
|
||||
joiner) do
|
||||
format_start_child(child, reason, joiner)
|
||||
end
|
||||
|
||||
# 2-Tuple could be an exit caused by mfa if second element is mfa, args
|
||||
# must be a list of arguments - max length 255 due to max arity.
|
||||
defp format_exit({reason2, {mod, fun, args}} = reason, joiner)
|
||||
when length(args) < 256 do
|
||||
try do
|
||||
format_mfa(mod, fun, args)
|
||||
else
|
||||
mfa ->
|
||||
# Assume tuple formattable as an mfa is an mfa, so exit was caused by
|
||||
# failed mfa.
|
||||
"exited in: " <> mfa <> joiner <>
|
||||
"** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not an mfa, was an exit.
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
end
|
||||
|
||||
defp format_exit(reason, _joiner) do
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
|
||||
defp format_exit_reason(:normal), do: "normal"
|
||||
defp format_exit_reason(:shutdown), do: "shutdown"
|
||||
|
||||
defp format_exit_reason({:shutdown, reason}) do
|
||||
"shutdown: #{inspect(reason)}"
|
||||
end
|
||||
|
||||
defp format_exit_reason(:timeout), do: "time out"
|
||||
defp format_exit_reason(:killed), do: "killed"
|
||||
defp format_exit_reason(:noconnection), do: "no connection"
|
||||
|
||||
defp format_exit_reason(:noproc) do
|
||||
"no process"
|
||||
end
|
||||
|
||||
defp format_exit_reason({:nodedown, node_name}) when is_atom(node_name) do
|
||||
"no connection to #{node_name}"
|
||||
end
|
||||
|
||||
# :gen_server exit reasons
|
||||
|
||||
defp format_exit_reason({:already_started, pid}) do
|
||||
"already started: " <> inspect(pid)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_return_value, value}) do
|
||||
"bad return value: " <> inspect(value)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_call, request}) do
|
||||
"bad call: " <> inspect(request)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_cast, request}) do
|
||||
"bad cast: " <> inspect(request)
|
||||
end
|
||||
|
||||
# :supervisor.start_link error reasons
|
||||
|
||||
# If value is a list will be be formatted by mfa exit in format_exit/1
|
||||
defp format_exit_reason({:bad_return, {mod, :init, value}})
|
||||
when is_atom(mod) do
|
||||
format_mfa(mod, :init, 1) <> " returned a bad value: " <> inspect(value)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_start_spec, start_spec}) do
|
||||
"bad start spec: invalid children: " <> inspect(start_spec)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:start_spec, start_spec}) do
|
||||
"bad start spec: " <> format_sup_spec(start_spec)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:supervisor_data, data}) do
|
||||
"bad supervisor data: " <> format_sup_data(data)
|
||||
end
|
||||
|
||||
defp format_exit_reason(reason), do: inspect(reason)
|
||||
|
||||
defp format_start_child(child, reason, joiner) do
|
||||
"shutdown: failed to start child: " <> inspect(child) <> joiner <>
|
||||
"** (EXIT) " <> format_exit(reason, joiner <> <<" ">>)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_type, type}) do
|
||||
"invalid type: " <> inspect(type)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_strategy, strategy}) do
|
||||
"invalid strategy: " <> inspect(strategy)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_intensity, intensity}) do
|
||||
"invalid intensity: " <> inspect(intensity)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_period, period}) do
|
||||
"invalid period: " <> inspect(period)
|
||||
end
|
||||
|
||||
defp format_sup_data(other), do: inspect(other)
|
||||
|
||||
defp format_sup_spec({:invalid_child_spec, child_spec}) do
|
||||
"invalid child spec: " <> inspect(child_spec)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_child_type, type}) do
|
||||
"invalid child type: " <> inspect(type)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_mfa, mfa}) do
|
||||
"invalid mfa: " <> inspect(mfa)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_restart_type, restart}) do
|
||||
"invalid restart type: " <> inspect(restart)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_shutdown, shutdown}) do
|
||||
"invalid shutdown: " <> inspect(shutdown)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_module, mod}) do
|
||||
"invalid module: " <> inspect(mod)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_modules, modules}) do
|
||||
"invalid modules: " <> inspect(modules)
|
||||
end
|
||||
|
||||
defp format_sup_spec(other), do: inspect(other)
|
||||
|
||||
@doc """
|
||||
Receives a stacktrace entry and formats it into a string.
|
||||
"""
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
|
||||
def format_stacktrace_entry({module, :__MODULE__, 0, location}) do
|
||||
format_location(location) <> inspect(module) <> " (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
|
||||
def format_stacktrace_entry({_module, :__MODULE__, 1, location}) do
|
||||
format_location(location) <> "(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
|
||||
def format_stacktrace_entry({_module, :__FILE__, 1, location}) do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
@@ -260,7 +547,7 @@ defmodule Exception do
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
|
||||
{:ok, app} -> "(" <> atom_to_binary(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
@@ -268,16 +555,14 @@ defmodule Exception do
|
||||
@doc """
|
||||
Formats the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
A stacktrace must be given as an argument. If not, the stacktrace
|
||||
is retrieved from `Process.info/2`.
|
||||
"""
|
||||
def format_stacktrace(trace \\ nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
trace = trace || case Process.info(self, :current_stacktrace) do
|
||||
{:current_stacktrace, t} -> Enum.drop(t, 3)
|
||||
end
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
@@ -294,7 +579,7 @@ defmodule Exception do
|
||||
#=> "#Function<...>/1"
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) do
|
||||
def format_fa(fun, arity) when is_function(fun) do
|
||||
"#{inspect fun}#{format_arity(arity)}"
|
||||
end
|
||||
|
||||
@@ -307,8 +592,10 @@ defmodule Exception do
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, 1
|
||||
"Foo.bar/1"
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, []
|
||||
"Foo.bar()"
|
||||
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
@@ -316,7 +603,7 @@ defmodule Exception do
|
||||
where func is the name of the enclosing function. Convert to
|
||||
"anonymous fn in func/arity"
|
||||
"""
|
||||
def format_mfa(module, fun, arity) when is_atom(fun) do
|
||||
def format_mfa(module, fun, arity) when is_atom(module) and is_atom(fun) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
":" <> fun -> fun
|
||||
@@ -332,11 +619,13 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_arity(arity) when is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
inspected = for x <- arity, do: inspect(x)
|
||||
"(#{Enum.join(inspected, ", ")})"
|
||||
end
|
||||
|
||||
defp format_arity(arity), do: "/#{arity}"
|
||||
defp format_arity(arity) when is_integer(arity) do
|
||||
"/" <> integer_to_binary(arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
@@ -370,19 +659,19 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp format_location(opts) do
|
||||
defp format_location(opts) when is_list(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
{ module, function, length(args) }
|
||||
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
|
||||
{module, function, length(args)}
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, arity, _ }|_]) do
|
||||
{ module, function, arity }
|
||||
defp from_stacktrace([{module, function, arity, _}|_]) do
|
||||
{module, function, arity}
|
||||
end
|
||||
|
||||
defp from_stacktrace(_) do
|
||||
{ nil, nil, nil }
|
||||
{nil, nil, nil}
|
||||
end
|
||||
end
|
||||
|
||||
+469
-258
File diff suppressed because it is too large
Load Diff
+23
-13
@@ -6,26 +6,29 @@ defmodule Float do
|
||||
@doc """
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
|
||||
If successful, returns a tuple of the form `{float, remainder_of_binary}`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.parse("34")
|
||||
{34.0,""}
|
||||
|
||||
iex> Float.parse("34.25")
|
||||
{34.25,""}
|
||||
|
||||
iex> Float.parse("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
|
||||
iex> Float.parse("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
@spec parse(binary) :: {float, binary} | :error
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +40,7 @@ defmodule Float do
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
|
||||
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +50,7 @@ defmodule Float do
|
||||
end
|
||||
|
||||
defp parse_unsign(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
{:erlang.float(int), rest}
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
@@ -62,14 +65,14 @@ defmodule Float do
|
||||
# the function clause because the current approach copies a binary
|
||||
# just on this branch. If we broke it apart in the function clause,
|
||||
# the copy would happen when calling Integer.parse/1.
|
||||
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
|
||||
{ exponential, after_exponential } ->
|
||||
{ floatify(int, float, decimal, exponential), after_exponential }
|
||||
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
|
||||
{exponential, after_exponential} ->
|
||||
{floatify(int, float, decimal, exponential), after_exponential}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
{floatify(int, float, decimal), bitstring}
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
@@ -82,7 +85,7 @@ defmodule Float do
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http:\\docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
@@ -94,14 +97,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer less than or equal to `num`
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34)
|
||||
34
|
||||
|
||||
iex> Float.floor(34.25)
|
||||
34
|
||||
|
||||
iex> Float.floor(-56.5)
|
||||
-57
|
||||
|
||||
@@ -117,14 +122,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer greater than or equal to `num`
|
||||
Rounds a float to the largest integer greater than or equal to `num`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.ceil(34)
|
||||
34
|
||||
|
||||
iex> Float.ceil(34.25)
|
||||
35
|
||||
|
||||
iex> Float.ceil(-56.5)
|
||||
-56
|
||||
|
||||
@@ -147,16 +154,19 @@ defmodule Float do
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
5.567
|
||||
|
||||
iex> Float.round(5.5675, 3)
|
||||
5.568
|
||||
|
||||
iex> Float.round(-5.5674, 3)
|
||||
-5.567
|
||||
|
||||
iex> Float.round(-5.5675, 3)
|
||||
-5.568
|
||||
|
||||
"""
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -15,11 +15,11 @@ defmodule GenEvent.Behaviour do
|
||||
# Callbacks
|
||||
|
||||
def init(_) do
|
||||
{ :ok, [] }
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def handle_event({:notification, x}, notifications) do
|
||||
{ :ok, [x|notifications] }
|
||||
{:ok, [x|notifications]}
|
||||
end
|
||||
|
||||
def handle_call(:notifications, notifications) do
|
||||
@@ -27,7 +27,7 @@ defmodule GenEvent.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
{:ok, pid} = :gen_event.start_link
|
||||
#=> {:ok,#PID<0.42.0>}
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
@@ -63,22 +63,22 @@ defmodule GenEvent.Behaviour do
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{ :ok, :ok, state }
|
||||
{:ok, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -88,7 +88,7 @@ defmodule GenEvent.Behaviour do
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1,
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
defmodule GenFSM.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenFSM callbacks in Elixir.
|
||||
|
||||
A finite state machine (FSM) is responsible for reacting to events received;
|
||||
GenFSM is an OTP behaviour that encapsulates common FSM
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenFSM that runs a very simple minded
|
||||
coffee vending machine (CVM). The CVM treats all coins the same. If
|
||||
you press the request button then the CVM will brew coffee, if you
|
||||
have paid enough coins; if not, it will wait until you have inserted
|
||||
enough coins and then it will instantly brew the coffee, since you
|
||||
had already pressed the request button. As we told you - a very
|
||||
simple minded CVM! And greedy too. If you insert more coins than you
|
||||
need it will gladly eat them until you press the request button.
|
||||
|
||||
We will leave it to the service-minded reader to improve the way the CVM
|
||||
works - we hereby declare a full disclaimer for any lawsuits that the
|
||||
behaviour of the CVM in its original state might encur.
|
||||
|
||||
defmodule MyFsm do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
|
||||
# API functions
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, _pid } = MyFsm.start_link()
|
||||
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
|
||||
MyFsm.request_coffee
|
||||
#=> :ok
|
||||
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
#=> Here's your coffee!
|
||||
|
||||
Notice we never call the GenFSM callbacks directly; they are called by
|
||||
OTP whenever we interact with the server throught the API. `send_event` is
|
||||
asynchronous, whereas `sync_send_event` is synchronous. For
|
||||
a GenFSM, the different values a callback can return depend
|
||||
on the type of callback.
|
||||
|
||||
State handling returns for `send_event` callbacks:
|
||||
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, new_state_data }
|
||||
|
||||
State handling returns for `sync_send_event` callbacks:
|
||||
|
||||
{ :reply, reply, next_state_name, new_state_data }
|
||||
{ :reply, reply, next_state_name, new_state_data, timeout }
|
||||
{ :reply, reply, next_state_name, new_state_data, :hibernate }
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, reply, new_state_data }
|
||||
{ :stop, reason, new_state_date }
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenFsm plus 1
|
||||
or 2 for each state. The `GenFSM.Behaviour` module defines
|
||||
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
|
||||
for you. The list of callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the FSM is started;
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
handle `sync_send_all_state_event` messages;
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
`send_all_state_event` messages;
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
messages which are normally received by processes;
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
is about to terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
|
||||
implemented by default, as it requires the next state to be returned.
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
`state_name/2` variant and vice-versa for synchronous events and
|
||||
`state_name/3`. Keep in mind that if you mix `send_event` and
|
||||
`sync_send_event` the best thing to do is to implement both
|
||||
callbacks for all states.
|
||||
|
||||
Starting and sending messages to the GenFSM is done via Erlang's
|
||||
`:gen_fsm` module. For more information, please refer to the
|
||||
following:
|
||||
|
||||
* http://www.erlang.org/doc/man/gen_fsm.html
|
||||
* http://www.erlang.org/doc/design_principles/fsm.html
|
||||
* http://learnyousomeerlang.com/finite-state-machines
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(event, state_name, state_data) do
|
||||
{ :stop, {:bad_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(event, from, state_name, state_data) do
|
||||
{ :stop, {:bad_sync_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state_name, _state_data) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state_name, state_data, _extra) do
|
||||
{ :ok, state_name, state_data }
|
||||
end
|
||||
|
||||
defoverridable [handle_event: 3, handle_sync_event: 4,
|
||||
handle_info: 3, terminate: 3, code_change: 4]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,7 +17,7 @@ defmodule GenServer.Behaviour do
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{ :reply, h, t }
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, config) do
|
||||
@@ -25,8 +25,8 @@ defmodule GenServer.Behaviour do
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, config) do
|
||||
{ :noreply, [item|config] }
|
||||
def handle_cast({:push, item}, config) do
|
||||
{:noreply, [item|config]}
|
||||
end
|
||||
|
||||
def handle_cast(request, config) do
|
||||
@@ -34,12 +34,12 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_server.start_link(MyServer, [:hello], [])
|
||||
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
#=> :hello
|
||||
|
||||
:gen_server.cast(pid, { :push, :world })
|
||||
:gen_server.cast(pid, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
@@ -51,14 +51,14 @@ defmodule GenServer.Behaviour do
|
||||
GenServer, there are 8 different values a callback such as
|
||||
`handle_call` or `handle_cast` can return:
|
||||
|
||||
{ :reply, reply, new_state }
|
||||
{ :reply, reply, new_state, timeout }
|
||||
{ :reply, reply, new_state, :hibernate }
|
||||
{ :noreply, new_state }
|
||||
{ :noreply, new_state, timeout }
|
||||
{ :noreply, new_state, :hibernate }
|
||||
{ :stop, reason, new_state }
|
||||
{ :stop, reason, reply, new_state }
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenServer. The
|
||||
`GenServer.Behaviour` module defines all of them automatically, but
|
||||
@@ -90,22 +90,22 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -115,7 +115,7 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
|
||||
+57
-74
@@ -2,8 +2,14 @@ defmodule HashDict do
|
||||
@moduledoc """
|
||||
A key-value store.
|
||||
|
||||
The `HashDict` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
The `HashDict` is represented internally as a struct, therefore
|
||||
`%HashDict{}` can be used whenever there is a need to match
|
||||
on any `HashDict`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Dict` module.
|
||||
|
||||
Implementation-wise, `HashDict` is implemented using tries, which
|
||||
grows in space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
@@ -15,131 +21,94 @@ defmodule HashDict do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
root: @node_template
|
||||
@opaque t :: map
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
trie()
|
||||
%HashDict{}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b, 1}, {:a, 2}]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: Dict.t
|
||||
def new(enum) do
|
||||
Enum.reduce enum, trie(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
def put(%HashDict{root: root, size: size}, key, value) do
|
||||
{root, counter} = do_put(root, key, value, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from the enumerable with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
Enum.reduce enum, trie(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
end
|
||||
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
def empty(trie()) do
|
||||
trie()
|
||||
def update(%HashDict{root: root, size: size}, key, initial, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
def put(trie(root: root, size: size), key, value) do
|
||||
{ root, counter } = do_put(root, key, value, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def update!(trie(root: root, size: size), key, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key end, fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def fetch(trie(root: root), key) do
|
||||
def fetch(%HashDict{root: root}, key) do
|
||||
do_fetch(root, key, key_hash(key))
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _value } -> dict
|
||||
{dict, _value} -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, value } -> { value, dict }
|
||||
:error -> { default, dict }
|
||||
{dict, value} -> {value, dict}
|
||||
:error -> {default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
def size(trie(size: size)) do
|
||||
def size(%HashDict{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
@doc false
|
||||
def reduce(%HashDict{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
Enum.reduce keys, {new, dict}, fn key, {inc, exc} = acc ->
|
||||
case dict_delete(exc, key) do
|
||||
{ exc, value } -> { put(inc, key, value), exc }
|
||||
{exc, value} -> {put(inc, key, value), exc}
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge(trie(size: size1) = dict1, trie(size: size2) = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &callback.(k, v1, &1)) }
|
||||
def merge(%HashDict{size: size1} = dict1, %HashDict{size: size2} = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &callback.(k, v1, &1))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def merge(trie() = dict1, trie() = dict2, callback) do
|
||||
reduce(dict2, { :cont, dict1 }, fn { k, v2 }, acc ->
|
||||
{ :cont, update(acc, k, v2, &callback.(k, &1, v2)) }
|
||||
def merge(%HashDict{} = dict1, %HashDict{} = dict2, callback) do
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &callback.(k, &1, v2))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(trie(root: root, size: size), key) do
|
||||
defp dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{ root, value } -> { trie(root: root, size: size - 1), value }
|
||||
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
@@ -278,11 +247,25 @@ end
|
||||
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: { :ok, match?({ :ok, ^v }, HashDict.fetch(dict, k)) }
|
||||
def member?(_dict, _), do: { :ok, false }
|
||||
def count(dict), do: { :ok, HashDict.size(dict) }
|
||||
def member?(dict, {k, v}), do: {:ok, match?({:ok, ^v}, HashDict.fetch(dict, k))}
|
||||
def member?(_dict, _), do: {:ok, false}
|
||||
def count(dict), do: {:ok, HashDict.size(dict)}
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
def empty(_dict) do
|
||||
HashDict.new
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
|
||||
dict, :done -> dict
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+50
-67
@@ -2,6 +2,12 @@ defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is represented internally as a struct, therefore
|
||||
`%HashSet{}` can be used whenever there is a need to match
|
||||
on any `HashSet`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Set` module.
|
||||
|
||||
The `HashSet` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
@@ -15,70 +21,36 @@ defmodule HashSet do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
root: @node_template
|
||||
@opaque t :: map
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
trie()
|
||||
%HashSet{}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
HashSet.new [1, 2]
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: Set.t
|
||||
def new(enum) do
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, i)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the enumerable with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
HashSet.new [1, 2], &integer_to_binary/1
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, (term -> term)) :: Set.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, transform.(i))
|
||||
end
|
||||
end
|
||||
|
||||
def union(trie(size: size1) = set1, trie(size: size2) = set2) when size1 <= size2 do
|
||||
def union(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) when size1 <= size2 do
|
||||
set_fold set1, set2, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def union(trie() = set1, trie() = set2) do
|
||||
def union(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold set2, set1, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def intersection(trie() = set1, trie() = set2) do
|
||||
set_fold set1, trie(), fn v, acc ->
|
||||
def intersection(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold set1, %HashSet{}, fn v, acc ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end
|
||||
end
|
||||
|
||||
def difference(trie() = set1, trie() = set2) do
|
||||
def difference(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold set2, set1, fn v, acc -> delete(acc, v) end
|
||||
end
|
||||
|
||||
@@ -86,66 +58,63 @@ defmodule HashSet do
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
|
||||
def equal?(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(trie() = set1, trie() = set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
def subset?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def disjoint?(trie() = set1, trie() = set2) do
|
||||
reduce(set2, { :cont, true }, fn member, acc ->
|
||||
def disjoint?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def empty(trie()) do
|
||||
trie()
|
||||
end
|
||||
|
||||
def member?(trie(root: root), term) do
|
||||
def member?(%HashSet{root: root}, term) do
|
||||
do_member?(root, term, key_hash(term))
|
||||
end
|
||||
|
||||
def put(trie(root: root, size: size), term) do
|
||||
def put(%HashSet{root: root, size: size}, term) do
|
||||
{root, counter} = do_put(root, term, key_hash(term))
|
||||
trie(root: root, size: size + counter)
|
||||
%HashSet{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
def delete(trie(root: root, size: size) = set, term) do
|
||||
def delete(%HashSet{root: root, size: size} = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> trie(root: root, size: size - 1)
|
||||
{:ok, root} -> %HashSet{root: root, size: size - 1}
|
||||
:error -> set
|
||||
end
|
||||
end
|
||||
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
@doc false
|
||||
def reduce(%HashSet{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def size(trie(size: size)) do
|
||||
def size(%HashSet{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
## Set helpers
|
||||
|
||||
defp set_fold(trie(root: root), acc, fun) do
|
||||
defp set_fold(%HashSet{root: root}, acc, fun) do
|
||||
do_fold(root, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
@@ -278,6 +247,20 @@ end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
|
||||
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
|
||||
def count(set), do: { :ok, HashSet.size(set) }
|
||||
def member?(set, v), do: {:ok, HashSet.member?(set, v)}
|
||||
def count(set), do: {:ok, HashSet.size(set)}
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashSet do
|
||||
def empty(_dict) do
|
||||
HashSet.new
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{original, fn
|
||||
set, {:cont, x} -> HashSet.put(set, x)
|
||||
set, :done -> set
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+120
-130
@@ -1,14 +1,6 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
pretty: false,
|
||||
width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
The `Inspect` protocol is responsible for converting any Elixir
|
||||
@@ -49,38 +41,25 @@ defprotocol Inspect do
|
||||
## Error handling
|
||||
|
||||
In case there is an error while your structure is being inspected,
|
||||
Elixir will automatically fall back to tuple inspection for records.
|
||||
Elixir will automatically fall back to a raw representation.
|
||||
|
||||
You can however access the underlying error by invoking the Inspect
|
||||
implementation directly. For example, to test Inspect.HashSet above,
|
||||
you just need to do:
|
||||
you can invoke it as:
|
||||
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
|
||||
# Handle structs in Any
|
||||
@fallback_to_any true
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
@doc """
|
||||
Represents the atom as an Elixir term. The atoms `false`, `true`
|
||||
and `nil` are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
def inspect(atom, _opts) do
|
||||
inspect(atom)
|
||||
end
|
||||
@@ -89,18 +68,21 @@ defimpl Inspect, for: Atom do
|
||||
def inspect(true), do: "true"
|
||||
def inspect(nil), do: "nil"
|
||||
def inspect(:""), do: ":\"\""
|
||||
def inspect(Elixir), do: "Elixir"
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_ref_identifier?(binary) ->
|
||||
if only_elixir?(binary) do
|
||||
binary
|
||||
else
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
end
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :<<>>, :...] ->
|
||||
atom in [:%{}, :{}, :<<>>, :..., :%] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -109,6 +91,10 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
defp only_elixir?("Elixir." <> rest), do: only_elixir?(rest)
|
||||
defp only_elixir?("Elixir"), do: true
|
||||
defp only_elixir?(_), do: false
|
||||
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
@@ -154,22 +140,7 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
iex> inspect(<<0,1,2>>)
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
"""
|
||||
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) do
|
||||
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
@@ -192,7 +163,7 @@ defimpl Inspect, for: BitString do
|
||||
escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{>>)
|
||||
end
|
||||
defp escape(<<?\a, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
@@ -249,7 +220,7 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
defp inspect_bitstring(bitstring, opts) do
|
||||
each_bit(bitstring, opts.limit, "<<") <> ">>"
|
||||
end
|
||||
|
||||
@@ -280,7 +251,7 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
@@ -290,8 +261,10 @@ defimpl Inspect, for: List do
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
@@ -299,10 +272,10 @@ defimpl Inspect, for: List do
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
cond do
|
||||
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
@@ -310,13 +283,25 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
def keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
## Private
|
||||
|
||||
defp key_to_binary(key) do
|
||||
case Inspect.Atom.inspect(key) do
|
||||
":" <> right -> right
|
||||
@@ -324,34 +309,23 @@ defimpl Inspect, for: List do
|
||||
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
|
||||
defp printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
|
||||
defp printable?([?\n|cs]), do: printable?(cs)
|
||||
defp printable?([?\r|cs]), do: printable?(cs)
|
||||
defp printable?([?\t|cs]), do: printable?(cs)
|
||||
defp printable?([?\v|cs]), do: printable?(cs)
|
||||
defp printable?([?\b|cs]), do: printable?(cs)
|
||||
defp printable?([?\f|cs]), do: printable?(cs)
|
||||
defp printable?([?\e|cs]), do: printable?(cs)
|
||||
defp printable?([?\a|cs]), do: printable?(cs)
|
||||
defp printable?([]), do: true
|
||||
defp printable?(_), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@doc """
|
||||
Represents tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1, 2, 3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, Inspect.Opts[] = opts) do
|
||||
def inspect(tuple, opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
@@ -361,7 +335,7 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, Inspect.Opts[] = opts) do
|
||||
defp record_inspect(record, opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
@@ -383,17 +357,17 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp zip_fields([{ key, _ }|tk], [value|tv]) do
|
||||
defp zip_fields([{key, _}|tk], [value|tv]) do
|
||||
case atom_to_binary(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{ key, value }|zip_fields(tk, tv)]
|
||||
key -> [{key, value}|zip_fields(tk, tv)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -401,61 +375,52 @@ defimpl Inspect, for: Tuple do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
defp keyword({k, v}, opts) do
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
map = :maps.to_list(map)
|
||||
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword(&1, opts)
|
||||
else
|
||||
&to_map(&1, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp to_map({key, value}, opts) do
|
||||
concat(
|
||||
concat(to_doc(key, opts), " => "),
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
@doc """
|
||||
Represents the integer as a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Float do
|
||||
@doc """
|
||||
Floats are represented using the shortened, correctly rounded string
|
||||
that converts to float when read back with `binary_to_float/1`. This
|
||||
is done via the Erlang implementation of _Printing Floating-Point
|
||||
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
|
||||
Conference on Programming Language Design and Implementation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1.0)
|
||||
"1.0"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %S"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
to_doc(other, opts.records(false))
|
||||
def inspect(regex, opts) do
|
||||
concat ["~r", to_doc(regex.source, opts), regex.opts]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -481,37 +446,62 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
|
||||
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
|
||||
end
|
||||
|
||||
defp extract_name([]) do
|
||||
""
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
case :binary.split(atom_to_binary(name), "-", [:global]) do
|
||||
["", name | _] -> name
|
||||
_ -> name
|
||||
name = atom_to_binary(name)
|
||||
case :binary.split(name, "-", [:global]) do
|
||||
["", name | _] -> "." <> name
|
||||
_ -> "." <> name
|
||||
end
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <>
|
||||
integer_to_binary(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
|
||||
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
iolist_to_binary :erlang.port_to_list(port)
|
||||
iodata_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Reference do
|
||||
def inspect(ref, _opts) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> iolist_to_binary(rest)
|
||||
"#Reference" <> iodata_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Any do
|
||||
def inspect(%{__struct__: struct} = map, opts) do
|
||||
try do
|
||||
struct.__struct__
|
||||
rescue
|
||||
_ -> Inspect.Map.inspect(map, opts)
|
||||
else
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(map) do
|
||||
Inspect.Map.inspect(:maps.remove(:__struct__, map),
|
||||
Inspect.Atom.inspect(struct, opts), opts)
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
defmodule Inspect.Opts do
|
||||
@moduledoc """
|
||||
Defines the Inspect.Opts used by the Inspect protocol.
|
||||
|
||||
The following fields are available:
|
||||
|
||||
* `:structs` - when false, structs are not formatted by the inspect protocol,
|
||||
they are instead printed as maps, defaults to true;
|
||||
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped; when `:as_binaries` all
|
||||
binaries will be printed in bit syntax; when the default
|
||||
`:infer`, the binary will be printed as a string if it is
|
||||
printable, otherwise in bit syntax;
|
||||
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
|
||||
non-printable elements will be escaped; when `:as_lists` all
|
||||
lists will be printed as lists; when the default `:infer`, the
|
||||
list will be printed as a char list if it is printable,
|
||||
otherwise as list;
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
|
||||
and lists, does not apply to strings nor char lists, defaults to 50;
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false;
|
||||
|
||||
* `:width` - defaults to the 80 characters;
|
||||
"""
|
||||
|
||||
defstruct structs: true :: boolean,
|
||||
binaries: :infer :: :infer | :as_binaries | :as_strings,
|
||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
||||
limit: 50 :: pos_integer,
|
||||
width: 80 :: pos_integer | :infinity,
|
||||
pretty: false :: boolean,
|
||||
records: true :: boolean
|
||||
end
|
||||
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
|
||||
|
||||
@@ -8,6 +46,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
|
||||
iex> "foo"
|
||||
"foo"
|
||||
|
||||
@@ -73,14 +112,24 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
|
||||
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
|
||||
defrecordp :doc_break, str: " " :: binary
|
||||
defrecordp :doc_group, doc: :doc_nil :: t
|
||||
@type t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | binary
|
||||
|
||||
require Record
|
||||
|
||||
@typep doc_cons :: {:doc_cons, t, t}
|
||||
Record.defrecordp :doc_cons, left: :doc_nil, right: :doc_nil
|
||||
|
||||
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
|
||||
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
|
||||
|
||||
@typep doc_break :: {:doc_break, binary}
|
||||
Record.defrecordp :doc_break, str: " "
|
||||
|
||||
@typep doc_group :: {:doc_group, t}
|
||||
Record.defrecordp :doc_group, doc: :doc_nil
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if __CALLER__.in_guard? do
|
||||
if Macro.Env.in_guard?(__CALLER__) do
|
||||
do_is_doc(doc)
|
||||
else
|
||||
var = quote do: doc
|
||||
@@ -105,26 +154,43 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
if is_tuple(arg) do
|
||||
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
|
||||
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
Inspect.inspect(arg, opts)
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
|
||||
if opts.structs do
|
||||
try do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) do
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
@@ -149,7 +215,7 @@ defmodule Inspect.Algebra do
|
||||
"TastelessArtosis"
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons_t
|
||||
@spec concat(t, t) :: doc_cons
|
||||
def concat(x, y) when is_doc(x) and is_doc(y) do
|
||||
doc_cons(left: x, right: y)
|
||||
end
|
||||
@@ -157,7 +223,7 @@ defmodule Inspect.Algebra do
|
||||
@doc """
|
||||
Concatenates a list of documents.
|
||||
"""
|
||||
@spec concat([t]) :: doc_cons_t
|
||||
@spec concat([t]) :: doc_cons
|
||||
def concat(docs) do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
@@ -173,7 +239,7 @@ defmodule Inspect.Algebra do
|
||||
" 6"
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest_t
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
def nest(x, 0) when is_doc(x) do
|
||||
x
|
||||
end
|
||||
@@ -182,7 +248,7 @@ defmodule Inspect.Algebra do
|
||||
doc_nest(indent: i, doc: x)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
@@ -203,26 +269,26 @@ defmodule Inspect.Algebra do
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break_t
|
||||
@spec break(binary) :: doc_break
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
|
||||
@spec break() :: doc_break_t
|
||||
@spec break() :: doc_break
|
||||
def break(), do: doc_break(str: @break)
|
||||
|
||||
@doc """
|
||||
Inserts a break between two docs. See `break/1` for more info.
|
||||
"""
|
||||
@spec glue(t, t) :: doc_cons_t
|
||||
@spec glue(t, t) :: doc_cons
|
||||
def glue(x, y), do: concat(x, concat(break, y))
|
||||
|
||||
@doc """
|
||||
Inserts a break, passed as the second argument, between two docs,
|
||||
the first and the third arguments.
|
||||
"""
|
||||
@spec glue(t, binary, t) :: doc_cons_t
|
||||
@spec glue(t, binary, t) :: doc_cons
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
@@ -249,7 +315,7 @@ defmodule Inspect.Algebra do
|
||||
"Hello,\nA B"
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group_t
|
||||
@spec group(t) :: doc_group
|
||||
def group(d) when is_doc(d) do
|
||||
doc_group(doc: d)
|
||||
end
|
||||
@@ -264,10 +330,10 @@ defmodule Inspect.Algebra do
|
||||
"Hughes Wadler"
|
||||
|
||||
"""
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
@spec space(t, t) :: doc_cons
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
@@ -277,7 +343,7 @@ defmodule Inspect.Algebra do
|
||||
"Hughes\nWadler"
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
@spec line(t, t) :: doc_cons
|
||||
def line(x, y), do: concat(x, concat(:doc_line, y))
|
||||
|
||||
@doc """
|
||||
@@ -301,7 +367,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Elixir conveniences
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Surrounds a document with characters.
|
||||
|
||||
Puts the document between left and right enclosing and nesting it.
|
||||
@@ -320,7 +386,7 @@ defmodule Inspect.Algebra do
|
||||
group concat left, concat(nest(doc, @nesting), right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
@@ -398,7 +464,7 @@ defmodule Inspect.Algebra do
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
|
||||
@spec fits?(integer, [{integer, mode, t}]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
@@ -411,7 +477,7 @@ defmodule Inspect.Algebra do
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
|
||||
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
@@ -436,6 +502,6 @@ defmodule Inspect.Algebra do
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary sdoc
|
||||
iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,26 +26,28 @@ defmodule Integer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{integer, remainder_of_binary}`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.parse("34")
|
||||
{34,""}
|
||||
|
||||
iex> Integer.parse("34.5")
|
||||
{34,".5"}
|
||||
|
||||
iex> Integer.parse("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { integer, binary } | :error
|
||||
@spec parse(binary) :: {integer, binary} | :error
|
||||
def parse(<< ?-, bin :: binary >>) do
|
||||
case do_parse(bin) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,6 +67,6 @@ defmodule Integer do
|
||||
end
|
||||
|
||||
defp do_parse(bitstring, acc) do
|
||||
{ acc, bitstring }
|
||||
{acc, bitstring}
|
||||
end
|
||||
end
|
||||
|
||||
+132
-59
@@ -1,11 +1,66 @@
|
||||
defexception IO.StreamError, [:reason, :message] do
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = iolist_to_binary(:file.format_error(reason))
|
||||
formatted = iodata_to_binary(:file.format_error(reason))
|
||||
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{device: device, raw: raw} = stream) do
|
||||
{:ok, into(stream, device, raw)}
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Functions handling IO.
|
||||
@@ -15,22 +70,35 @@ defmodule IO do
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcuts to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
The majority of the functions expect data encoded in UTF-8
|
||||
and will do a conversion to string, via the `String.Chars`
|
||||
protocol (as shown in typespecs).
|
||||
The majority of the functions expect char data, i.e. strings or
|
||||
lists of characters and strings. In case another type is given,
|
||||
it will do a conversion to string via the `String.Chars` protocol
|
||||
(as shown in typespecs).
|
||||
|
||||
The functions starting with `bin*` expects iodata as arguments,
|
||||
i.e. iolists or binaries with no particular encoding.
|
||||
The functions starting with `bin*` expects iodata as argument,
|
||||
i.e. binaries or lists of bytes and binaries.
|
||||
|
||||
## IO devices
|
||||
|
||||
An IO device may be an atom or a pid. In case it is an atom,
|
||||
the atom must be the name of a registered process. However,
|
||||
there are three exceptions for this rule:
|
||||
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@type chardata :: char_list | String.Chars.t
|
||||
@type nodata :: { :error, term } | :eof
|
||||
@type device :: atom | pid
|
||||
@type nodata :: {:error, term} | :eof
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
defmacrop is_iolist(data) do
|
||||
defmacrop is_iodata(data) do
|
||||
quote do
|
||||
is_list(unquote(data)) or is_binary(unquote(data))
|
||||
end
|
||||
@@ -48,7 +116,7 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
@spec read(device, :line | non_neg_integer) :: char_data | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
@@ -76,24 +144,22 @@ defmodule IO do
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
{:ok, data} -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def binread(device, count) when count >= 0 do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
{:ok, data} -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
By default the device is the standard output.
|
||||
The argument is expected to be a chardata (i.e.
|
||||
a char list or an unicode binary).
|
||||
|
||||
By default the device is the standard output.
|
||||
It returns `:ok` if it succeeds.
|
||||
|
||||
## Examples
|
||||
@@ -105,9 +171,9 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
@spec write(device, chardata) :: :ok
|
||||
@spec write(device, char_data | String.Chars.t) :: :ok
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
:io.put_chars map_dev(device), to_char_data(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -116,29 +182,32 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | { :error, term }
|
||||
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
|
||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the argument to the device, similar to `write/2`,
|
||||
but adds a newline at the end. The argument is expected
|
||||
to be a chardata.
|
||||
to be a char_data.
|
||||
"""
|
||||
@spec puts(device, chardata) :: :ok
|
||||
@spec puts(device, char_data | String.Chars.t) :: :ok
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
:io.put_chars erl_dev, [to_char_data(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a newline. A set of options can be given.
|
||||
Inspects and writes the given argument to the device.
|
||||
|
||||
It sets by default pretty printing to true and the
|
||||
width to be the width of the device, with a minimum
|
||||
of 80 characters.
|
||||
It sets by default pretty printing to true and returns
|
||||
the item itself.
|
||||
|
||||
Note this function does not use the IO device width
|
||||
because some IO devices does not implement the
|
||||
appropriate functions. Setting the width must be done
|
||||
explicitly by passing the `:width` option.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -156,14 +225,6 @@ defmodule IO do
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
|
||||
unless Keyword.get(opts, :width) do
|
||||
opts = case :io.columns(device) do
|
||||
{ :ok, width } -> Keyword.put(opts, :width, width)
|
||||
{ :error, _ } -> opts
|
||||
end
|
||||
end
|
||||
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
@@ -183,8 +244,8 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
@@ -201,9 +262,9 @@ defmodule IO do
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
"""
|
||||
@spec getn(device, chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
:io.get_chars(map_dev(device), to_char_data(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -218,15 +279,20 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
:io.get_line(map_dev(device), to_char_data(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line if `:line` is given or by a given
|
||||
number of codepoints.
|
||||
Converts the io device into a `IO.Stream`.
|
||||
|
||||
An `IO.Stream` implements both `Enumerable` and
|
||||
`Collectable`, allowing it to be used for both read
|
||||
and write.
|
||||
|
||||
The device is iterated line by line if `:line` is given or
|
||||
by a given number of codepoints.
|
||||
|
||||
This reads the IO as utf-8. Check out
|
||||
`IO.binstream/2` to handle the IO as a raw binary.
|
||||
@@ -244,51 +310,58 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
|
||||
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line or by a number of bytes. This
|
||||
reads the IO as a raw binary.
|
||||
Converts the IO device into a `IO.Stream`.
|
||||
|
||||
An `IO.Stream` implements both `Enumerable` and
|
||||
`Collectable`, allowing it to be used for both read
|
||||
and write.
|
||||
|
||||
The device is iterated line by line or by a number of bytes.
|
||||
This reads the IO device as a raw binary.
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
|
||||
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_stream(device, what) do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{ data, device }
|
||||
{data, device}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_binstream(device, what) do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{ data, device }
|
||||
{data, device}
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, map_dev: 1, to_char_data: 1}
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other), do: other
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
defp to_char_data(list) when is_list(list), do: list
|
||||
defp to_char_data(other), do: to_string(other)
|
||||
end
|
||||
|
||||
+50
-44
@@ -7,8 +7,8 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
|
||||
defp escape_sequence(unquote(atom_to_list(name))) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,7 +17,7 @@ end
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
Functionality to render ANSI escape sequences
|
||||
(http:\\en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
"""
|
||||
@@ -28,12 +28,15 @@ defmodule IO.ANSI do
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be displayed as intended.
|
||||
be displayed as intended. This is checked by sending a message to
|
||||
the group leader. In case the group leader does not support the message,
|
||||
it will likely lead to a timeout (and a slow down on execution time).
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
!match?({:win32, _}, :os.type()) and
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -72,7 +75,7 @@ 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
|
||||
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
@@ -92,7 +95,7 @@ defmodule IO.ANSI do
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
for {code, color} <- colors do
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@@ -127,18 +130,11 @@ defmodule IO.ANSI do
|
||||
@doc "Clear screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
|
||||
# Catch spaces between codes
|
||||
defp escape_sequence(<< ?\s, rest :: binary >>) do
|
||||
escape_sequence(rest)
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, %r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
@@ -160,15 +156,15 @@ defmodule IO.ANSI do
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
@@ -183,40 +179,50 @@ defmodule IO.ANSI do
|
||||
|
||||
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
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
end
|
||||
|
||||
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, true, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, [x|acc])
|
||||
end
|
||||
defp do_escape("", false, _emit, emitted, acc) do
|
||||
{iolist_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
sequences =
|
||||
buffer
|
||||
|> Enum.reverse()
|
||||
|> :string.tokens(',')
|
||||
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|
||||
|> Enum.reverse()
|
||||
|
||||
defp do_escape_sequence(rest, emit, acc) do
|
||||
{code, rest} = escape_sequence(rest)
|
||||
if emit do
|
||||
acc = [code|acc]
|
||||
if emit and sequences != [] do
|
||||
do_escape(t, emit, true, nil, sequences ++ acc)
|
||||
else
|
||||
do_escape(t, emit, emitted, nil, acc)
|
||||
end
|
||||
do_escape(rest, true, emit, true, acc)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
do_escape(t, emit, emitted, [h|buffer], acc)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, [], acc)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, nil, [h|acc])
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
end
|
||||
|
||||
+137
-138
@@ -3,33 +3,10 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
@bullets [?*, ?-, ?+]
|
||||
|
||||
@default_colors [ enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline" ]
|
||||
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature).
|
||||
The default options used by this module.
|
||||
|
||||
See `print/3` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, colors \\ @default_colors) do
|
||||
IO.puts IO.ANSI.reset
|
||||
width = column_width()
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, colors)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a truth value for whether to use ANSI
|
||||
escape codes, and a keyword list for the printing color settings. Supported
|
||||
keys for the color settings are:
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
@@ -38,53 +15,82 @@ defmodule IO.ANSI.Docs do
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
|
||||
Values for the color settings are strings with comma-separated attributes.
|
||||
Supported attributes are:
|
||||
|
||||
* Colors: `black red green yellow blue magenta cyan white`
|
||||
* Intensity: `normal bright`
|
||||
* Decoration: `underline reverse`
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
def print(doc, colors \\ @default_colors) do
|
||||
def default_options do
|
||||
[enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline",
|
||||
width: 80]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature).
|
||||
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, options \\ []) do
|
||||
IO.puts IO.ANSI.reset
|
||||
options = Keyword.merge(default_options, options)
|
||||
width = options[:width]
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a set of options
|
||||
defined in `default_options/1`.
|
||||
"""
|
||||
def print(doc, options \\ []) do
|
||||
options = Keyword.merge(default_options, options)
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", colors)
|
||||
|> process("", options)
|
||||
end
|
||||
|
||||
defp process([], _indent, _colors), do: nil
|
||||
defp process([], _indent, _options), do: nil
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, colors) do
|
||||
write_h1(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
defp process(["# " <> heading | rest], _indent, options) do
|
||||
write_h1(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, colors) do
|
||||
write_h2(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
defp process(["## " <> heading | rest], _indent, options) do
|
||||
write_h2(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, colors) do
|
||||
write_h3(String.strip(heading), indent, colors)
|
||||
process(rest, indent, colors)
|
||||
defp process(["### " <> heading | rest], indent, options) do
|
||||
write_h3(String.strip(heading), indent, options)
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, colors) do
|
||||
process(rest, indent, colors)
|
||||
defp process(["" | rest], indent, options) do
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, colors) do
|
||||
process_code(rest, [line], indent, colors)
|
||||
defp process([" " <> line | rest], indent, options) do
|
||||
process_code(rest, [line], indent, options)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
defp process([line | rest], indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent, colors)
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false, colors)
|
||||
process_text(rest, [line], indent, false, options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,32 +99,32 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{ rest, acc }
|
||||
{rest, acc}
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, colors) do
|
||||
write_h2(String.upcase(heading), colors)
|
||||
defp write_h1(heading, options) do
|
||||
write_h2(String.upcase(heading), options)
|
||||
end
|
||||
|
||||
defp write_h2(heading, colors) do
|
||||
write(:doc_headings, heading, colors)
|
||||
defp write_h2(heading, options) do
|
||||
write(:doc_headings, heading, options)
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, colors) do
|
||||
defp write_h3(heading, indent, options) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, colors)
|
||||
write(:doc_headings, heading, options)
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, colors) do
|
||||
defp process_list(line, rest, count, indent, options) do
|
||||
IO.write indent <> "• "
|
||||
{ contents, rest, done } = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, colors)
|
||||
{contents, rest, done} = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, options)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, colors)
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
@@ -129,13 +135,13 @@ defmodule IO.ANSI.Docs do
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
:done -> { Enum.reverse(acc), [line|rest], false }
|
||||
:done -> {Enum.reverse(acc), [line|rest], false}
|
||||
chopped -> process_list_next(rest, count, false, [chopped|acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
|
||||
{ Enum.reverse(acc), rest, false }
|
||||
{Enum.reverse(acc), rest, false}
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
@@ -143,7 +149,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{ Enum.reverse(acc), rest, done }
|
||||
{Enum.reverse(acc), rest, done}
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
@@ -156,69 +162,69 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
process(doc, indent, colors)
|
||||
defp process_text(doc=["" | _], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
process(doc, indent, options)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
defp process_text([], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
defp process_text([line | rest], para, indent, true, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true, colors)
|
||||
process_list(item, rest, count, indent, colors)
|
||||
write_text(Enum.reverse(para), indent, true, options)
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, colors)
|
||||
process_text(rest, [line | para], indent, true, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, colors) do
|
||||
process_text(rest, [line | para], indent, from_list, colors)
|
||||
defp process_text([line | rest], para, indent, from_list, options) do
|
||||
process_text(rest, [line | para], indent, from_list, options)
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, colors) do
|
||||
defp write_text(lines, indent, from_list, options) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> String.split(%r{\s})
|
||||
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
## Code blocks
|
||||
|
||||
defp process_code([], code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
defp process_code([], code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line, "" | code], indent, colors)
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, options) do
|
||||
process_code(rest, [line, "" | code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line|code], indent, colors)
|
||||
defp process_code([ " " <> line | rest ], code, indent, options) do
|
||||
process_code(rest, [line|code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
process(rest, indent, colors)
|
||||
defp process_code(rest, code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, colors) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
|
||||
defp write_code(code, indent, options) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp write(style, string, colors) do
|
||||
IO.puts color(style, colors) <> string <> IO.ANSI.reset
|
||||
defp write(style, string, options) do
|
||||
IO.puts color(style, options) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
end
|
||||
|
||||
@@ -227,7 +233,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp write_with_wrap(words, available, indent, first) do
|
||||
{ words, rest } = take_words(words, available, [])
|
||||
{words, rest} = take_words(words, available, [])
|
||||
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
|
||||
write_with_wrap(rest, available, indent, false)
|
||||
end
|
||||
@@ -242,16 +248,16 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# No space but we got no words
|
||||
acc == [] ->
|
||||
{ [word], words }
|
||||
{[word], words}
|
||||
|
||||
# Otherwise
|
||||
true ->
|
||||
{ Enum.reverse(acc), [word|words] }
|
||||
{Enum.reverse(acc), [word|words]}
|
||||
end
|
||||
end
|
||||
|
||||
defp take_words([], _available, acc) do
|
||||
{ Enum.reverse(acc), [] }
|
||||
{Enum.reverse(acc), []}
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
|
||||
@@ -264,7 +270,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{ _, rest } -> length_without_escape(rest, count + 1)
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
@@ -276,16 +282,16 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(%r{.*(https?\S*)}, text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(%r{_}, text, "\\\\_")
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
@@ -295,66 +301,66 @@ defmodule IO.ANSI.Docs do
|
||||
@spaced [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, colors)
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, colors)
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, colors)
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, colors)
|
||||
handle_inline(rest, limit, [mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, colors) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], colors)
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, colors) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, colors)
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
|
||||
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
|
||||
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, colors) do
|
||||
defp inline_buffer(buffer, options) do
|
||||
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
|
||||
[color_for(h, colors)|t]
|
||||
[color_for(h, options)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
@@ -367,11 +373,4 @@ defmodule IO.ANSI.Docs do
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
end
|
||||
|
||||
defp column_width() do
|
||||
case :io.columns do
|
||||
{ :ok, width } -> min(width, 80)
|
||||
_ -> 80
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+569
-468
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
defrecord Config, commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false
|
||||
@blank_config %{commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false}
|
||||
|
||||
@doc """
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = lc arg inlist argv, do: String.from_char_list!(arg)
|
||||
argv = for arg <- argv, do: String.from_char_data!(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
{config, argv} = process_argv(argv, @blank_config)
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
command_errors = for {:error, msg} <- command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
@@ -50,7 +50,7 @@ defmodule Kernel.CLI do
|
||||
System.halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
@@ -60,12 +60,12 @@ defmodule Kernel.CLI do
|
||||
defp at_exit(status) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
|
||||
lc hook inlist hooks do
|
||||
for hook <- hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
catch
|
||||
kind, reason ->
|
||||
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -76,35 +76,25 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|hs], _ } when h == hd(list) ->
|
||||
new_config = config.update_errors &["#{h} : Unknown option" | &1]
|
||||
{[h|hs], _} when h == hd(list) ->
|
||||
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
|
||||
callback.(hs, new_config)
|
||||
{ new_list, new_config } ->
|
||||
{new_list, new_config} ->
|
||||
callback.(new_list, new_config)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_error(:error, exception, trace) do
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, format_stacktrace(trace)
|
||||
end
|
||||
|
||||
defp print_error(kind, reason, trace) do
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts :stderr, format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
|
||||
end
|
||||
|
||||
defp format_stacktrace(stack) do
|
||||
Exception.format_stacktrace(prune_stacktrace(stack))
|
||||
end
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module]
|
||||
|
||||
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
end
|
||||
|
||||
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
|
||||
defp prune_stacktrace([{__MODULE__, :wrapper, 1, _}|_]) do
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -134,44 +124,44 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:app, h}|&1]
|
||||
process_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, config.halt(false)
|
||||
process_shared t, %{config | halt: false}
|
||||
end
|
||||
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:eval, h}|&1]
|
||||
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:require, h}|&1]
|
||||
process_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
|
||||
process_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(list, config) do
|
||||
{ list, config }
|
||||
{list, config}
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp process_argv(["--"|t], config) do
|
||||
{ config, t }
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_argv(["+compile"|t], config) do
|
||||
defp process_argv(["+elixirc"|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
@@ -180,7 +170,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -188,42 +178,42 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_argv([], config) do
|
||||
{ config, [] }
|
||||
{config, []}
|
||||
end
|
||||
|
||||
# Process compiler options
|
||||
|
||||
defp process_compiler(["--"|t], config) do
|
||||
{ config, t }
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, config.output(h)
|
||||
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 | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
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 | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
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 | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
process_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, config.verbose_compile(true)
|
||||
process_compiler t, %{config | verbose_compile: true}
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
@@ -232,18 +222,18 @@ defmodule Kernel.CLI do
|
||||
shared_option? list, config, &process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile &[pattern|&1]
|
||||
process_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
|
||||
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
|
||||
defp process_iex(["--"|t], config) do
|
||||
{ config, t }
|
||||
{config, t}
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
@@ -257,7 +247,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
@@ -265,12 +255,12 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_iex([], config) do
|
||||
{ config, [] }
|
||||
{config, []}
|
||||
end
|
||||
|
||||
# Process commands
|
||||
@@ -279,7 +269,7 @@ defmodule Kernel.CLI do
|
||||
if Node.alive? do
|
||||
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
|
||||
else
|
||||
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
|
||||
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -288,10 +278,11 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.Behaviour.start(binary_to_atom(app)) do
|
||||
{ :error, reason } ->
|
||||
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
|
||||
:ok ->
|
||||
case Application.ensure_all_started(binary_to_atom(app)) do
|
||||
{:error, {app, reason}} ->
|
||||
{:error, "--app : Could not start application #{app}: " <>
|
||||
Application.format_error(reason)}
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@@ -300,7 +291,7 @@ defmodule Kernel.CLI do
|
||||
if exec = find_elixir_executable(file) do
|
||||
wrapper fn -> Code.require_file(exec) end
|
||||
else
|
||||
{ :error, "-S : Could not find executable #{file}" }
|
||||
{:error, "-S : Could not find executable #{file}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -308,7 +299,7 @@ defmodule Kernel.CLI do
|
||||
if :filelib.is_regular(file) do
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{ :error, "No file named #{file}" }
|
||||
{:error, "No file named #{file}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -320,7 +311,7 @@ defmodule Kernel.CLI do
|
||||
if files != [] do
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
else
|
||||
{ :error, "-r : No files matched pattern #{pattern}" }
|
||||
{:error, "-r : No files matched pattern #{pattern}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -332,7 +323,7 @@ defmodule Kernel.CLI do
|
||||
if files != [] do
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
else
|
||||
{ :error, "-pr : No files matched pattern #{pattern}" }
|
||||
{:error, "-pr : No files matched pattern #{pattern}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -340,24 +331,24 @@ defmodule Kernel.CLI do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
case match_regular_files(patterns) do
|
||||
{ :ok, [] } ->
|
||||
{ :error, "No files matched provided patterns" }
|
||||
{ :ok, files } ->
|
||||
{:ok, []} ->
|
||||
{:error, "No files matched provided patterns"}
|
||||
{:ok, files} ->
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
end
|
||||
{ :missing, missing } ->
|
||||
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
|
||||
{:missing, missing} ->
|
||||
{:error, "No files matched pattern(s) #{Enum.join(missing, ",")}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp match_regular_files(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
[] -> { :missing, pattern }
|
||||
files -> { :ok, files }
|
||||
[] -> {:missing, pattern}
|
||||
files -> {:ok, files}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -372,9 +363,9 @@ defmodule Kernel.CLI do
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{ :ok, files }
|
||||
{:ok, files}
|
||||
else
|
||||
{ :missing, Enum.uniq(missing_patterns) }
|
||||
{:missing, Enum.uniq(missing_patterns)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -389,7 +380,7 @@ defmodule Kernel.CLI do
|
||||
# a .bat file that must be in the same directory as
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{ :win32, _ } ->
|
||||
{:win32, _} ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
_ ->
|
||||
|
||||
@@ -13,24 +13,27 @@ defmodule Kernel.ErrorHandler do
|
||||
:error_handler.undefined_lambda(module, fun, args)
|
||||
end
|
||||
|
||||
def release() do
|
||||
# On release, no further allow elixir_ensure_compiled
|
||||
# directives and revert to the original error handler.
|
||||
# Note we should not delete the elixir_compiler_pid though,
|
||||
# as we still want to send notifications to the compiler.
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
{:module, _} -> []
|
||||
{:error, _} ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, self(), ref, module }
|
||||
send parent, {:waiting, module, self(), ref, module}
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ ^ref, :ready } ->
|
||||
:ok
|
||||
{ ^ref, :release } ->
|
||||
# On release, no further allow elixir_ensure_compiled
|
||||
# directives and revert to the original error handler.
|
||||
# Note we should not delete the elixir_compiler_pid though,
|
||||
# as we still want to send notifications to the compiler.
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
{^ref, :ready} -> :ok
|
||||
{^ref, :release} -> release()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Kernel.LexicalTracker do
|
||||
# scope may be long gone, so it has no associated PID.
|
||||
if pid = to_pid(arg) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
@@ -30,7 +30,7 @@ defmodule Kernel.LexicalTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
|
||||
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
@@ -39,7 +39,7 @@ defmodule Kernel.LexicalTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
@@ -50,27 +50,27 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
@doc false
|
||||
def add_import(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_import, module, line, warn })
|
||||
:gen_server.cast(pid, {:add_import, module, line, warn})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_alias(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_alias, module, line, warn })
|
||||
:gen_server.cast(pid, {:add_alias, module, line, warn})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def remote_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :remote_dispatch, module })
|
||||
:gen_server.cast(pid, {:remote_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def import_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :import_dispatch, module })
|
||||
:gen_server.cast(pid, {:import_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def alias_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :alias_dispatch, module })
|
||||
:gen_server.cast(pid, {:alias_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -86,8 +86,8 @@ defmodule Kernel.LexicalTracker do
|
||||
defp unused(pid, pos) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.foldl(fn
|
||||
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
|
||||
[{ module, :erlang.element(pos, tuple) }|acc]
|
||||
{module, _, _} = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
|
||||
[{module, :erlang.element(pos, tuple)}|acc]
|
||||
_, acc ->
|
||||
acc
|
||||
end, [], ets) |> Enum.sort
|
||||
@@ -97,52 +97,52 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
|
||||
def init([]) do
|
||||
{ :ok, :ets.new(:lexical, [:protected]) }
|
||||
{:ok, :ets.new(:lexical, [:protected])}
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{ :reply, d, d }
|
||||
{:reply, d, d}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, d) do
|
||||
{ :stop, { :bad_call, request }, d }
|
||||
{:stop, {:bad_call, request}, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
def handle_cast({:remote_dispatch, module}, d) do
|
||||
add_module(d, module)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :import_dispatch, module }, d) do
|
||||
def handle_cast({:import_dispatch, module}, d) do
|
||||
add_dispatch(d, module, @import)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :alias_dispatch, module }, d) do
|
||||
def handle_cast({:alias_dispatch, module}, d) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, module, line, warn }, d) do
|
||||
def handle_cast({:add_import, module, line, warn}, d) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_alias, module, line, warn }, d) do
|
||||
def handle_cast({:add_alias, module, line, warn}, d) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
{:stop, :normal, d}
|
||||
end
|
||||
|
||||
def handle_cast(msg, d) do
|
||||
{ :stop, { :bad_cast, msg }, d }
|
||||
{:stop, {:bad_cast, msg}, d}
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
@@ -150,7 +150,7 @@ defmodule Kernel.LexicalTracker do
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
{:ok, d}
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
@@ -160,16 +160,16 @@ defmodule Kernel.LexicalTracker do
|
||||
# If the value is true, it was imported/aliased
|
||||
# If the value is a line, it was imported/aliased and has a pending warning
|
||||
defp add_module(d, module) do
|
||||
:ets.insert_new(d, { module, false, false })
|
||||
:ets.insert_new(d, {module, false, false})
|
||||
end
|
||||
|
||||
defp add_dispatch(d, module, pos) do
|
||||
:ets.update_element(d, module, { pos, true })
|
||||
:ets.update_element(d, module, {pos, true})
|
||||
end
|
||||
|
||||
defp add_directive(d, module, line, warn, pos) do
|
||||
add_module(d, module)
|
||||
marker = if warn, do: line, else: true
|
||||
:ets.update_element(d, module, { pos, marker })
|
||||
:ets.update_element(d, module, {pos, marker})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,30 +43,30 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({ :reset_warnings, compiler_pid })
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok -> result
|
||||
:error -> raise CompileError, [], []
|
||||
:error -> exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
|
||||
send h, { ref, :ready }
|
||||
waiting = List.keydelete(waiting, h, 0)
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
@@ -74,7 +74,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{ pid, ref } =
|
||||
{pid, ref} =
|
||||
:erlang.spawn_monitor fn ->
|
||||
# Notify Code.ensure_compiled/2 that we should
|
||||
# attempt to compile the module by doing a dispatch.
|
||||
@@ -90,24 +90,27 @@ defmodule Kernel.ParallelCompiler do
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
{ :compiled, h }
|
||||
{:compiled, h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
|
||||
send child, { ref, :release }
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
@@ -118,82 +121,127 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
send child, { ref, :ack }
|
||||
send child, {ref, :ack}
|
||||
|
||||
available = lc { pid, _, waiting_module } inlist waiting,
|
||||
waiting_module == module,
|
||||
available = for {_kind, pid, _, waiting_module} <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ files, original, output, callbacks,
|
||||
waiting, queued, schedulers, [module|result])
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{ :waiting, child, ref, on } ->
|
||||
# Oops, we already got this module. Do not put it on waiting.
|
||||
if :lists.member(on, result) do
|
||||
send child, { :release, ref }
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
defined = fn {k, m} -> on == m and k in [kind, :module] end
|
||||
|
||||
# Oops, we already got it, do not put it on waiting.
|
||||
if :lists.any(defined, result) do
|
||||
send child, {ref, :ready}
|
||||
else
|
||||
waiting = [{ child, ref, on }|waiting]
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, down_pid, 0)
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, other } ->
|
||||
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
|
||||
defp handle_failure(ref, reason, entries, waiting, queued) do
|
||||
if file = find_failure(ref, queued) do
|
||||
print_failure(file, reason)
|
||||
if all_missing?(entries, waiting, queued) do
|
||||
collect_failures(queued, length(queued) - 1)
|
||||
end
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
defp find_failure(ref, queued) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{ child, ^ref, file } ->
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
end
|
||||
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
_ ->
|
||||
:ok
|
||||
{_child, ^ref, file} -> file
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
defp print_failure(_file, {:compiled, _}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
waiting_length == length(queued)
|
||||
defp print_failure(file, {:failure, kind, reason, stacktrace}) do
|
||||
IO.puts "\n== Compilation error on file #{Path.relative_to_cwd(file)} =="
|
||||
IO.puts Exception.format(kind, reason, prune_stacktrace(stacktrace))
|
||||
end
|
||||
|
||||
defp print_failure(file, reason) do
|
||||
IO.puts "\n== Compilation error on file #{Path.relative_to_cwd(file)} =="
|
||||
IO.puts Exception.format(:exit, reason, [])
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
end
|
||||
|
||||
defp prune_stacktrace([h|t]) do
|
||||
[h|prune_stacktrace(t)]
|
||||
end
|
||||
|
||||
defp prune_stacktrace([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp all_missing?(entries, waiting, queued) do
|
||||
entries == [] and waiting != [] and
|
||||
length(waiting) == length(queued)
|
||||
end
|
||||
|
||||
defp collect_failures(_queued, 0), do: :ok
|
||||
|
||||
defp collect_failures(queued, remaining) do
|
||||
receive do
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
if file = find_failure(down_ref, queued) do
|
||||
print_failure(file, reason)
|
||||
collect_failures(queued, remaining - 1)
|
||||
else
|
||||
collect_failures(queued, remaining)
|
||||
end
|
||||
after
|
||||
# Give up if no failure appears in 5 seconds
|
||||
5000 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,9 +33,9 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
compiler_pid = :erlang.get(:elixir_compiler_pid)
|
||||
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
|
||||
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
|
||||
{:error_handler, handler} = :erlang.process_info(parent, :error_handler)
|
||||
|
||||
{ pid, ref } = :erlang.spawn_monitor fn ->
|
||||
{pid, ref} = :erlang.spawn_monitor fn ->
|
||||
if compiler_pid != :undefined do
|
||||
:erlang.put(:elixir_compiler_pid, compiler_pid)
|
||||
end
|
||||
@@ -48,27 +48,27 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
exit(try do
|
||||
new = Code.require_file(h) || []
|
||||
{ :required, Enum.map(new, &elem(&1, 0)), h }
|
||||
{:required, Enum.map(new, &elem(&1, 0)), h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_requires(t, [{ pid, ref }|waiting], callback, schedulers, result)
|
||||
spawn_requires(t, [{pid, ref}|waiting], callback, schedulers, result)
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, schedulers, result) do
|
||||
receive do
|
||||
{ :DOWN, ref, :process, pid, status } ->
|
||||
tuple = { pid, ref }
|
||||
{:DOWN, ref, :process, pid, status} ->
|
||||
tuple = {pid, ref}
|
||||
if tuple in waiting do
|
||||
case status do
|
||||
{ :required, mods, file } ->
|
||||
{:required, mods, file} ->
|
||||
callback.(file)
|
||||
result = mods ++ result
|
||||
waiting = List.delete(waiting, tuple)
|
||||
{ :failure, kind, reason, stacktrace } ->
|
||||
{:failure, kind, reason, stacktrace} ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
other ->
|
||||
:erlang.raise(:exit, other, [])
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
# This is an optimization Elixir runs on function clauses.
|
||||
# Whenever a variables matches against a record (a tagged
|
||||
# tuple), this information is stored in order to optimize
|
||||
# record calls.
|
||||
defmodule Kernel.RecordRewriter do
|
||||
@moduledoc false
|
||||
|
||||
def optimize_clause(module, clause) do
|
||||
optimize_clause(clause, module, :orddict.new)
|
||||
end
|
||||
|
||||
## Clause
|
||||
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ body, dict, res } = optimize_body(body, module, dict, [])
|
||||
{ { :clause, line, args, guards, body }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_args(args, module, dict) do
|
||||
Enum.map_reduce args, dict, fn(arg, acc) ->
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
|
||||
{ new_arg, new_acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_body([], _module, dict, _acc) do
|
||||
{ [], dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_body([h], module, dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
|
||||
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
|
||||
end
|
||||
|
||||
defp optimize_body([h|t], module, dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
|
||||
optimize_body(t, module, new_dict, [new_expr|acc])
|
||||
end
|
||||
|
||||
## Record helpers
|
||||
|
||||
defp record_fields(record, record) do
|
||||
fields = Module.get_attribute(record, :record_fields)
|
||||
optimizable = Module.get_attribute(record, :record_optimizable)
|
||||
unless nil?(fields) or nil?(optimizable) do
|
||||
{ (lc { k, _ } inlist fields, do: k), optimizable }
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(_module, record) do
|
||||
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
|
||||
try do
|
||||
fields = lc { k, _ } inlist record.__record__(:fields), do: k
|
||||
optimizable = record.__record__(:optimizable)
|
||||
{ fields, optimizable }
|
||||
rescue
|
||||
[UndefinedFunctionError, FunctionClauseError] -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_field_info(function) do
|
||||
case atom_to_list(function) do
|
||||
'update' -> nil
|
||||
'update_' ++ _field -> nil
|
||||
_ -> { :accessor, function }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if { function, length(args) + 1 } in optimizable do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, &(field == &1)) do
|
||||
optimize_record_accessor_call(line, res, kind, field, index, left, args)
|
||||
end
|
||||
nil ->
|
||||
optimize_record_other_call(line, record, res, function, left, args)
|
||||
end
|
||||
end
|
||||
|
||||
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(_line, _module, _res, _left, _right, _args) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
|
||||
[{ :integer, 0, index + 2 }, left]
|
||||
}
|
||||
{ call, nil }
|
||||
end
|
||||
|
||||
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
|
||||
[{ :integer, 0, index + 2 }, left, arg]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
defp optimize_record_other_call(line, record, res, function, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
|
||||
args ++ [left]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line,
|
||||
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
|
||||
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
|
||||
|
||||
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
|
||||
{ value, dict, _ } = optimize_expr(value, module, dict)
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
case optimize_call(call_line, module, res, left, right, args) do
|
||||
{ call, call_res } ->
|
||||
{ call, dict, call_res }
|
||||
nil ->
|
||||
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, line, expr, args }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ { :call, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :match, line, left, right }, module, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, module, dict)
|
||||
|
||||
match = { :match, line, left, right }
|
||||
|
||||
if left_res do
|
||||
dict = assign_vars(extract_vars(right, []), dict, left_res)
|
||||
end
|
||||
|
||||
if right_res do
|
||||
dict = assign_vars(extract_vars(left, []), dict, right_res)
|
||||
end
|
||||
|
||||
{ match, dict, right_res || left_res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :op, line, op, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, expr }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :op, line, op, expr }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin, line, elements }, module, dict) do
|
||||
{ elements, dict } = optimize_args(elements, module, dict)
|
||||
{ { :bin, line, elements }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :cons, line, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :cons, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :block, line, args }, module, dict) do
|
||||
{ args, dict, res } = optimize_body(args, module, dict, [])
|
||||
{ { :block, line, args }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :tuple, line, args }, module, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
|
||||
args_res = if Enum.any?(args_res), do: args_res, else: nil
|
||||
|
||||
res =
|
||||
case args do
|
||||
[{ :atom, _, atom }|_] -> atom
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{ { :tuple, line, args }, dict, { res, args_res } }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :var, _, name } = var, _module, dict) do
|
||||
case :orddict.find(name, dict) do
|
||||
{ :ok, res } -> { var, dict, res }
|
||||
:error -> { var, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :case, line, expr, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :receive, line, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
|
||||
|
||||
dict = join_dict(tuples, dict)
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ { :receive, line, clauses, after_key, after_value }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
|
||||
catches = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
|
||||
elses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ body, _, res } = optimize_body(body, module, dict, [])
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
|
||||
{ { :try, line, body, elses, catches, try_after }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
|
||||
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, module, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, module, dict)
|
||||
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
|
||||
clauses = lc clause inlist clauses do
|
||||
{ clause, _, _ } = optimize_clause(clause, module, dict)
|
||||
clause
|
||||
end
|
||||
|
||||
{ { :fun, line, { :clauses, clauses } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, module, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
|
||||
{ { comprehension, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { generate, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
{ other, dict, nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp optimize_tuple_args(args, module, dict) do
|
||||
{ final_args, { final_dict, final_acc } } =
|
||||
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
|
||||
{ new_arg, { new_acc, [res|acc_res] } }
|
||||
end
|
||||
|
||||
{ final_args, final_dict, Enum.reverse(final_acc) }
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
|
||||
assign_vars t, assign_nested_vars(key, dict, value), res
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
|
||||
dict =
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } ->
|
||||
dict
|
||||
{ :ok, { ^value, _ } } ->
|
||||
:orddict.store(key, { value, nil }, dict)
|
||||
{ :ok, _ } ->
|
||||
# We are overriding a type of an existing variable,
|
||||
# which means the source code is invalid.
|
||||
:orddict.store(key, nil, dict)
|
||||
:error ->
|
||||
:orddict.store(key, res, dict)
|
||||
end
|
||||
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([_|t], dict, res) do
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([], dict, _res) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
|
||||
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
|
||||
end
|
||||
|
||||
defp assign_nested_vars([], dict, []) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp extract_vars({ :match, _, left, right }, vars) do
|
||||
vars = extract_vars(right, vars)
|
||||
extract_vars(left, vars)
|
||||
end
|
||||
|
||||
defp extract_vars({ :var, _, name }, vars) do
|
||||
[name|vars]
|
||||
end
|
||||
|
||||
defp extract_vars({ :tuple, _, args }, vars) do
|
||||
[Enum.map(args, &extract_vars(&1, []))|vars]
|
||||
end
|
||||
|
||||
defp extract_vars(_, vars) do
|
||||
vars
|
||||
end
|
||||
|
||||
defp join_dict([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t]) do
|
||||
join_dict(t, dict)
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t], other) do
|
||||
other = Enum.reduce other, other, fn
|
||||
{ key, { value, _ } = res }, acc ->
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } -> acc
|
||||
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
|
||||
{ :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)
|
||||
end
|
||||
|
||||
defp join_dict([], other) do
|
||||
other
|
||||
end
|
||||
|
||||
defp join_result([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t]) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t], res) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
|
||||
join_result(t, { res, nil })
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, _ }|_], _res) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp join_result([], res) do
|
||||
res
|
||||
end
|
||||
end
|
||||
@@ -20,22 +20,141 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a new tuple.
|
||||
Creates a tuple.
|
||||
|
||||
Only two item tuples are considered literals in Elixir.
|
||||
Therefore all other tuples are represented in the AST
|
||||
as a call to the special form `:{}`.
|
||||
|
||||
Conveniences for manipulating tuples can be found in the
|
||||
`Tuple` module. Some functions for working with tuples are
|
||||
also available in `Kernel`, namely `Kernel.elem/2`,
|
||||
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
iex> quote do: { 1, 2, 3 }
|
||||
{ :{}, [], [1,2,3] }
|
||||
iex> {1, 2, 3}
|
||||
{1, 2, 3}
|
||||
|
||||
iex> quote do: {1, 2, 3}
|
||||
{:{}, [], [1,2,3]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:{})(args)
|
||||
|
||||
@doc """
|
||||
Creates a map.
|
||||
|
||||
Maps are key-value stores where keys are compared
|
||||
using the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form where keys are associated via `=>`:
|
||||
|
||||
%{1 => 2}
|
||||
|
||||
Maps also support the keyword notation, as other special forms,
|
||||
as long as they are at the end of the argument list:
|
||||
|
||||
%{hello: :world, with: :keywords}
|
||||
%{:hello => :world, with: :keywords}
|
||||
|
||||
If a map has duplicated keys, the last key will always have
|
||||
higher precedence:
|
||||
|
||||
iex> %{a: :b, a: :c}
|
||||
%{a: :c}
|
||||
|
||||
Conveniences for manipulating maps can be found in the
|
||||
`Map` module.
|
||||
|
||||
## Access syntax
|
||||
|
||||
Besides the access functions available in the `Map` module,
|
||||
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
|
||||
`.` operator:
|
||||
|
||||
iex> map = %{a: :b}
|
||||
iex> map.a
|
||||
:b
|
||||
|
||||
Note that the `.` operator expects the field to exist in the map.
|
||||
If not, an `ArgumentError` is raised.
|
||||
|
||||
## Update syntax
|
||||
|
||||
Maps also support an update syntax:
|
||||
|
||||
iex> map = %{:a => :b}
|
||||
iex> %{map | :a => :c}
|
||||
%{:a => :c}
|
||||
|
||||
Notice the update syntax requires the given keys to exist.
|
||||
Trying to update a key that does not exist will raise an `ArgumentError`.
|
||||
|
||||
## AST representation
|
||||
|
||||
Regardless if `=>` or the keywords syntax is used, Maps are
|
||||
always represented internally as a list of two-items tuples
|
||||
for simplicity:
|
||||
|
||||
iex> quote do: %{:a => :b, c: :d}
|
||||
{:%{}, [], [{:a, :b}, {:c, :d}]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:%{})(args)
|
||||
|
||||
@doc """
|
||||
Creates a struct.
|
||||
|
||||
A struct is a tagged map that allows developers to provide
|
||||
default values for keys, tags to be used in polymorphic
|
||||
dispatches and compile time assertions.
|
||||
|
||||
To define a struct, you just need to implement the `__struct__/0`
|
||||
function in a module:
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{name: "josé", age: 27}
|
||||
end
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
regular map operations:
|
||||
|
||||
user = %User{}
|
||||
Map.put(user, :a_non_struct_key, :value)
|
||||
|
||||
An update operation specific for structs is also available:
|
||||
|
||||
%User{user | age: 28}
|
||||
|
||||
The syntax above will guarantee the given keys are valid at
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Check `Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also,
|
||||
see `Kernel.struct/2` for examples on how to create and update
|
||||
structs dynamically.
|
||||
"""
|
||||
defmacro unquote(:%)(struct, map)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
|
||||
@@ -108,7 +227,7 @@ defmodule Kernel.SpecialForms do
|
||||
signedness, e.g.:
|
||||
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
...> val
|
||||
iex> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
@@ -200,12 +319,16 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> Kernel.Sample
|
||||
Kernel.Sample
|
||||
|
||||
iex> Kernel.length([1,2,3])
|
||||
3
|
||||
|
||||
iex> Kernel.+(1, 2)
|
||||
3
|
||||
|
||||
iex> Kernel."length"([1,2,3])
|
||||
3
|
||||
|
||||
iex> Kernel.'+'(1, 2)
|
||||
3
|
||||
|
||||
@@ -229,6 +352,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
@@ -331,7 +455,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviours could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
@@ -377,7 +501,7 @@ defmodule Kernel.SpecialForms do
|
||||
for example:
|
||||
|
||||
iex> import List
|
||||
...> flatten([1, [2], 3])
|
||||
iex> flatten([1, [2], 3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
@@ -443,7 +567,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviours could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
@@ -495,28 +619,28 @@ defmodule Kernel.SpecialForms do
|
||||
value, instead of rebinding. This can be done with the `^` special form:
|
||||
|
||||
iex> x = 1
|
||||
iex> ^x = 1
|
||||
iex> ^x = 2
|
||||
iex> ^x = List.first([1])
|
||||
iex> ^x = List.first([2])
|
||||
** (MatchError) no match of right hand side value: 2
|
||||
|
||||
Note that `^` always refers to the value of x prior to the match. The
|
||||
following example will match:
|
||||
|
||||
iex> x = 0
|
||||
iex> { x, ^x } = { 1, 0 }
|
||||
iex> {x, ^x} = {1, 0}
|
||||
iex> x
|
||||
1
|
||||
|
||||
"""
|
||||
defmacro ^(var)
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Gets the representation of any expression.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> quote do: sum(1, 2, 3)
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> {:sum, [], [1, 2, 3]}
|
||||
|
||||
## Explanation
|
||||
|
||||
@@ -524,7 +648,7 @@ defmodule Kernel.SpecialForms do
|
||||
The building block of Elixir macros is a tuple with three elements,
|
||||
for example:
|
||||
|
||||
{ :sum, [], [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:
|
||||
@@ -901,7 +1025,7 @@ defmodule Kernel.SpecialForms do
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.each kv, fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
@@ -910,7 +1034,7 @@ defmodule Kernel.SpecialForms do
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.map kv, fn { k, v } ->
|
||||
Enum.map kv, fn {k, v} ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
@@ -937,7 +1061,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
Enum.each unquote(kv), fn { k, v } ->
|
||||
Enum.each unquote(kv), fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
@@ -958,7 +1082,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote bind_quoted: [kv: kv] do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.each kv, fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
@@ -983,13 +1107,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Which would then return:
|
||||
|
||||
{ :sum, [], [1, { :value, [], quoted }, 3] }
|
||||
{: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] }
|
||||
#=> {:sum, [], [1, 13, 3]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:unquote)(expr)
|
||||
@@ -1002,82 +1126,78 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
values = [2, 3, 4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
#=> {:sum, [], [1, 2, 3, 4, 5]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:unquote_splicing)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
@doc ~S"""
|
||||
Comprehensions allow you to quickly build a data structure from
|
||||
an enumerable or a bitstring.
|
||||
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
Let's start with an example:
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
are defined using both `inlist` and `inbits` operators, allowing you
|
||||
to loop lists and bitstrings:
|
||||
iex> for n <- [1, 2, 3, 4], do: n * 2
|
||||
[2, 4, 6, 8]
|
||||
|
||||
A comprehension accepts many generators and filters. Enumerable
|
||||
generators are defined using `<-`:
|
||||
|
||||
# A list generator:
|
||||
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
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
iex> list = [1, 2, 3, 4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
iex> for n <- [1, 2, 3, 4], 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
|
||||
[2,3,4,6]
|
||||
iex> for x <- [1, 2], y <- [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
|
||||
[2,4,6]
|
||||
iex> for n <- [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:
|
||||
Note generators can also be used to filter as it removes any value
|
||||
that doesn't match the left side of `<-`:
|
||||
|
||||
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do
|
||||
...> String.upcase(name)
|
||||
...> end
|
||||
["JOSE", "ERIC"]
|
||||
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring 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> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
Variable assignments in the `do` block of the comprehension
|
||||
are not reflected outside of the block.
|
||||
Variable assignments inside the comprehension, be it in generators,
|
||||
filters or inside the block, are not reflected outside of the
|
||||
comprehension.
|
||||
|
||||
Note: Unlike Erlang, Elixir comprehension filters
|
||||
never behave as guards when it comes to errors. Errors in
|
||||
list comprehensions will always be raised. Consider this
|
||||
Erlang example:
|
||||
## Into
|
||||
|
||||
erl> [I || I <- [1,2,3], hd(I)].
|
||||
[]
|
||||
In the examples above, the result returned by the comprehension was
|
||||
always a list. The returned result can be configured by passing an
|
||||
`:into` option, that accepts any structure as long as it implements
|
||||
the `Collectable` protocol.
|
||||
|
||||
In Elixir, it will raise:
|
||||
For example, we can use bitstring generators with the `:into` option
|
||||
to easily remove all spaces in a string:
|
||||
|
||||
iex> lc i inlist [1,2,3], hd(i), do: i
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
defmacro lc(args)
|
||||
|
||||
@doc """
|
||||
Defines a bit comprehension.
|
||||
|
||||
It follows the same syntax and behaviour as a list comprehension
|
||||
but expects each element returned to be a bitstring. For example,
|
||||
here is how to remove all spaces from a string:
|
||||
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
|
||||
"helloworld"
|
||||
|
||||
The `IO` module provides streams, that are both `Enumerable` and
|
||||
`Collectable`, here is an upcase echo server using comprehensions:
|
||||
|
||||
for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
|
||||
String.upcase(line)
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro bc(args)
|
||||
defmacro for(args)
|
||||
|
||||
@doc """
|
||||
Defines an anonymous function.
|
||||
@@ -1099,16 +1219,18 @@ 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)
|
||||
|
||||
@doc """
|
||||
Captures an anonymous function.
|
||||
Captures or creates an anonymous function.
|
||||
|
||||
The most common format to capture a function is via module,
|
||||
name and arity:
|
||||
## Capture
|
||||
|
||||
The capture operator is most commonly used to capture a
|
||||
function with given name and arity from a module:
|
||||
|
||||
iex> fun = &Kernel.is_atom/1
|
||||
iex> fun.(:atom)
|
||||
@@ -1116,66 +1238,56 @@ defmodule Kernel.SpecialForms do
|
||||
iex> fun.("string")
|
||||
false
|
||||
|
||||
Local functions, including private ones, and imported ones
|
||||
can also be captured by omitting the module name:
|
||||
In the example above, we captured `Kernel.is_atom/1` as an
|
||||
anonymous function and then invoked it.
|
||||
|
||||
The capture operator can also be used to capture local functions,
|
||||
including private ones, and imported functions by omitting the
|
||||
module name:
|
||||
|
||||
&local_function/1
|
||||
|
||||
A capture also allows the captured functions to be partially
|
||||
applied, for example:
|
||||
## Anonymous functions
|
||||
|
||||
iex> fun = &is_record(&1, Range)
|
||||
iex> fun.(1..3)
|
||||
The capture operator can also be used to partially apply
|
||||
functions, where `&1`, `&2` and so on can be used as value
|
||||
placeholders. For example:
|
||||
|
||||
iex> double = &(&1 * 2)
|
||||
iex> double.(2)
|
||||
4
|
||||
|
||||
In other words, `&(&1 * 2)` is equivalent to `fn x -> x * 2 end`.
|
||||
Another example using a local function:
|
||||
|
||||
iex> fun = &is_atom(&1)
|
||||
iex> fun.(:atom)
|
||||
true
|
||||
|
||||
In the example above, we use &1 as a placeholder, generating
|
||||
a function with one argument. We could also use `&2` and `&3`
|
||||
to delimit more arguments:
|
||||
|
||||
iex> fun = &is_record(&1, &2)
|
||||
iex> fun.(1..3, Range)
|
||||
true
|
||||
|
||||
Since operators are calls, they are also supported, although
|
||||
they require explicit parentheses around:
|
||||
|
||||
iex> fun = &(&1 + &2)
|
||||
iex> fun.(1, 2)
|
||||
3
|
||||
|
||||
And even more complex call expressions:
|
||||
The `&` operator can be used with more complex expressions:
|
||||
|
||||
iex> fun = &(&1 + &2 + &3)
|
||||
iex> fun.(1, 2, 3)
|
||||
6
|
||||
|
||||
Record-like calls are also allowed:
|
||||
|
||||
iex> fun = &(&1.first)
|
||||
iex> fun.(1..3)
|
||||
1
|
||||
|
||||
Remember tuple and lists are represented as calls in the AST and
|
||||
therefore are also allowed:
|
||||
As well as with lists and tuples:
|
||||
|
||||
iex> fun = &{&1, &2}
|
||||
iex> fun.(1, 2)
|
||||
{ 1, 2 }
|
||||
{1, 2}
|
||||
|
||||
iex> fun = &[&1|&2]
|
||||
iex> fun.(1, 2)
|
||||
[1|2]
|
||||
|
||||
Anything that is not a call is not allowed though, examples:
|
||||
The only restrictions when creating anonymous functions is that at
|
||||
least one placeholder must be present, i.e. it must contain at least
|
||||
`&1`:
|
||||
|
||||
# An atom is not a call
|
||||
&:foo
|
||||
|
||||
# A var is not a call
|
||||
var = 1
|
||||
# No placeholder fails to compile
|
||||
&var
|
||||
|
||||
# A block expression is not a call
|
||||
# Block expressions are also not supported
|
||||
&(foo(&1, &2); &3 + &4)
|
||||
|
||||
"""
|
||||
@@ -1232,7 +1344,7 @@ defmodule Kernel.SpecialForms do
|
||||
## Examples
|
||||
|
||||
case thing do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value ->
|
||||
value
|
||||
@@ -1248,7 +1360,7 @@ defmodule Kernel.SpecialForms do
|
||||
outer context:
|
||||
|
||||
case data do
|
||||
{ :ok, value } -> value
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
@@ -1273,7 +1385,7 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
@@ -1286,10 +1398,10 @@ defmodule Kernel.SpecialForms do
|
||||
IO.puts "Invalid argument given"
|
||||
catch
|
||||
value ->
|
||||
IO.puts "caught \#{value}"
|
||||
IO.puts "caught #{value}"
|
||||
else
|
||||
value ->
|
||||
IO.puts "Success! The result was \#{value}"
|
||||
IO.puts "Success! The result was #{value}"
|
||||
after
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
@@ -1335,10 +1447,39 @@ defmodule Kernel.SpecialForms do
|
||||
x -> nil
|
||||
end
|
||||
|
||||
## Catching exits and Erlang errors
|
||||
## Erlang errors
|
||||
|
||||
The catch clause works exactly the same as in erlang. Therefore,
|
||||
one can also handle exits/errors coming from Erlang as below:
|
||||
Erlang errors are transformed into Elixir ones during rescue:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
rescue
|
||||
ArgumentError -> :ok
|
||||
end
|
||||
|
||||
The most common Erlang errors will be transformed into their
|
||||
Elixir counter-part. Those which are not will be transformed
|
||||
into `ErlangError`:
|
||||
|
||||
try do
|
||||
:erlang.error(:unknown)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
|
||||
In fact, ErlangError can be used to rescue any error that is
|
||||
not an Elixir error proper. For example, it can be used to rescue
|
||||
the earlier `:badarg` error too, prior to transformation:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
|
||||
## Catching throws and exits
|
||||
|
||||
The catch clause can be used to catch throws values and exits.
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
@@ -1347,14 +1488,14 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
try do
|
||||
error(:sample)
|
||||
throw(:sample)
|
||||
catch
|
||||
:error, :sample ->
|
||||
IO.puts "sample error"
|
||||
:throw, :sample ->
|
||||
IO.puts "sample thrown"
|
||||
end
|
||||
|
||||
Although the second form should be avoided in favor of raise/rescue
|
||||
control mechanisms.
|
||||
catch values also support `:error`, as in Erlang, although it is
|
||||
commonly avoided in favor of raise/rescue control mechanisms.
|
||||
|
||||
## Else clauses
|
||||
|
||||
@@ -1428,7 +1569,7 @@ defmodule Kernel.SpecialForms do
|
||||
:exit_b
|
||||
end
|
||||
|
||||
This means the VM nolonger needs to keep the stacktrace once inside
|
||||
This means the VM no longer needs to keep the stacktrace once inside
|
||||
an else clause and so tail recursion is possible when using a `try`
|
||||
with a tail call as the final call inside an else clause. The same
|
||||
is true for rescue and catch clauses.
|
||||
@@ -1437,7 +1578,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Since an expression inside `try` may not have been evaluated
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
be accessed externaly. For instance:
|
||||
be accessed externally. For instance:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
@@ -1475,7 +1616,7 @@ defmodule Kernel.SpecialForms do
|
||||
## Examples
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
@@ -1487,7 +1628,7 @@ defmodule Kernel.SpecialForms do
|
||||
received after the specified period of time:
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
@@ -1499,7 +1640,7 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timout value given to after
|
||||
There are two special cases for the timeout value given to `after`
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
|
||||
+206
-176
@@ -69,7 +69,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| { TList }
|
||||
| {TList}
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
@@ -119,7 +119,7 @@ defmodule Kernel.Typespec do
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{ atom, atom, arity }`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
@@ -151,7 +151,7 @@ defmodule Kernel.Typespec do
|
||||
Types can be parametrised by defining variables as parameters, these variables
|
||||
can then be used to define the type.
|
||||
|
||||
@type dict(key, value) :: [{ key, value }]
|
||||
@type dict(key, value) :: [{key, value}]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
@@ -269,12 +269,12 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
{ kind, export } =
|
||||
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> { :type, true }
|
||||
:typep -> { :type, false }
|
||||
:opaque -> { :opaque, true }
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
@@ -283,7 +283,7 @@ defmodule Kernel.Typespec do
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{ name, arity }])
|
||||
Module.compile_typespec(module, :export_type, [{name, arity}])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
@@ -296,7 +296,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
|
||||
else
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
@@ -310,14 +310,14 @@ defmodule Kernel.Typespec do
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, { tuple, definition }
|
||||
Module.compile_typespec module, :spec, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, { tuple, definition }
|
||||
Module.compile_typespec module, :callback, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -326,7 +326,7 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
|
||||
Enum.any?(Module.get_attribute(module, :type), finder) or
|
||||
Enum.any?(Module.get_attribute(module, :opaque), finder)
|
||||
end
|
||||
@@ -336,7 +336,7 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@@ -345,69 +345,69 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@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
|
||||
meta = [line: line]
|
||||
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
|
||||
body = {name, meta, Enum.map(args, &typespec_to_ast/1)}
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|> Enum.map(&{&1, {:var, meta, nil}})
|
||||
|
||||
result = if vars == [] do
|
||||
typespec_to_ast(result)
|
||||
spec = {:::, meta, [body, typespec_to_ast(result)]}
|
||||
|
||||
if vars == [] do
|
||||
spec
|
||||
else
|
||||
{ :when, meta, [typespec_to_ast(result), vars] }
|
||||
{:when, meta, [spec, vars]}
|
||||
end
|
||||
|
||||
{ :::, meta, [body, result] }
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
{ :::, [line: line], [{ name, [line: line], [] }, quote(do: term)] }
|
||||
def spec_to_ast(name, {:type, line, :fun, []}) do
|
||||
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
def spec_to_ast(name, {:type, line, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, args}, result]}, constraints]}) do
|
||||
guards =
|
||||
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
|
||||
{ var, typespec_to_ast(type) }
|
||||
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, var}, type]]} <- constraints do
|
||||
{var, typespec_to_ast(type)}
|
||||
end
|
||||
|
||||
meta = [line: line]
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{&1, {:var, meta, nil}})
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
|
||||
{ :::, meta, [
|
||||
{ name, [line: line], args },
|
||||
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
|
||||
] }
|
||||
{:when, meta, [
|
||||
{:::, meta, [{name, [line: line], args}, typespec_to_ast(result)]},
|
||||
guards ++ vars
|
||||
]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
|
||||
fields = lc field inlist fields, do: typespec_to_ast(field)
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
type = { :{}, [], [record|fields] }
|
||||
def type_to_ast({{:record, record}, fields, args}) when is_atom(record) do
|
||||
fields = for field <- fields, do: typespec_to_ast(field)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
type = {:{}, [], [record|fields]}
|
||||
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
|
||||
end
|
||||
|
||||
def type_to_ast({ name, type, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
def type_to_ast({name, type, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@@ -423,8 +423,8 @@ defmodule Kernel.Typespec do
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
|
||||
{:ok, abstract_code} ->
|
||||
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
@@ -443,15 +443,15 @@ defmodule Kernel.Typespec do
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
def beam_types(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
{ name, length(args) } in exported_types -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
kind == :opaque -> {:opaque, type}
|
||||
{name, length(args)} in exported_types -> {:type, type}
|
||||
true -> {:typep, type}
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
@@ -489,8 +489,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp from_abstract_code(module, kind) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
{:ok, abstract_code} ->
|
||||
for {:attribute, _, abs_kind, value} <- abstract_code, kind == abs_kind, do: value
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
@@ -498,8 +498,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
{:ok, {_, [{:abstract_code, {_raw_abstract_v1, abstract_code}}]}} ->
|
||||
{:ok, abstract_code}
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
@@ -507,7 +507,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{ ^module, beam, _filename } -> beam
|
||||
{^module, beam, _filename} -> beam
|
||||
:error -> module
|
||||
end
|
||||
end
|
||||
@@ -519,19 +519,19 @@ defmodule Kernel.Typespec do
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
|
||||
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
else
|
||||
lc(arg inlist args, do: variable(arg))
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = lc { :var, _, var } inlist args, do: var
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
type = { name, spec, vars }
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
@@ -542,9 +542,17 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
|
||||
|
||||
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
end
|
||||
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
guard = Macro.to_string(guard)
|
||||
@@ -554,70 +562,57 @@ defmodule Kernel.Typespec do
|
||||
vars = Keyword.keys(guard)
|
||||
constraints = guard_to_constraints(guard, vars, meta, caller)
|
||||
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
|
||||
spec = {:type, line(meta), :fun, fn_args(meta, args, return, vars, caller)}
|
||||
if constraints != [] do
|
||||
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
|
||||
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
|
||||
end
|
||||
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
code = {{name, Kernel.length(args)}, spec}
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
Enum.reduce(guard, [], fn
|
||||
{ _name, { :var, _, context } }, acc when is_atom(context) ->
|
||||
{_name, {:var, _, context}}, acc when is_atom(context) ->
|
||||
acc
|
||||
{ name, type }, acc ->
|
||||
constraint = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = { :type, line, :constraint, constraint }
|
||||
{name, type}, acc ->
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = {:type, line, :constraint, constraint}
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
|
||||
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
|
||||
defp collect_vars({:ann_type, _line, args}) when is_list(args) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
|
||||
defp collect_vars({:type, _line, _kind, args}) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({ :remote_type, _line, args }) when is_list(args) do
|
||||
defp collect_vars({:remote_type, _line, args}) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({ :typed_record_field, _line, type }) do
|
||||
defp collect_vars({:typed_record_field, _line, type}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({ :paren_type, _line, [type] }) do
|
||||
defp collect_vars({:paren_type, _line, [type]}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({ :var, _line, var }) do
|
||||
defp collect_vars({:var, _line, var}) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
@@ -625,28 +620,43 @@ defmodule Kernel.Typespec do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
{ :tuple, [line: line], [] }
|
||||
defp typespec_to_ast({:type, line, :tuple, :any}) do
|
||||
{:tuple, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :{}, [line: line], args }
|
||||
defp typespec_to_ast({:type, line, :tuple, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{:{}, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [{ :type, _, :union, unions } = arg] }) do
|
||||
defp typespec_to_ast({:type, _line, :list, [{:type, _, :union, unions} = arg]}) do
|
||||
case unpack_typespec_kw(unions, []) do
|
||||
{ :ok, ast } -> ast
|
||||
{: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)
|
||||
defp typespec_to_ast({:type, _line, :list, args}) do
|
||||
for arg <- 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)
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
end
|
||||
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
map = {:%{}, [line: line], fields}
|
||||
|
||||
if struct do
|
||||
{:%, [line: line], [struct, map]}
|
||||
else
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :binary, [arg1, arg2]}) do
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
arg2 == 0 ->
|
||||
quote line: line, do: <<_ :: unquote(arg1)>>
|
||||
@@ -657,75 +667,79 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
|
||||
defp typespec_to_ast({:type, line, :union, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> {:|, [line: line], [arg, expr]} end
|
||||
end
|
||||
|
||||
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)] }]
|
||||
defp typespec_to_ast({:type, line, :fun, [{:type, _, :product, args}, result]}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
[{:->, [line: line], [args, 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)] }]
|
||||
defp typespec_to_ast({:type, line, :fun, [args, result]}) do
|
||||
[{:->, [line: line], [[typespec_to_ast(args)], 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, []} ] })
|
||||
defp typespec_to_ast({:type, line, :fun, []}) do
|
||||
typespec_to_ast({:type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
|
||||
{ :"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)] }
|
||||
defp typespec_to_ast({:type, line, :range, [left, right]}) do
|
||||
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ name, [line: line], args }
|
||||
defp typespec_to_ast({:type, line, name, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{name, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
{ erl_to_ex_var(var), line, nil }
|
||||
defp typespec_to_ast({:var, line, var}) do
|
||||
{erl_to_ex_var(var), line, nil}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :op, line, op, arg }) do
|
||||
{ op, [line: line], [typespec_to_ast(arg)] }
|
||||
defp typespec_to_ast({:op, line, op, arg}) do
|
||||
{op, [line: line], [typespec_to_ast(arg)]}
|
||||
end
|
||||
|
||||
# Special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_data}, []]}) do
|
||||
typespec_to_ast({:type, line, :char_data, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
{ dot, [line: line], args }
|
||||
defp typespec_to_ast({:remote_type, line, [mod, name, args]}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
dot = {:., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)]}
|
||||
{dot, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
|
||||
{ :::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
defp typespec_to_ast({:ann_type, line, [var, type]}) do
|
||||
{:::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :typed_record_field,
|
||||
{ :record_field, line, { :atom, line1, name } },
|
||||
type }) do
|
||||
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
|
||||
defp typespec_to_ast({:typed_record_field,
|
||||
{:record_field, line, {:atom, line1, name}},
|
||||
type}) do
|
||||
typespec_to_ast({:ann_type, line, [{:var, line1, name}, type]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _, :any }) do
|
||||
defp typespec_to_ast({:type, _, :any}) do
|
||||
quote do: ...
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :paren_type, _, [type] }) do
|
||||
defp typespec_to_ast({:paren_type, _, [type]}) do
|
||||
typespec_to_ast(type)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
|
||||
defp typespec_to_ast({t, _line, atom}) when is_atom(t) do
|
||||
atom
|
||||
end
|
||||
|
||||
@@ -744,16 +758,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp line(meta) do
|
||||
case :lists.keyfind(:line, 1, meta) do
|
||||
{ :line, line } -> line
|
||||
{:line, line} -> line
|
||||
false -> 0
|
||||
end
|
||||
end
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
defp typespec({:|, meta, [_, _]} = exprs, vars, caller) do
|
||||
exprs = collect_union(exprs)
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
union = for e <- exprs, do: typespec(e, vars, caller)
|
||||
{:type, line(meta), :union, union}
|
||||
end
|
||||
|
||||
# Handle binaries
|
||||
@@ -769,6 +783,18 @@ defmodule Kernel.Typespec do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
|
||||
end
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
@@ -787,25 +813,25 @@ defmodule Kernel.Typespec do
|
||||
# Handle funs
|
||||
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 }
|
||||
{:type, line(meta), :fun, args}
|
||||
end
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:::, meta, [var, expr] }, vars, caller) do
|
||||
defp typespec({:::, meta, [var, expr]}, vars, caller) do
|
||||
left = typespec(var, [elem(var, 0)|vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{ :ann_type, line(meta), [left, right] }
|
||||
{:ann_type, line(meta), [left, right]}
|
||||
end
|
||||
|
||||
# Handle unary ops
|
||||
defp typespec({op, meta, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
|
||||
{ :op, line(meta), op, {:integer, line(meta), integer} }
|
||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||
end
|
||||
|
||||
# Handle access macro
|
||||
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
|
||||
access = {{:., meta, [Kernel, :access]}, meta1,
|
||||
[target, args ++ [_: { :any, [], [] }]]}
|
||||
[target, args ++ [_: {:any, [], []}]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
@@ -820,16 +846,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle tuples
|
||||
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
|
||||
{ :type, line(meta), :tuple, :any }
|
||||
{:type, line(meta), :tuple, :any}
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
|
||||
args = lc e inlist t, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :tuple, args }
|
||||
args = for e <- t, do: typespec(e, vars, caller)
|
||||
{:type, line(meta), :tuple, args}
|
||||
end
|
||||
|
||||
defp typespec({ left, right }, vars, caller) do
|
||||
typespec({ :{}, [], [left, right] }, vars, caller)
|
||||
defp typespec({left, right}, vars, caller) do
|
||||
typespec({:{}, [], [left, right]}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
@@ -840,7 +866,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if name in vars do
|
||||
{ :var, line(meta), name }
|
||||
{:var, line(meta), name}
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
end
|
||||
@@ -849,51 +875,55 @@ defmodule Kernel.Typespec do
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :string, arguments}
|
||||
end
|
||||
|
||||
defp typespec({:char_list, _meta, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
|
||||
defp typespec({:char_list, _meta, []}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_list()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
|
||||
defp typespec({:char_data, _meta, []}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_data()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), name, arguments }
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), name, arguments}
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
defp typespec(atom, _, _) when is_atom(atom) do
|
||||
{ :atom, 0, atom }
|
||||
{:atom, 0, atom}
|
||||
end
|
||||
|
||||
defp typespec(integer, _, _) when is_integer(integer) do
|
||||
{ :integer, 0, integer }
|
||||
{:integer, 0, integer}
|
||||
end
|
||||
|
||||
defp typespec([], vars, caller) do
|
||||
typespec({ nil, [], [] }, vars, caller)
|
||||
typespec({nil, [], []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec], vars, caller) do
|
||||
typespec({ :list, [], [spec] }, vars, caller)
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
{ :|, [], [validate_kw(x, list, caller), acc] }
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -903,14 +933,14 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{:remote_type, line(meta), [ remote, name, arguments ]}
|
||||
end
|
||||
|
||||
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
|
||||
defp collect_union({:|, _, [a, b]}), do: [a|collect_union(b)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw({key, _} = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
|
||||
end
|
||||
@@ -923,24 +953,24 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
|
||||
{ :type, line(meta), :any }
|
||||
{:type, line(meta), :any}
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, vars, caller) do
|
||||
args = lc arg inlist args, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :product, args }
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{:type, line(meta), :product, args}
|
||||
end
|
||||
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw([{ :type, _, :tuple, [{ :atom, _, atom }, type] }|t], acc) do
|
||||
defp unpack_typespec_kw([{:type, _, :tuple, [{:atom, _, atom}, type]}|t], acc) do
|
||||
unpack_typespec_kw(t, [{atom, typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw([], acc) do
|
||||
{ :ok, :lists.reverse(acc) }
|
||||
{:ok, :lists.reverse(acc)}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
|
||||
+83
-69
@@ -23,27 +23,19 @@ defmodule Keyword do
|
||||
in `Enum` and `List` can also be applied.
|
||||
"""
|
||||
|
||||
@behaviour Dict
|
||||
|
||||
@type key :: atom
|
||||
@type value :: any
|
||||
|
||||
@type t :: [{key, value}]
|
||||
@type t(value) :: [{ key, value }]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enum. Unlike `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` does not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) do
|
||||
Enum.to_list(enum)
|
||||
end
|
||||
@type t(value) :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
keyword?(rest)
|
||||
end
|
||||
|
||||
@@ -59,8 +51,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable. Similar to dicts,
|
||||
duplicated entries are removed, the latest one prevails.
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -70,15 +65,17 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec new(Enum.t) :: t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, [], fn { k, v }, keywords ->
|
||||
Enum.reduce pairs, [], fn {k, v}, keywords ->
|
||||
put(keywords, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable with the
|
||||
help of the transformation function. Duplicated
|
||||
entries are removed, the latest one prevails.
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [], fun)`,
|
||||
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -89,7 +86,7 @@ defmodule Keyword do
|
||||
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
|
||||
def new(pairs, transform) do
|
||||
Enum.reduce pairs, [], fn i, keywords ->
|
||||
{ k, v } = transform.(i)
|
||||
{k, v} = transform.(i)
|
||||
put(keywords, k, v)
|
||||
end
|
||||
end
|
||||
@@ -116,9 +113,9 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@spec get(t, key, value) :: value
|
||||
def get(keywords, key, default \\ nil) when is_atom(key) do
|
||||
def get(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
{^key, value} -> value
|
||||
false -> default
|
||||
end
|
||||
end
|
||||
@@ -130,16 +127,16 @@ defmodule Keyword do
|
||||
## Examples
|
||||
|
||||
iex> Keyword.fetch([a: 1], :a)
|
||||
{ :ok, 1 }
|
||||
{:ok, 1}
|
||||
|
||||
iex> Keyword.fetch([a: 1], :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(keywords, key) when is_atom(key) do
|
||||
@spec fetch(t, key) :: {:ok, value}
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
{^key, value} -> {:ok, value}
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
@@ -154,14 +151,14 @@ defmodule Keyword do
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(keywords, key) when is_atom(key) do
|
||||
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
{^key, value} -> value
|
||||
false -> raise(KeyError, key: key, term: keywords)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -175,8 +172,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_atom(key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} <- keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -193,8 +190,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) do
|
||||
lc { key, _ } inlist keywords, do: key
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for {key, _} <- keywords, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -207,8 +204,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for {_, value} <- keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -228,8 +225,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_atom(key) do
|
||||
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -251,8 +248,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_atom(key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, _} = tuple <- keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -269,7 +266,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_atom(key) do
|
||||
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
:lists.keydelete(key, 1, keywords)
|
||||
end
|
||||
|
||||
@@ -289,7 +286,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
def put(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
[{key, value}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
@@ -298,6 +295,7 @@ defmodule Keyword do
|
||||
already exists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put_new([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
|
||||
@@ -306,9 +304,9 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
def put_new(keywords, key, value) when is_atom(key) do
|
||||
def put_new(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
{^key, _} -> keywords
|
||||
false -> [{key, value}|keywords]
|
||||
end
|
||||
end
|
||||
@@ -340,7 +338,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) when is_list(d1) and is_list(d2) do
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -351,7 +349,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
iex> end)
|
||||
...> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -360,7 +358,7 @@ defmodule Keyword do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
defp do_merge([{ k, v2 }|t], acc, fun) do
|
||||
defp do_merge([{k, v2}|t], acc, fun) do
|
||||
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
|
||||
end
|
||||
|
||||
@@ -381,7 +379,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(keywords, key) when is_atom(key) do
|
||||
def has_key?(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@@ -395,20 +393,24 @@ defmodule Keyword do
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
|
||||
** (KeyError) key not found: :b
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec update!(t, key, (value -> value)) :: t | no_return
|
||||
def update!([{key, value}|keywords], key, fun) do
|
||||
def update!(keywords, key, fun) do
|
||||
update!(keywords, key, fun, keywords)
|
||||
end
|
||||
|
||||
defp update!([{key, value}|keywords], key, fun, _dict) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update!([{_, _} = e|keywords], key, fun) do
|
||||
[e|update!(keywords, key, fun)]
|
||||
defp update!([{_, _} = e|keywords], key, fun, dict) do
|
||||
[e|update!(keywords, key, fun, dict)]
|
||||
end
|
||||
|
||||
def update!([], key, _fun) when is_atom(key) do
|
||||
raise(KeyError, key: key)
|
||||
defp update!([], key, _fun, dict) when is_atom(key) do
|
||||
raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -445,24 +447,24 @@ defmodule Keyword do
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
|
||||
{[a: 1, c: 3, a: 5], [b: 2, d: 4]}
|
||||
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { [], [] }
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = {[], []}
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
|
||||
case k in keys do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
true -> {[{k, v}|take], drop}
|
||||
false -> {take, [{k, v}|drop]}
|
||||
end
|
||||
end
|
||||
|
||||
{ Enum.reverse(take), Enum.reverse(drop) }
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -480,8 +482,8 @@ defmodule Keyword do
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -499,8 +501,8 @@ defmodule Keyword do
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -528,8 +530,8 @@ defmodule Keyword do
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
def pop(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{get(keywords, key, default), delete(keywords, key)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -557,7 +559,19 @@ defmodule Keyword do
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{get(keywords, key, default), delete_first(keywords, key)}
|
||||
end
|
||||
|
||||
# Dict callbacks
|
||||
|
||||
@doc false
|
||||
def size(keyword) do
|
||||
length(keyword)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(keyword) do
|
||||
keyword
|
||||
end
|
||||
end
|
||||
|
||||
+74
-8
@@ -47,6 +47,7 @@ defmodule List do
|
||||
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
|
||||
"""
|
||||
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
|
||||
def duplicate(elem, n) do
|
||||
@@ -123,8 +124,10 @@ defmodule List do
|
||||
|
||||
iex> List.first([])
|
||||
nil
|
||||
|
||||
iex> List.first([1])
|
||||
1
|
||||
|
||||
iex> List.first([1, 2, 3])
|
||||
1
|
||||
|
||||
@@ -140,8 +143,10 @@ defmodule List do
|
||||
|
||||
iex> List.last([])
|
||||
nil
|
||||
|
||||
iex> List.last([1])
|
||||
1
|
||||
|
||||
iex> List.last([1, 2, 3])
|
||||
3
|
||||
|
||||
@@ -159,10 +164,10 @@ defmodule List do
|
||||
## Examples
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], :a, 0)
|
||||
{ :a, 1 }
|
||||
{:a, 1}
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], 2, 1)
|
||||
{ :b, 2 }
|
||||
{:b, 2}
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], :c, 0)
|
||||
nil
|
||||
@@ -201,7 +206,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
iex> List.keyreplace([a: 1, b: 2], :a, 0, {:a, 3})
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@@ -235,10 +240,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, {:a, 3})
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
|
||||
iex> List.keystore([a: 1, b: 2], :c, 0, {:c, 3})
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@@ -448,6 +453,67 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data("æß")
|
||||
{:ok, 'æß'}
|
||||
iex> List.from_char_data([?a, "bc"])
|
||||
{:ok, 'abc'}
|
||||
|
||||
"""
|
||||
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
|
||||
def from_char_data(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
|
||||
In case the conversion fails or is incomplete, it raises a
|
||||
`UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data!("æß")
|
||||
'æß'
|
||||
iex> List.from_char_data!("abc")
|
||||
'abc'
|
||||
|
||||
"""
|
||||
@spec from_char_data!(char_data) :: char_list | no_return
|
||||
def from_char_data!(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# replace_at
|
||||
@@ -531,15 +597,15 @@ defmodule List do
|
||||
end
|
||||
|
||||
defp do_zip_each(_, nil) do
|
||||
{ nil, nil }
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
defp do_zip_each([h|t], acc) do
|
||||
{ t, [h|acc] }
|
||||
{t, [h|acc]}
|
||||
end
|
||||
|
||||
defp do_zip_each([], _) do
|
||||
{ nil, nil }
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defprotocol List.Chars do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
The List.Chars protocol is responsible for
|
||||
converting a structure to a list (only if applicable).
|
||||
The only function required to be implemented is
|
||||
@@ -21,7 +21,7 @@ defimpl List.Chars, for: BitString do
|
||||
Returns the given binary converted to a char list.
|
||||
"""
|
||||
def to_char_list(thing) when is_binary(thing) do
|
||||
String.to_char_list!(thing)
|
||||
List.from_char_data!(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
defmodule ListDict do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on lists of two-items tuples.
|
||||
|
||||
This dictionary is only recommended for keeping a small amount
|
||||
of values. Other dict alternatives are more viable for keeping
|
||||
any other amount than a handful.
|
||||
|
||||
For more information about the functions and their APIs, please
|
||||
consult the `Dict` module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns a new `ListDict`, i.e. an empty list.
|
||||
"""
|
||||
def new, do: []
|
||||
|
||||
@doc """
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.to_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new `ListDict` from the given pairs
|
||||
via the given transformation function.
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.map list, transform
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
lc { key, _ } inlist dict, do: key
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
lc { _, value } inlist dict, do: value
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
length(dict)
|
||||
end
|
||||
|
||||
def has_key?(dict, key)
|
||||
def has_key?([{ key, _ }|_], key), do: true
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default \\ nil)
|
||||
def get([{ key, value }|_], key, _default), do: value
|
||||
def get([{ _, _ }|t], key, default), do: get(t, key, default)
|
||||
def get([], _key, default), do: default
|
||||
|
||||
def fetch(dict, key)
|
||||
def fetch([{ key, value }|_], key), do: { :ok, value }
|
||||
def fetch([{ _, _ }|t], key), do: fetch(t, key)
|
||||
def fetch([], _key), do: :error
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
def put(dict, key, val) do
|
||||
[{key, val}|delete(dict, key)]
|
||||
end
|
||||
|
||||
def put_new(dict, key, val) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
def delete(dict, key)
|
||||
def delete([{ key, _ }|t], key), do: t
|
||||
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
|
||||
def delete([], _key), do: []
|
||||
|
||||
def merge(dict, enum, callback \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce enum, dict, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if k in keys do
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
|
||||
def update!([{_, _} = e|dict], key, fun) do
|
||||
[e|update!(dict, key, fun)]
|
||||
end
|
||||
|
||||
def update!([], key, _fun) do
|
||||
raise(KeyError, key: key)
|
||||
end
|
||||
|
||||
def update([{key, value}|dict], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|dict], key, initial, fun) do
|
||||
[e|update(dict, key, initial, fun)]
|
||||
end
|
||||
|
||||
def update([], key, initial, _fun) do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
def empty(_dict), do: []
|
||||
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
def reduce([{_,_}=h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def to_list(dict), do: dict
|
||||
end
|
||||
+164
-130
@@ -6,14 +6,14 @@ defmodule Macro do
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST)"
|
||||
@type t :: expr | { t, t } | atom | number | binary | pid | fun | [t]
|
||||
@type t :: expr | {t, t} | atom | number | binary | pid | fun | [t]
|
||||
|
||||
@typedoc "Expr node (remaining ones are literals)"
|
||||
@type expr :: { expr | atom, Keyword.t, atom | [t] }
|
||||
@type expr :: {expr | atom, Keyword.t, atom | [t]}
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
@@ -27,10 +27,10 @@ defmodule Macro do
|
||||
@doc false
|
||||
defmacro unary_ops, do: @unary_ops
|
||||
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
o when o in [:<-, :inlist, :inbits, ://, :\\, :::] -> {:left, 40}
|
||||
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
@@ -53,31 +53,30 @@ defmodule Macro do
|
||||
Raises if the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({ :|> , _, [left, right] }) do
|
||||
[left|unpipe(right)]
|
||||
def unpipe({:|> , _, [left, right]}) do
|
||||
[{left, 0}|unpipe(right)]
|
||||
end
|
||||
|
||||
def unpipe(other) do
|
||||
[other]
|
||||
[{other, 0}]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pipes `expr` into the `call_expr` as the
|
||||
argument in the given `position`.
|
||||
Pipes `expr` into the `call_args` at the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer \\ 0)
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, { :&, _, _ } = call_args, _integer) do
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
|
||||
{ call, line, List.insert_at([], integer, expr) }
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
{call, line, List.insert_at([], integer, expr)}
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, args }, integer) when is_list(args) do
|
||||
{ call, line, List.insert_at(args, integer, expr) }
|
||||
def pipe(expr, {call, line, args}, integer) when is_list(args) do
|
||||
{call, line, List.insert_at(args, integer, expr)}
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
@@ -104,16 +103,16 @@ defmodule Macro do
|
||||
@spec update_meta(t, (Keyword.t -> Keyword.t)) :: t
|
||||
def update_meta(quoted, fun)
|
||||
|
||||
def update_meta({ left, meta, right }, fun) when is_list(meta) do
|
||||
{ update_meta(left, fun), fun.(meta), update_meta(right, fun) }
|
||||
def update_meta({left, meta, right}, fun) when is_list(meta) do
|
||||
{update_meta(left, fun), fun.(meta), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta({ left, right }, fun) do
|
||||
{ update_meta(left, fun), update_meta(right, fun) }
|
||||
def update_meta({left, right}, fun) do
|
||||
{update_meta(left, fun), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
lc x inlist list, do: update_meta(x, fun)
|
||||
for x <- list, do: update_meta(x, fun)
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
@@ -129,30 +128,30 @@ defmodule Macro do
|
||||
## Examples
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo)
|
||||
{ :foo, [] }
|
||||
{:foo, []}
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo())
|
||||
{ :foo, [] }
|
||||
{:foo, []}
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo(1, 2, 3))
|
||||
{ :foo, [1, 2, 3] }
|
||||
{:foo, [1, 2, 3]}
|
||||
|
||||
iex> Macro.decompose_call(quote do: Elixir.M.foo(1, 2, 3))
|
||||
{ { :__aliases__, [], [:Elixir, :M] }, :foo, [1, 2, 3] }
|
||||
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}
|
||||
|
||||
iex> Macro.decompose_call(quote do: 42)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec decompose_call(Macro.t) :: { atom, [Macro.t] } | { Macro.t, atom, [Macro.t] } | :error
|
||||
def decompose_call({ { :., _, [remote, function] }, _, args }) when is_tuple(remote) or is_atom(remote),
|
||||
do: { remote, function, args }
|
||||
@spec decompose_call(Macro.t) :: {atom, [Macro.t]} | {Macro.t, atom, [Macro.t]} | :error
|
||||
def decompose_call({{:., _, [remote, function]}, _, args}) when is_tuple(remote) or is_atom(remote),
|
||||
do: {remote, function, args}
|
||||
|
||||
def decompose_call({ name, _, args }) when is_atom(name) and is_atom(args),
|
||||
do: { name, [] }
|
||||
def decompose_call({name, _, args}) when is_atom(name) and is_atom(args),
|
||||
do: {name, []}
|
||||
|
||||
def decompose_call({ name, _, args }) when is_atom(name) and is_list(args),
|
||||
do: { name, args }
|
||||
def decompose_call({name, _, args}) when is_atom(name) and is_list(args),
|
||||
do: {name, args}
|
||||
|
||||
def decompose_call(_),
|
||||
do: :error
|
||||
@@ -170,10 +169,10 @@ defmodule Macro do
|
||||
iex> Macro.escape(:foo)
|
||||
:foo
|
||||
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
iex> Macro.escape({:a, :b, :c})
|
||||
{:{}, [], [:a, :b, :c]}
|
||||
|
||||
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
|
||||
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -183,7 +182,7 @@ defmodule Macro do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
This is the unescaping behaviour used by default in Elixir
|
||||
@@ -195,7 +194,7 @@ defmodule Macro do
|
||||
also escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%s` and others) which receive a raw, unescaped
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
string.
|
||||
|
||||
## Examples
|
||||
@@ -211,7 +210,7 @@ defmodule Macro do
|
||||
:elixir_interpolation.unescape_chars(chars)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Unescape the given chars according to the map given.
|
||||
|
||||
Check `unescape_string/1` if you want to use the same map
|
||||
@@ -300,27 +299,27 @@ defmodule Macro do
|
||||
def to_string(tree, fun \\ fn(_ast, string) -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
|
||||
fun.(ast, atom_to_binary(var))
|
||||
end
|
||||
|
||||
# Aliases
|
||||
def to_string({ :__aliases__, _, refs } = ast, fun) do
|
||||
def to_string({:__aliases__, _, refs} = ast, fun) do
|
||||
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_string({ :__block__, _, [expr] } = ast, fun) do
|
||||
def to_string({:__block__, _, [expr]} = ast, fun) do
|
||||
fun.(ast, to_string(expr, fun))
|
||||
end
|
||||
|
||||
def to_string({ :__block__, _, _ } = ast, fun) do
|
||||
def to_string({:__block__, _, _} = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(ast, fun), "\n "
|
||||
fun.(ast, "(\n " <> block <> "\n)")
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_string({ :<<>>, _, args } = ast, fun) do
|
||||
def to_string({:<<>>, _, args} = ast, fun) do
|
||||
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
|
||||
"<" <> rest -> "<< <" <> rest <> " >>"
|
||||
rest -> "<<" <> rest <> ">>"
|
||||
@@ -328,33 +327,45 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
def to_string({:{}, _, args} = ast, fun) do
|
||||
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
|
||||
fun.(ast, tuple)
|
||||
end
|
||||
|
||||
# Map containers
|
||||
def to_string({:%{}, _, args} = ast, fun) do
|
||||
map = "%{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, map)
|
||||
end
|
||||
|
||||
def to_string({:%, _, [structname, map]} = ast, fun) do
|
||||
{:%{}, _, args} = map
|
||||
struct = "%" <> to_string(structname, fun) <> "{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, struct)
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
|
||||
def to_string({:fn, _, [{:->, _, [_, tuple]}] = arrow} = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
|
||||
def to_string({:fn, _, [{:->, _, _}] = block} = ast, fun) do
|
||||
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, block } = ast, fun) do
|
||||
def to_string({:fn, _, block} = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(block, fun), "\n "
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_string([{ :->, _, _ }|_] = ast, fun) do
|
||||
def to_string([{:->, _, _}|_] = ast, fun) do
|
||||
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
|
||||
end
|
||||
|
||||
# left when right
|
||||
def to_string({ :when, _, [left, right] } = ast, fun) do
|
||||
def to_string({:when, _, [left, right]} = ast, fun) do
|
||||
if right != [] and Keyword.keyword?(right) do
|
||||
right = kw_list_to_string(right, fun)
|
||||
else
|
||||
@@ -365,33 +376,38 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in unquote(@binary_ops) do
|
||||
def to_string({op, _, [left, right]} = ast, fun) when op in unquote(@binary_ops) do
|
||||
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
|
||||
end
|
||||
|
||||
# Splat when
|
||||
def to_string({ :when, _, args } = ast, fun) do
|
||||
{ left, right } = :elixir_utils.split_last(args)
|
||||
def to_string({:when, _, args} = ast, fun) do
|
||||
{left, right} = :elixir_utils.split_last(args)
|
||||
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_string({ :not, _, [arg] } = ast, fun) do
|
||||
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
def to_string({:not, _, [arg]} = ast, fun) do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unquote(@unary_ops) do
|
||||
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
|
||||
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_string({ target, _, args } = ast, fun) when is_list(args) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
def to_string({target, _, args} = ast, fun) when is_list(args) do
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
@@ -399,8 +415,8 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
def to_string({ left, right }, fun) do
|
||||
to_string({ :{}, [], [left, right] }, fun)
|
||||
def to_string({left, right}, fun) do
|
||||
to_string({:{}, [], [left, right]}, fun)
|
||||
end
|
||||
|
||||
# Lists
|
||||
@@ -409,7 +425,7 @@ defmodule Macro do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
|
||||
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
true ->
|
||||
@@ -432,8 +448,8 @@ defmodule Macro do
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
@@ -443,7 +459,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp args_to_string(args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
|
||||
if last != [] and Keyword.keyword?(last) do
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
@@ -468,32 +484,49 @@ defmodule Macro do
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
|
||||
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
|
||||
defp block_to_string([{:->, _, _}|_] = block, fun) do
|
||||
Enum.map_join(block, "\n", fn({:->, _, [left, right]}) ->
|
||||
left = comma_join_or_empty_paren(left, fun, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_string({ :__block__, _, exprs }, fun) do
|
||||
defp block_to_string({:__block__, _, exprs}, fun) do
|
||||
Enum.map_join(exprs, "\n", &to_string(&1, fun))
|
||||
end
|
||||
|
||||
defp block_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp map_to_string([{:|, _, [update_map, update_args]}], fun) do
|
||||
to_string(update_map, fun) <> " | " <> map_to_string(update_args, fun)
|
||||
end
|
||||
|
||||
defp map_to_string(list, fun) do
|
||||
cond do
|
||||
Keyword.keyword?(list) -> kw_list_to_string(list, fun)
|
||||
true -> map_list_to_string(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp map_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp parenthise(expr, fun) do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
|
||||
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
|
||||
{ _, prec } = binary_op_props(op)
|
||||
defp op_to_string({op, _, [_, _]} = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
|
||||
{parent_assoc, parent_prec} = binary_op_props(parent_op)
|
||||
{_, prec} = binary_op_props(op)
|
||||
cond do
|
||||
parent_prec < prec -> to_string(expr, fun)
|
||||
parent_prec > prec -> parenthise(expr, fun)
|
||||
@@ -510,7 +543,7 @@ defmodule Macro do
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string(pairs, fun, paren \\ false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
Enum.map_join(pairs, "; ", fn({:->, _, [left, right]}) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
end)
|
||||
@@ -524,11 +557,11 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
bc <<x>> inbits block do
|
||||
<< case x == ?\n do
|
||||
for <<x <- block>>, into: "" do
|
||||
case x == ?\n do
|
||||
true -> replacement
|
||||
false -> <<x>>
|
||||
end :: binary >>
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -576,7 +609,7 @@ defmodule Macro do
|
||||
The compilation will fail because `My.Module` when quoted
|
||||
is not an atom, but a syntax tree as follow:
|
||||
|
||||
{:__aliases__, [], [:My, :Module] }
|
||||
{:__aliases__, [], [:My, :Module]}
|
||||
|
||||
That said, we need to expand the aliases node above to an
|
||||
atom, so we can retrieve its length. Expanding the node is
|
||||
@@ -612,129 +645,129 @@ defmodule Macro do
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
|
||||
defp do_expand_once({ :__aliases__, _, _ } = original, env) do
|
||||
defp do_expand_once({:__aliases__, _, _} = original, env) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true }
|
||||
{receiver, true}
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true }
|
||||
{receiver, true}
|
||||
false ->
|
||||
{ original, false }
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp do_expand_once({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
defp do_expand_once({:@, _, [{name, _, args}]} = original, env) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> { Module.get_attribute(module, name), true }
|
||||
false -> { original, false }
|
||||
true -> {escape(Module.get_attribute(module, name)), true}
|
||||
false -> {original, false}
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp do_expand_once({ :__MODULE__, _, atom }, env) when is_atom(atom),
|
||||
do: { env.module, true }
|
||||
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true }
|
||||
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
|
||||
do: { { :{}, [], tuple_to_list(env) }, true }
|
||||
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
|
||||
defp do_expand_once({:__MODULE__, _, atom}, env) when is_atom(atom),
|
||||
do: {env.module, true}
|
||||
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
|
||||
do: {:filename.dirname(env.file), true}
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
|
||||
do: {{:%{}, [], Map.to_list(env)}, true}
|
||||
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env) when
|
||||
is_atom(atom) and is_atom(field) do
|
||||
case :erlang.function_exported(Macro.Env, field, 1) do
|
||||
true -> { apply(env, field, []), true }
|
||||
false -> { original, false }
|
||||
if Map.has_key?(env, field) do
|
||||
{Map.get(env, field), true}
|
||||
else
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp do_expand_once({ atom, meta, context } = original, env)
|
||||
defp do_expand_once({atom, meta, context} = original, env)
|
||||
when is_atom(atom) and is_list(meta) and is_atom(context) do
|
||||
if :lists.member({ atom, Keyword.get(meta, :counter, context) }, env.vars) do
|
||||
{ original, false }
|
||||
if :lists.member({atom, Keyword.get(meta, :counter, context)}, env.vars) do
|
||||
{original, false}
|
||||
else
|
||||
case do_expand_once({ atom, meta, [] }, env) do
|
||||
{ _, true } = exp -> exp
|
||||
{ _, false } -> { original, false }
|
||||
case do_expand_once({atom, meta, []}, env) do
|
||||
{_, true} = exp -> exp
|
||||
{_, false} -> {original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_expand_once({ atom, meta, args } = original, env)
|
||||
defp do_expand_once({atom, meta, args} = original, env)
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
arity = length(args)
|
||||
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
{original, false}
|
||||
else
|
||||
module = env.module
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
[{module, module.__info__(:macros)}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
|
||||
env, extra)
|
||||
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
{:ok, receiver, quoted} ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{ :ok, _receiver } ->
|
||||
{ original, false }
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
{:ok, _receiver} ->
|
||||
{original, false}
|
||||
:error ->
|
||||
{ original, false }
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp do_expand_once({ { :., _, [left, right] }, meta, args } = original, env) when is_atom(right) do
|
||||
{ receiver, _ } = do_expand_once(left, env)
|
||||
defp do_expand_once({{:., _, [left, right]}, meta, args} = original, env) when is_atom(right) do
|
||||
{receiver, _} = do_expand_once(left, env)
|
||||
|
||||
case is_atom(receiver) do
|
||||
false -> { original, false }
|
||||
false -> {original, false}
|
||||
true ->
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
|
||||
args, :elixir_env.ex_to_env(env))
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, {right, length(args)}, args, env)
|
||||
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
{:ok, receiver, quoted} ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
:error ->
|
||||
{ original, false }
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
defp do_expand_once(other, _env), do: {other, false}
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it no longer represents
|
||||
a macro.
|
||||
Receives an AST node and expands it until it can no longer
|
||||
be expanded.
|
||||
|
||||
Check `expand_once/2` for more information on how
|
||||
expansion works.
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({ tree, true }, env)
|
||||
expand_until({tree, true}, env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true }, env) do
|
||||
defp expand_until({tree, true}, env) do
|
||||
expand_until(do_expand_once(tree, env), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false }, _env) do
|
||||
defp expand_until({tree, false}, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@@ -744,22 +777,23 @@ defmodule Macro do
|
||||
|
||||
Terms are considered safe if they represent data structures and don't actually
|
||||
evaluate code. Returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{ :unsafe, term }`.
|
||||
which is returned as `{:unsafe, term}`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
IO.write :stderr, "Macro.safe_term/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
|
||||
defp do_safe_term({local, _, terms}) when local in [:{}, :%{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
defp do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
defp do_safe_term({unary, _, [term]}) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term({left, right}), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: {:unsafe, terms}
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
+56
-33
@@ -1,6 +1,6 @@
|
||||
defmodule Macro.Env do
|
||||
@moduledoc """
|
||||
A record that holds compile time environment information.
|
||||
A struct that holds compile time environment information.
|
||||
|
||||
The current environment can be accessed at any time as
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
@@ -9,7 +9,7 @@ defmodule Macro.Env do
|
||||
* `module` - the current module name.
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{ atom, integer` }, where the first
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity. Returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment. It can be nil
|
||||
@@ -21,76 +21,99 @@ defmodule Macro.Env do
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as { var, context }
|
||||
* `vars` - a list keeping all defined variables as {var, context}
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@type name_arity :: {atom, non_neg_integer}
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{ module, module }]
|
||||
@type macro_aliases :: [{ module, { integer, module } }]
|
||||
@type aliases :: [{module, module}]
|
||||
@type macro_aliases :: [{module, {integer, module}}]
|
||||
@type context :: :match | :guard | nil
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type functions :: [{module, [name_arity]}]
|
||||
@type macros :: [{module, [name_arity]}]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom | non_neg_integer }]
|
||||
@type vars :: [{atom, atom | non_neg_integer}]
|
||||
@type export_vars :: vars | nil
|
||||
@type lexical_tracker :: pid
|
||||
@type local :: module | nil
|
||||
|
||||
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
|
||||
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
|
||||
:local]
|
||||
@type t :: %{__struct__: __MODULE__,
|
||||
module: module,
|
||||
file: file,
|
||||
line: line,
|
||||
function: name_arity | nil,
|
||||
context: context,
|
||||
requires: requires,
|
||||
aliases: aliases,
|
||||
functions: functions,
|
||||
macros: macros,
|
||||
macro_aliases: aliases,
|
||||
context_modules: context_modules,
|
||||
vars: vars,
|
||||
export_vars: export_vars,
|
||||
lexical_tracker: lexical_tracker,
|
||||
local: local}
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, context: context, requires: requires, aliases: aliases,
|
||||
functions: functions, macros: macros, macro_aliases: aliases,
|
||||
context_modules: context_modules, vars: vars, export_vars: export_vars,
|
||||
lexical_tracker: lexical_tracker, local: local]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
def __struct__ do
|
||||
%{__struct__: __MODULE__,
|
||||
module: nil,
|
||||
file: "nofile",
|
||||
line: 0,
|
||||
function: nil,
|
||||
context: nil,
|
||||
requires: [],
|
||||
aliases: [],
|
||||
functions: [],
|
||||
macros: [],
|
||||
macro_aliases: [],
|
||||
context_modules: [],
|
||||
vars: [],
|
||||
export_vars: nil,
|
||||
lexical_tracker: nil,
|
||||
local: nil}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a keyword list containing the file and line
|
||||
information as keys.
|
||||
"""
|
||||
def location(record) do
|
||||
[file: file(record), line: line(record)]
|
||||
def location(%{__struct__: Macro.Env, file: file, line: line}) do
|
||||
[file: file, line: line]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: context(record) == :guard
|
||||
def in_guard?(%{__struct__: Macro.Env, context: context}), do: context == :guard
|
||||
|
||||
@doc """
|
||||
Returns whether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :match
|
||||
def in_match?(%{__struct__: Macro.Env, context: context}), do: context == :match
|
||||
|
||||
@doc """
|
||||
Returns the environment stacktrace.
|
||||
"""
|
||||
def stacktrace(record) do
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(record.module) ->
|
||||
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
|
||||
nil?(record.function) ->
|
||||
[{ module(record), :__MODULE__, 0, relative_location(record) }]
|
||||
nil?(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{ name, arity } = record.function
|
||||
[{ module(record), name, arity, relative_location(record) }]
|
||||
{name, arity} = env.function
|
||||
[{env.module, name, arity, relative_location(env)}]
|
||||
end
|
||||
end
|
||||
|
||||
defp relative_location(record) do
|
||||
[file: Path.relative_to_cwd(file(record)), line: line(record)]
|
||||
defp relative_location(env) do
|
||||
[file: Path.relative_to_cwd(env.file), line: env.line]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
defmodule Map do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on maps.
|
||||
|
||||
Maps are key-value stores where keys are compared using
|
||||
the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form defined in the `Kernel.SpecialForms`
|
||||
module.
|
||||
|
||||
For more information about the functions in this module and
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
"""
|
||||
def new, do: %{}
|
||||
|
||||
def has_key?(map, key), do: :maps.is_key(key, map)
|
||||
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
def pop(map, key, default \\ nil) do
|
||||
{get(map, key, default), delete(map, key)}
|
||||
end
|
||||
|
||||
def put(map, key, val) do
|
||||
:maps.put(key, val, map)
|
||||
end
|
||||
|
||||
def put_new(map, key, val) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> :maps.put(key, val, map)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
def merge(map1, map2, callback) do
|
||||
:maps.fold fn k, v2, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
def split(map, keys) do
|
||||
acc = {%{}, %{}}
|
||||
|
||||
:maps.fold fn k, v, {take, drop} ->
|
||||
if k in keys do
|
||||
{put(take, k, v), drop}
|
||||
else
|
||||
{take, put(drop, k, v)}
|
||||
end
|
||||
end, acc, map
|
||||
end
|
||||
|
||||
def update!(map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
raise(KeyError, key: key, term: map)
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def update(map, key, initial, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
:maps.put(key, initial, map)
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def equal?(map, map), do: true
|
||||
def equal?(_, _), do: false
|
||||
end
|
||||
+116
-93
@@ -1,17 +1,11 @@
|
||||
defmodule Module do
|
||||
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 %S'''
|
||||
@moduledoc ~S'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their purpose
|
||||
this module will raise errors, since it is out of their scope
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
|
||||
@@ -24,7 +18,7 @@ defmodule Module do
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
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`.
|
||||
@@ -43,7 +37,7 @@ defmodule Module do
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
|
||||
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.
|
||||
@@ -51,7 +45,7 @@ defmodule Module do
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
Note: differently from `@after_compile`, the callback function/macro must
|
||||
Note: unlike `@after_compile`, the callback function/macro must
|
||||
be placed in a separate module (because when the callback is invoked,
|
||||
the current module does not yet exist).
|
||||
|
||||
@@ -94,7 +88,7 @@ defmodule Module do
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
@compile {:inline, myfun: 1}
|
||||
|
||||
def myfun(arg) do
|
||||
to_string(arg)
|
||||
@@ -164,31 +158,34 @@ defmodule Module do
|
||||
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked after each function or macro in the current
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
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
|
||||
- list of expanded arguments
|
||||
- list of expanded guards
|
||||
- expanded function body
|
||||
|
||||
Note the hook receives the expanded arguments and it is invoked before
|
||||
the function is stored in the module. So `Module.defines?/2` will return
|
||||
false for the first clause of every function.
|
||||
|
||||
If the function/macro being defined has multiple clauses, the hook will
|
||||
be called for each clause.
|
||||
|
||||
Unlike other hooks, `@on_definition` will only invoke functions
|
||||
and never macros. This is because the hook is invoked inside the context
|
||||
of the function (and nested function definitions are not allowed in
|
||||
Elixir).
|
||||
|
||||
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. Finally, since
|
||||
the `on_definition` hook is executed inside the context of the defined
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
can only be a function, not a macro.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule H do
|
||||
@@ -203,7 +200,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition { H, :on_def }
|
||||
@on_definition {H, :on_def}
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_string(arg)
|
||||
@@ -290,7 +287,7 @@ defmodule Module do
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
|
||||
* `: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
|
||||
|
||||
@@ -308,15 +305,14 @@ defmodule Module do
|
||||
and its attributes and functions can be modified.
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == :ets.info(table, :name)
|
||||
:elixir_module.is_open(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
See `Code.eval_string` for more information.
|
||||
See `Code.eval_string/3` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -342,20 +338,20 @@ defmodule Module do
|
||||
"""
|
||||
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
def eval_quoted(%Macro.Env{} = env, quoted, binding, opts) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(Map.to_list(env), opts))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, env) when is_env(env) do
|
||||
eval_quoted(module, quoted, binding, env.to_keywords)
|
||||
def eval_quoted(module, quoted, binding, %Macro.Env{} = env) do
|
||||
eval_quoted(module, quoted, binding, Map.to_list(env))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
:elixir_def.reset_last(module)
|
||||
{ value, binding, _env, _scope } =
|
||||
{value, binding, _env, _scope} =
|
||||
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
|
||||
{ value, binding }
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -370,7 +366,7 @@ defmodule Module do
|
||||
def world, do: true
|
||||
end
|
||||
|
||||
Module.create(Hello, contents, __ENV__.location)
|
||||
Module.create(Hello, contents, Macro.Env.location(__ENV__))
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
@@ -389,8 +385,8 @@ defmodule Module do
|
||||
"""
|
||||
def create(module, quoted, opts \\ [])
|
||||
|
||||
def create(module, quoted, env) when is_env(env) do
|
||||
create(module, quoted, env.to_keywords)
|
||||
def create(module, quoted, %Macro.Env{} = env) do
|
||||
create(module, quoted, Map.to_list(env))
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
@@ -404,6 +400,7 @@ defmodule Module do
|
||||
|
||||
iex> Module.concat([Foo, Bar])
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
|
||||
@@ -420,6 +417,7 @@ defmodule Module do
|
||||
|
||||
iex> Module.concat(Foo, Bar)
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
|
||||
@@ -493,7 +491,7 @@ defmodule Module do
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, { :version, 0 }, [], "Manually added docs")
|
||||
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, {:version, 0}, [], "Manually added docs")
|
||||
def version, do: 1
|
||||
end
|
||||
|
||||
@@ -501,7 +499,7 @@ defmodule Module do
|
||||
def add_doc(module, line, kind, tuple, signature \\ [], doc)
|
||||
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
if doc, do: {:error, :private_doc}, else: :ok
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
@@ -509,50 +507,68 @@ defmodule Module do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end
|
||||
|
||||
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 }] ->
|
||||
[{tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
})
|
||||
})
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
# Simplify signatures to be stored in docs
|
||||
|
||||
defp simplify_signature({ :\\, defline, [left, right ] }, line, i) do
|
||||
{ :\\, defline, [simplify_signature(left, line, i), right] }
|
||||
defp simplify_signature({:\\, _, [left, right ]}, i) do
|
||||
{:\\, [], [simplify_signature(left, i), right]}
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
|
||||
case atom_to_list(var) do
|
||||
[?_|_] -> { var, line, :guess }
|
||||
_ -> { var, line, nil }
|
||||
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
|
||||
last = List.last(String.split(atom_to_binary(left), "."))
|
||||
atom = binary_to_atom(downcase(last))
|
||||
{atom, [], nil}
|
||||
end
|
||||
|
||||
defp simplify_signature({:=, _, [_, right]}, i) do
|
||||
simplify_signature(right, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
|
||||
_ -> {var, [], nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, line, i) do
|
||||
simplify_signature(right, line, i)
|
||||
defp simplify_signature(other, i) when is_integer(other), do: {:"int#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: {:"bool#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_atom(other), do: {:"atom#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_list(other), do: {:"list#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_float(other), do: {:"float#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_binary(other), do: {:"binary#{i}", [], Elixir}
|
||||
defp simplify_signature(_, i), do: {:"arg#{i}", [], Elixir}
|
||||
|
||||
defp downcase(<<c :: utf8, rest :: binary>>) when c >= ?A and c <= ?Z do
|
||||
<<c + 32 :: utf8, downcase(rest) :: binary>>
|
||||
end
|
||||
|
||||
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
|
||||
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
|
||||
defp downcase(<<c, rest :: binary>>) do
|
||||
<<c, downcase(rest) :: binary>>
|
||||
end
|
||||
|
||||
defp downcase(<<>>) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
# Merge
|
||||
|
||||
@@ -564,23 +580,23 @@ defmodule Module do
|
||||
[]
|
||||
end
|
||||
|
||||
defp merge_signature({ :\\, line, [left, right] }, newer, i) do
|
||||
{ :\\, line, [merge_signature(left, newer, i), right] }
|
||||
defp merge_signature({:\\, line, [left, right]}, newer, i) do
|
||||
{:\\, line, [merge_signature(left, newer, i), right]}
|
||||
end
|
||||
|
||||
defp merge_signature(older, { :\\, _, [left, _] }, i) do
|
||||
defp merge_signature(older, {:\\, _, [left, _]}, i) do
|
||||
merge_signature(older, left, i)
|
||||
end
|
||||
|
||||
# The older signature, when given, always have higher precedence
|
||||
defp merge_signature({ _, _, nil } = older, _newer, _), do: older
|
||||
defp merge_signature(_older, { _, _, nil } = newer, _), do: newer
|
||||
defp merge_signature({_, _, nil} = older, _newer, _), do: older
|
||||
defp merge_signature(_older, {_, _, nil} = newer, _), do: newer
|
||||
|
||||
# Both are a guess, so check if they are the same guess
|
||||
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
|
||||
defp merge_signature({var, _, _} = older, {var, _, _}, _), do: older
|
||||
|
||||
# Otherwise, returns a generic guess
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
|
||||
defp merge_signature({_, line, _}, _newer, i), do: {:"arg#{i}", line, Elixir}
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
@@ -589,9 +605,9 @@ defmodule Module do
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
Module.defines? __MODULE__, { :version, 0 } #=> false
|
||||
Module.defines? __MODULE__, {:version, 0} #=> false
|
||||
def version, do: 1
|
||||
Module.defines? __MODULE__, { :version, 0 } #=> true
|
||||
Module.defines? __MODULE__, {:version, 0} #=> true
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -609,9 +625,9 @@ defmodule Module do
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
|
||||
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
|
||||
def version, do: 1
|
||||
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
|
||||
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -619,7 +635,7 @@ defmodule Module do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _ }] -> true
|
||||
[{_, ^kind, _, _, _, _, _}] -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
@@ -638,7 +654,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
|
||||
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -657,22 +673,22 @@ 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
|
||||
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in `module` overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
developer to customize it. See `Kernel.defoverridable/1` for
|
||||
more information and documentation.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
|
||||
lc tuple inlist tuples do
|
||||
for tuple <- tuples do
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{ name, arity } = tuple
|
||||
{name, arity} = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
@@ -684,9 +700,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
{ count + 1, clause, neighbours, false }
|
||||
end, { 1, clause, neighbours, false }, old)
|
||||
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
|
||||
{count + 1, clause, neighbours, false}
|
||||
end, {1, clause, neighbours, false}, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
end
|
||||
@@ -703,7 +719,7 @@ defmodule Module do
|
||||
@doc """
|
||||
Puts an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of putting the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/2`.
|
||||
if the attribute was registered or not via `register_attribute/3`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -728,12 +744,12 @@ defmodule Module do
|
||||
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 with `accumulate` with `Module.register_attribute`,
|
||||
was marked with `accumulate` with `Module.register_attribute/3`,
|
||||
a list is always returned.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
@@ -777,10 +793,8 @@ defmodule Module do
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
is_list(warn) && (entry = List.first(warn)) ->
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
info = Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line))
|
||||
:elixir_errors.warn info, "undefined module attribute @#{key}, " <>
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
true ->
|
||||
@@ -789,6 +803,15 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that match the given key.
|
||||
|
||||
@@ -843,12 +866,12 @@ defmodule Module do
|
||||
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, { :__persisted_attributes, [new|old] })
|
||||
:ets.insert(table, {:__persisted_attributes, [new|old]})
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, { :__acc_attributes, [new|old] })
|
||||
:ets.insert(table, {:__acc_attributes, [new|old]})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -872,13 +895,13 @@ defmodule Module do
|
||||
module = env.module
|
||||
line = env.line
|
||||
arity = length(args)
|
||||
pair = { name, arity }
|
||||
pair = {name, arity}
|
||||
doc = get_attribute(module, :doc)
|
||||
|
||||
case add_doc(module, line, kind, pair, args, doc) do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
{:error, :private_doc} ->
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
@@ -898,13 +921,13 @@ defmodule Module do
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
:ets.insert(table, { key, new })
|
||||
:ets.insert(table, {key, new})
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
||||
{ atom, 0 }
|
||||
{atom, 0}
|
||||
end
|
||||
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
@@ -918,7 +941,7 @@ defmodule Module do
|
||||
|
||||
defp normalize_attribute(key, atom) when is_atom(atom) and
|
||||
key in [:before_compile, :after_compile, :on_definition] do
|
||||
{ atom, :"__#{key}__" }
|
||||
{atom, :"__#{key}__"}
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
|
||||
@@ -12,21 +12,21 @@
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{name, arity}` - a local function/arity pair
|
||||
# * `{:import, name, arity}` - an invoked function/arity import
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `{ :import, name, arity }`
|
||||
# * in neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }`
|
||||
# * `{name, arity}`
|
||||
# * in neighbours: `:local`, `{name, arity}`
|
||||
# * out neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * `{:import, name, arity}`
|
||||
# * in neighbours: `{name, arity}`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
@@ -39,21 +39,21 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@type name_arity :: { name, arity }
|
||||
@type name_arity :: {name, arity}
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type local :: {name, arity}
|
||||
@type import :: {:import, name, arity}
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
`{name, arity}` invoked.
|
||||
"""
|
||||
@spec imports_with_dispatch(ref, name_arity) :: [module]
|
||||
def imports_with_dispatch(ref, { name, arity }) do
|
||||
def imports_with_dispatch(ref, {name, arity}) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
:digraph.out_neighbours(d, {:import, name, arity})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -71,7 +71,7 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
@@ -80,7 +80,7 @@ defmodule Module.LocalsTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
|
||||
[{_, val}] = :ets.lookup(table, :__locals_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
@@ -89,7 +89,7 @@ defmodule Module.LocalsTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
@@ -99,36 +99,36 @@ defmodule Module.LocalsTracker do
|
||||
# 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 })
|
||||
: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 })
|
||||
: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 })
|
||||
: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 })
|
||||
:gen_server.cast(pid, {:add_local, from, to})
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
:gen_server.cast(pid, {:add_import, function, module, target})
|
||||
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)
|
||||
:gen_server.call(to_pid(pid), {:yank, local}, @timeout)
|
||||
end
|
||||
|
||||
# Reattach a previously yanked node
|
||||
@@ -136,7 +136,7 @@ defmodule Module.LocalsTracker do
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, { :reattach, tuple, neighbours })
|
||||
:gen_server.cast(pid, {:reattach, tuple, neighbours})
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@@ -144,10 +144,11 @@ defmodule Module.LocalsTracker do
|
||||
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 }
|
||||
for {name, arity} <- all_defined,
|
||||
:digraph.in_neighbours(d, {:import, name, arity}) != [],
|
||||
n = :digraph.out_neighbours(d, {:import, name, arity}),
|
||||
n != [] do
|
||||
{n, name, arity}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -160,40 +161,40 @@ defmodule Module.LocalsTracker do
|
||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
[{:unused_def, tuple, kind}|acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
|
||||
{ name, arity } = tuple
|
||||
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
|
||||
invoked = for {n, a} <- reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
[{: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]
|
||||
^default -> [{:unused_args, tuple}|acc]
|
||||
unused_args -> [{:unused_args, tuple, unused_args}|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def cache_env(pid, env) do
|
||||
:gen_server.call(pid, { :cache_env, env }, @timeout)
|
||||
:gen_server.call(pid, {:cache_env, env}, @timeout)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_cached_env(pid, ref) do
|
||||
:gen_server.call(pid, { :get_cached_env, ref }, @timeout)
|
||||
:gen_server.call(pid, {:get_cached_env, ref}, @timeout)
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@@ -207,78 +208,78 @@ defmodule Module.LocalsTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
{ :ok, { d, [] } }
|
||||
{:ok, {d, []}}
|
||||
end
|
||||
|
||||
def handle_call({ :cache_env, env }, _from, { d, cache }) do
|
||||
def handle_call({:cache_env, env}, _from, {d, cache}) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
{ :reply, i, { d, cache } }
|
||||
{:reply, i, {d, cache}}
|
||||
t ->
|
||||
i = length(t)
|
||||
{ :reply, i, { d, [{i,env}|t] } }
|
||||
{:reply, i, {d, [{i,env}|t]}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({ :get_cached_env, ref }, _from, { _, cache } = state) do
|
||||
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
|
||||
{ :reply, env, state }
|
||||
def handle_call({:get_cached_env, ref}, _from, {_, cache} = state) do
|
||||
{^ref, env} = :lists.keyfind(ref, 1, cache)
|
||||
{:reply, env, state}
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, { d, _ } = state) do
|
||||
def handle_call({:yank, local}, _from, {d, _} = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, state }
|
||||
{:reply, {in_vertices, out_vertices}, state}
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, { d, _ } = state) do
|
||||
{ :reply, d, state }
|
||||
def handle_call(:digraph, _from, {d, _} = state) do
|
||||
{:reply, d, state}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
|
||||
def handle_cast({:add_local, from, to}, {d, _} = state) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
|
||||
def handle_cast({:add_import, function, module, {name, arity}}, {d, _} = state) do
|
||||
handle_import(d, function, module, name, arity)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
|
||||
def handle_cast({:add_definition, kind, tuple}, {d, _} = state) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) 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 })
|
||||
def handle_cast({:add_defaults, kind, {name, arity}, defaults}, {d, _} = state) do
|
||||
for i <- :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, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, state }
|
||||
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, state) do
|
||||
{ :stop, :normal, state }
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
@@ -286,13 +287,13 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp handle_import(d, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
|
||||
tuple = { :import, name, arity }
|
||||
tuple = {:import, name, arity}
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
|
||||
+73
-42
@@ -8,10 +8,12 @@ defmodule Node do
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@type t :: atom
|
||||
@type t :: node
|
||||
|
||||
@doc """
|
||||
Returns the current node. It returns the same as the built-in `node()`.
|
||||
Returns the current node.
|
||||
|
||||
It returns the same as the built-in `node()`.
|
||||
"""
|
||||
@spec self :: t
|
||||
def self do
|
||||
@@ -19,8 +21,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the local node is alive; that is, if the node can be
|
||||
part of a distributed system. Otherwise, it returns `false`.
|
||||
Returns `true` if the local node is alive.
|
||||
|
||||
That is, if the node can be part of a distributed system.
|
||||
"""
|
||||
@spec alive? :: boolean
|
||||
def alive? do
|
||||
@@ -29,7 +32,9 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns a list of all visible nodes in the system, excluding
|
||||
the local node. Same as `list(:visible)`.
|
||||
the local node.
|
||||
|
||||
Same as `list(:visible)`.
|
||||
"""
|
||||
@spec list :: [t]
|
||||
def list do
|
||||
@@ -37,8 +42,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of nodes according to argument given. The result
|
||||
returned when the argument is a list, is the list of nodes
|
||||
Returns a list of nodes according to argument given.
|
||||
|
||||
The result returned when the argument is a list, is the list of nodes
|
||||
satisfying the disjunction(s) of the list elements.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
|
||||
@@ -50,8 +56,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Monitors the status of the node. If `flag` is `true`, monitoring is
|
||||
turned on. If `flag` is `false`, monitoring is turned off.
|
||||
Monitors the status of the node.
|
||||
|
||||
If `flag` is `true`, monitoring is turned on.
|
||||
If `flag` is `false`, monitoring is turned off.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
|
||||
"""
|
||||
@@ -72,10 +80,28 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces the disconnection of a node. This will appear to the `node` as if
|
||||
the local node has crashed. This BIF is mainly used in the Erlang network
|
||||
authentication protocols. Returns `true` if disconnection succeeds, otherwise
|
||||
`false`. If the local node is not alive, the function returns `:ignored`.
|
||||
Tries to set up a connection to node.
|
||||
|
||||
Returns `:pang` if it fails, or `:pong` if it is successful.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Node.ping(:unknown_node)
|
||||
:pang
|
||||
|
||||
"""
|
||||
@spec ping(t) :: :pong | :pang
|
||||
def ping(node) do
|
||||
:net_adm.ping(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces the disconnection of a node.
|
||||
|
||||
This will appear to the `node` as if the local node has crashed.
|
||||
This function is mainly used in the Erlang network authentication
|
||||
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
|
||||
If the local node is not alive, the function returns `:ignored`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
|
||||
"""
|
||||
@@ -85,9 +111,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Establishes a connection to `node`. Returns `true` if successful,
|
||||
`false` if not, and the atom `:ignored` if the local node is not
|
||||
alive.
|
||||
Establishes a connection to `node`.
|
||||
|
||||
Returns `true` if successful, `false` if not, and the atom
|
||||
`:ignored` if the local node is not alive.
|
||||
|
||||
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
|
||||
"""
|
||||
@@ -112,10 +139,11 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -126,11 +154,11 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn-4 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -141,11 +169,11 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -155,11 +183,11 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. A link is created between the calling process and the
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason `:noconnection` will be
|
||||
received).
|
||||
Returns the pid of a new linked process started by the application of `fun` on `node`.
|
||||
|
||||
A link is created between the calling process and the new process, atomically.
|
||||
If `node` does not exist, a useless pid is returned (and due to the link, an exit
|
||||
signal with exit reason `:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -169,11 +197,12 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. A link is created between the calling
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
`:noconnection` will be received).
|
||||
Returns the pid of a new linked process started by the application of
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
A link is created between the calling process and the new process, atomically.
|
||||
If `node` does not exist, a useless pid is returned (and due to the link, an exit
|
||||
signal with exit reason `:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -183,9 +212,10 @@ defmodule Node do
|
||||
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`.
|
||||
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.
|
||||
"""
|
||||
@@ -194,8 +224,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the magic cookie of the local node, if the node is alive;
|
||||
otherwise `:nocookie`.
|
||||
Returns the magic cookie of the local node.
|
||||
|
||||
Returns the cookie if the node is alive, otherwise `:nocookie`.
|
||||
"""
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
|
||||
+121
-88
@@ -4,79 +4,85 @@ defmodule OptionParser do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses `argv` and returns a tuple with the parsed options, its
|
||||
arguments, and a list options that couldn't be parsed.
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
It returns the parsed values, remaining arguments and the
|
||||
invalid options.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{ [debug: true], [], [] }
|
||||
{[debug: true], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source", "lib"])
|
||||
{ [source: "lib"], [], [] }
|
||||
{[source: "lib"], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
Notice how Elixir automatically translates the "--source-path"
|
||||
switch to the underscored atom `:source_path`, which better follows
|
||||
Elixir conventions.
|
||||
By default, Elixir will try to automatically parse switches.
|
||||
Switches without an argument, like `--debug` will automatically
|
||||
be set to true. Switches followed by a value will be assigned
|
||||
to the value, always as strings.
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as the second argument:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [debug: true], [], [] }
|
||||
Note Elixir also converts the switches to underscore atoms, as
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions.
|
||||
|
||||
## Switches
|
||||
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overridden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
Many times though, it is better to explicitly list the available
|
||||
switches and their formats. The following types are supported:
|
||||
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as a string;
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
|
||||
The following extra options are supported:
|
||||
The following extra "types" are supported:
|
||||
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
|
||||
Examples:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: true], ["path/to/file"], [] }
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
{ [unlock: true, limit: 0], ["path/to/file"], [] }
|
||||
{[unlock: true, limit: 0], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
|
||||
{ [limit: 3], [], [] }
|
||||
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
|
||||
{[limit: 3], [], []}
|
||||
|
||||
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
|
||||
{ [], [], [limit: "yyz"] }
|
||||
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
|
||||
{[], [], [limit: "yyz"]}
|
||||
|
||||
## Negation switches
|
||||
|
||||
Any switches starting with `--no-` are always considered to be
|
||||
booleans and never parse the next value:
|
||||
All switches starting with `--no-` are considered to be booleans and never
|
||||
parse the next value:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{ [no_op: true], ["path/to/file"], [] }
|
||||
{[no_op: true], ["path/to/file"], []}
|
||||
|
||||
In case the negated switch exists as a boolean, it sets the boolean to false:
|
||||
However, in case the base switch exists, it sets that particular switch to
|
||||
false:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{ [op: false], ["path/to/file"], [] }
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as options too:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -92,10 +98,11 @@ defmodule OptionParser do
|
||||
## Example
|
||||
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
|
||||
{[source: "lib"], ["test/enum_test.exs", "--verbose"], []}
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
@@ -113,24 +120,15 @@ defmodule OptionParser do
|
||||
parse(argv, aliases, switches, [], [], [], all)
|
||||
end
|
||||
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
|
||||
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, args, invalid, _all) do
|
||||
{Enum.reverse(dict), Enum.reverse(args, value), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{ option, kinds, value } = normalize_option(option, switches, aliases)
|
||||
|
||||
if nil?(value) do
|
||||
{ value, t } =
|
||||
if :boolean in kinds do
|
||||
{ true, t }
|
||||
else
|
||||
value_from_tail(t)
|
||||
end
|
||||
end
|
||||
|
||||
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
|
||||
|
||||
{option, value} = split_option(option)
|
||||
{option, kinds, value} = normalize_option(option, value, switches, aliases)
|
||||
{value, kinds, t} = normalize_value(value, kinds, t)
|
||||
{dict, invalid} = store_option(dict, invalid, option, value, kinds)
|
||||
parse(t, aliases, switches, dict, args, invalid, all)
|
||||
end
|
||||
|
||||
@@ -139,89 +137,124 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp parse([], _, _switches, dict, args, invalid, true) do
|
||||
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
|
||||
{Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(value, _, _switches, dict, _args, invalid, false) do
|
||||
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
|
||||
{Enum.reverse(dict), value, Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
|
||||
defp value_from_tail([h|t]), do: { h, t }
|
||||
defp value_from_tail([]), do: { true, [] }
|
||||
|
||||
defp store_option(dict, invalid, option, value, kinds) do
|
||||
{ invalid_option, value } =
|
||||
{invalid_option, value} =
|
||||
cond do
|
||||
:invalid in kinds ->
|
||||
{option, value}
|
||||
:boolean in kinds ->
|
||||
{ nil, value in [true, "true"] }
|
||||
{nil, value in [true, "true"]}
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
true ->
|
||||
{ nil, value }
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
if invalid_option do
|
||||
{ dict, [{ option, value }|invalid] }
|
||||
{dict, [{option, value}|invalid]}
|
||||
else
|
||||
{ do_store_option(dict, option, value, kinds), invalid }
|
||||
{do_store_option(dict, option, value, kinds), invalid}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_store_option(dict, option, value, kinds) do
|
||||
cond do
|
||||
:keep in kinds ->
|
||||
[{ option, value }|dict]
|
||||
[{option, value}|dict]
|
||||
true ->
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
[{option, value}|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
|
||||
normalize_option(option, switches, aliases)
|
||||
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
normalize_option(get_negated(option, switches), value, switches)
|
||||
end
|
||||
|
||||
defp normalize_option(option, switches, aliases) do
|
||||
{ option, value } = split_option(option)
|
||||
|
||||
if non_neg = get_non_negated(option, aliases) do
|
||||
kinds = List.wrap(switches[non_neg])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ non_neg, kinds, false }
|
||||
else
|
||||
{ get_aliased(option, aliases), [:boolean], true }
|
||||
end
|
||||
defp normalize_option(option, value, switches, aliases) do
|
||||
option = get_option(option)
|
||||
if alias = aliases[option] do
|
||||
normalize_option({:default, alias}, value, switches)
|
||||
else
|
||||
atom = get_aliased(option, aliases)
|
||||
{ atom, List.wrap(switches[atom]), value }
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, _value, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{option, kinds, false}
|
||||
kinds == [] ->
|
||||
{option, kinds, true}
|
||||
true ->
|
||||
{option, [:invalid], false}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
end
|
||||
|
||||
defp normalize_value(nil, kinds, t) do
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{true, kinds, t}
|
||||
value_in_tail?(t) ->
|
||||
[h|t] = t
|
||||
{h, kinds, t}
|
||||
kinds == [] ->
|
||||
{true, kinds, t}
|
||||
true ->
|
||||
{true, [:invalid], t}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_value(value, kinds, t) do
|
||||
{value, kinds, t}
|
||||
end
|
||||
|
||||
defp value_in_tail?(["-" <> _|_]), do: false
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> { h, nil }
|
||||
[h, t] -> { h, t }
|
||||
[h] -> {h, nil}
|
||||
[h, t] -> {h, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
|
||||
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
|
||||
defp get_aliased(option, aliases) do
|
||||
atom = option |> to_underscore |> binary_to_atom
|
||||
aliases[atom] || atom
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> binary_to_atom
|
||||
end
|
||||
|
||||
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
|
||||
defp get_non_negated(_, _), do: nil
|
||||
defp get_negated("no-" <> rest = option, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
|
||||
{:negated, option}
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
{:default, get_option(rest)}
|
||||
end
|
||||
end
|
||||
|
||||
+127
-120
@@ -3,9 +3,9 @@ defmodule Path do
|
||||
This module provides conveniences for manipulating or
|
||||
retrieving file system paths.
|
||||
|
||||
The functions in this module may receive a char list or
|
||||
a binary as an argument and will return a value of the same
|
||||
type.
|
||||
The functions in this module may receive a char data as
|
||||
argument (i.e. a string or a list of characters / string)
|
||||
and will always return a string (encoded in UTF-8).
|
||||
|
||||
The majority of the functions in this module do not
|
||||
interact with the file system, except for a few functions
|
||||
@@ -13,9 +13,7 @@ defmodule Path do
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
|
||||
@type t :: char_list | atom | binary
|
||||
@type r :: char_list | binary
|
||||
@type t :: char_data
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Unlike
|
||||
@@ -37,8 +35,9 @@ defmodule Path do
|
||||
"D:/usr/local/../x"
|
||||
|
||||
"""
|
||||
@spec absname(t) :: binary
|
||||
def absname(path) do
|
||||
FN.absname(path, get_cwd(path))
|
||||
absname(path, System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -57,8 +56,39 @@ defmodule Path do
|
||||
"bar/../x"
|
||||
|
||||
"""
|
||||
@spec absname(t, t) :: binary
|
||||
def absname(path, relative_to) do
|
||||
FN.absname(path, relative_to)
|
||||
path = String.from_char_data!(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
if :binary.last(path) == ?/ do
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
else
|
||||
path
|
||||
end
|
||||
:volumerelative ->
|
||||
relative_to = String.from_char_data!(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
|
||||
## Absolute path on current drive
|
||||
defp absname_vr(["/"|rest], [volume|_], _relative),
|
||||
do: join([volume|rest])
|
||||
|
||||
## Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
|
||||
do: absname(join(rest), relative)
|
||||
|
||||
## Relative to current directory on another drive.
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> String.from_char_data!(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -71,8 +101,9 @@ defmodule Path do
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec expand(t) :: binary
|
||||
def expand(path) do
|
||||
normalize FN.absname(expand_home(path), get_cwd(path))
|
||||
normalize absname(expand_home(path), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -97,8 +128,9 @@ defmodule Path do
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec expand(t, t) :: binary
|
||||
def expand(path, relative_to) do
|
||||
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
|
||||
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -119,9 +151,10 @@ defmodule Path do
|
||||
Path.type("/bar/foo.ex") #=> :volumerelative
|
||||
|
||||
"""
|
||||
@spec type(t) :: :absolute | :relative | :volumerelative
|
||||
def type(name) when is_list(name) or is_binary(name) do
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> win32_pathtype(name)
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(0)
|
||||
end
|
||||
@@ -143,53 +176,50 @@ defmodule Path do
|
||||
Path.relative("/bar/foo.ex") #=> "bar/foo.ex"
|
||||
|
||||
"""
|
||||
@spec relative(t) :: binary
|
||||
def relative(name) do
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> win32_pathtype(name)
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1)
|
||||
end |> elem(1) |> String.from_char_data!
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp unix_pathtype([?/|relative]), do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp unix_pathtype([list|rest]) when is_list(list), do:
|
||||
unix_pathtype(list ++ rest)
|
||||
defp unix_pathtype([atom|rest]) when is_atom(atom), do:
|
||||
unix_pathtype(atom_to_list(atom) ++ rest)
|
||||
defp unix_pathtype(relative), do:
|
||||
{ :relative, relative }
|
||||
{:relative, relative}
|
||||
|
||||
@slash [?/, ?\\]
|
||||
|
||||
defp win32_pathtype([list|rest]) when is_list(list), do:
|
||||
win32_pathtype(list++rest)
|
||||
defp win32_pathtype([atom|rest]) when is_atom(atom), do:
|
||||
win32_pathtype(atom_to_list(atom)++rest)
|
||||
defp win32_pathtype([char, list|rest]) when is_list(list), do:
|
||||
win32_pathtype([char|list++rest])
|
||||
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
|
||||
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype([c | relative]) when c in @slash, do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype([c1, c2, list|rest]) when is_list(list), do:
|
||||
win32_pathtype([c1, c2|list++rest])
|
||||
defp win32_pathtype([_letter, ?:, c | relative]) when c in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype([_letter, ?: | relative]), do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype(relative), do:
|
||||
{ :relative, relative }
|
||||
{:relative, relative}
|
||||
|
||||
@doc """
|
||||
Returns the given `path` relative to the given `from` path.
|
||||
@@ -205,23 +235,18 @@ defmodule Path do
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/usr/local")
|
||||
"foo"
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/")
|
||||
"usr/local/foo"
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/etc")
|
||||
"/usr/local/foo"
|
||||
|
||||
"""
|
||||
def relative_to(path, from) when is_list(path) and is_binary(from) do
|
||||
path = filename_string_to_binary(path)
|
||||
relative_to(FN.split(path), FN.split(from), path)
|
||||
end
|
||||
|
||||
def relative_to(path, from) when is_binary(path) and is_list(from) do
|
||||
relative_to(FN.split(path), FN.split(filename_string_to_binary(from)), path)
|
||||
end
|
||||
|
||||
@spec relative_to(t, t) :: binary
|
||||
def relative_to(path, from) do
|
||||
relative_to(FN.split(path), FN.split(from), path)
|
||||
path = String.from_char_data!(path)
|
||||
relative_to(split(path), split(from), path)
|
||||
end
|
||||
|
||||
defp relative_to([h|t1], [h|t2], original) do
|
||||
@@ -229,7 +254,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
defp relative_to([_|_] = l1, [], _original) do
|
||||
FN.join(l1)
|
||||
join(l1)
|
||||
end
|
||||
|
||||
defp relative_to(_, _, original) do
|
||||
@@ -241,9 +266,10 @@ defmodule Path do
|
||||
directory. If, for some reason, the current working directory
|
||||
cannot be retrieved, returns the full path.
|
||||
"""
|
||||
@spec relative_to_cwd(t) :: binary
|
||||
def relative_to_cwd(path) do
|
||||
case :file.get_cwd do
|
||||
{ :ok, base } -> relative_to(path, base)
|
||||
{:ok, base} -> relative_to(path, String.from_char_data!(base))
|
||||
_ -> path
|
||||
end
|
||||
end
|
||||
@@ -264,8 +290,9 @@ defmodule Path do
|
||||
""
|
||||
|
||||
"""
|
||||
@spec basename(t) :: binary
|
||||
def basename(path) do
|
||||
FN.basename(path)
|
||||
FN.basename(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -277,14 +304,17 @@ defmodule Path do
|
||||
|
||||
iex> Path.basename("~/foo/bar.ex", ".ex")
|
||||
"bar"
|
||||
|
||||
iex> Path.basename("~/foo/bar.exs", ".ex")
|
||||
"bar.exs"
|
||||
|
||||
iex> Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
"bar.old"
|
||||
|
||||
"""
|
||||
@spec basename(t, t) :: binary
|
||||
def basename(path, extension) do
|
||||
FN.basename(path, extension)
|
||||
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -298,8 +328,9 @@ defmodule Path do
|
||||
#=> "/foo/bar"
|
||||
|
||||
"""
|
||||
@spec dirname(t) :: binary
|
||||
def dirname(path) do
|
||||
FN.dirname(path)
|
||||
FN.dirname(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -309,12 +340,14 @@ defmodule Path do
|
||||
|
||||
iex> Path.extname("foo.erl")
|
||||
".erl"
|
||||
|
||||
iex> Path.extname("~/foo/bar")
|
||||
""
|
||||
|
||||
"""
|
||||
@spec extname(t) :: binary
|
||||
def extname(path) do
|
||||
FN.extension(path)
|
||||
FN.extension(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -324,12 +357,14 @@ defmodule Path do
|
||||
|
||||
iex> Path.rootname("/foo/bar")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.rootname("/foo/bar.ex")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec rootname(t) :: binary
|
||||
def rootname(path) do
|
||||
FN.rootname(path)
|
||||
FN.rootname(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -340,34 +375,39 @@ defmodule Path do
|
||||
|
||||
iex> Path.rootname("/foo/bar.erl", ".erl")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.rootname("/foo/bar.erl", ".ex")
|
||||
"/foo/bar.erl"
|
||||
|
||||
"""
|
||||
@spec rootname(t, t) :: binary
|
||||
def rootname(path, extension) do
|
||||
FN.rootname(path, extension)
|
||||
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
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.
|
||||
Note that any trailing slash is removed on join.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Path.join(["~", "foo"])
|
||||
"~/foo"
|
||||
|
||||
iex> Path.join(["foo"])
|
||||
"foo"
|
||||
iex> Path.join(["/", "foo", "bar"])
|
||||
|
||||
iex> Path.join(["/", "foo", "bar/"])
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec join([t]) :: binary
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]) when is_list(name), do:
|
||||
binary_to_filename_string(do_join(filename_string_to_binary(name), <<>>, [], major_os_type()))
|
||||
def join([name]) when is_binary(name), do:
|
||||
do_join(name, <<>>, [], major_os_type())
|
||||
def join([name]), do:
|
||||
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
@@ -378,23 +418,9 @@ defmodule Path do
|
||||
"foo/bar"
|
||||
|
||||
"""
|
||||
def join(left, right) when is_binary(left) and is_binary(right), do:
|
||||
do_join(left, Path.relative(right), [], major_os_type())
|
||||
|
||||
def join(left, right) when is_binary(left) and is_list(right), do:
|
||||
join(left, filename_string_to_binary(right))
|
||||
|
||||
def join(left, right) when is_list(left) and is_binary(right), do:
|
||||
join(filename_string_to_binary(left), right)
|
||||
|
||||
def join(left, right) when is_list(left) and is_list(right), do:
|
||||
binary_to_filename_string join(filename_string_to_binary(left), filename_string_to_binary(right))
|
||||
|
||||
def join(left, right) when is_atom(left), do:
|
||||
join(atom_to_binary(left), right)
|
||||
|
||||
def join(left, right) when is_atom(right), do:
|
||||
join(left, atom_to_binary(right))
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
@@ -408,17 +434,26 @@ defmodule Path do
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, _os_type), do:
|
||||
iolist_to_binary(:lists.reverse(result))
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
@@ -427,17 +462,21 @@ defmodule Path do
|
||||
|
||||
iex> Path.split("")
|
||||
[]
|
||||
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [binary]
|
||||
|
||||
# Work around a bug in Erlang on UNIX
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(path)
|
||||
FN.split(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -473,62 +512,30 @@ defmodule Path do
|
||||
Path.wildcard("projects/*/ebin/**/*.{beam,app}")
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = :filelib.wildcard binary_to_filename_string(glob)
|
||||
encoding = :file.native_name_encoding()
|
||||
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
:filelib.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
|
||||
defp get_cwd(_), do: System.cwd!
|
||||
|
||||
defp binary_to_filename_string(binary) do
|
||||
case :unicode.characters_to_list(binary) do
|
||||
{ :error, _, _ } ->
|
||||
:erlang.error(:badarg)
|
||||
list when is_list(list) ->
|
||||
list
|
||||
end
|
||||
end
|
||||
|
||||
defp filename_string_to_binary(list) do
|
||||
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
|
||||
end
|
||||
|
||||
defp flatten_filename_to_binary(list, encoding) do
|
||||
case :unicode.characters_to_binary(list, :unicode, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
:erlang.error(:badarg)
|
||||
bin when is_binary(bin) ->
|
||||
bin
|
||||
end
|
||||
@spec wildcard(t) :: [binary]
|
||||
def wildcard(glob) do
|
||||
glob
|
||||
|> List.from_char_data!
|
||||
|> :filelib.wildcard
|
||||
|> Enum.map(&String.from_char_data!/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
|
||||
defp expand_home(<<?~, rest :: binary>>) do
|
||||
System.user_home! <> rest
|
||||
defp expand_home(type) do
|
||||
case String.from_char_data!(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
rest -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_home('~' ++ rest) do
|
||||
(System.user_home! |> binary_to_filename_string) ++ rest
|
||||
end
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
|
||||
defp expand_home(other), do: other
|
||||
|
||||
defp normalize(path), do: normalize(FN.split(path), [])
|
||||
|
||||
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp normalize([top|t], acc) when top in [".", '.'] do
|
||||
defp normalize(["."|t], acc) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
@@ -537,6 +544,6 @@ defmodule Path do
|
||||
end
|
||||
|
||||
defp normalize([], acc) do
|
||||
join Enum.reverse(acc)
|
||||
join :lists.reverse(acc)
|
||||
end
|
||||
end
|
||||
|
||||
+71
-75
@@ -62,6 +62,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec delete :: [{term, term}]
|
||||
def delete() do
|
||||
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
|
||||
:erlang.erase()
|
||||
end
|
||||
|
||||
@@ -103,24 +104,31 @@ 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 """
|
||||
Sends a message to the given process.
|
||||
|
||||
It does the same as `Kernel.send/2`.
|
||||
If the option `:noconnect` is used and sending the message would require an
|
||||
auto-connection to another node the message is not sent and `:noconnect` is
|
||||
returned.
|
||||
|
||||
If the option `:nosuspend` is used and sending the message would cause the
|
||||
sender to be suspended the message is not sent and `:nosuspend` is returned.
|
||||
|
||||
Otherwise the message is sent and `:ok` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Process.send({:name, :node_does_not_exist}, :hi, [:noconnect])
|
||||
:noconnect
|
||||
|
||||
"""
|
||||
@spec send(dest, msg) :: msg when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
@spec send(dest, msg, [option]) :: result when
|
||||
dest: pid | port | atom | {atom, node},
|
||||
msg: any,
|
||||
option: :noconnect | :nosuspend,
|
||||
result: :ok | :noconnect | :nosuspend
|
||||
def send(dest, msg, options) do
|
||||
:erlang.send(dest, msg, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -152,21 +160,16 @@ defmodule Process do
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
Spawns the given module and function passing the given args
|
||||
according to the given options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
The result depends on the given options. In particular,
|
||||
if `:monitor` is given as an option, it will return a tuple
|
||||
containing the pid and the monitoring reference, otherwise
|
||||
just the spawned process pid.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -176,81 +179,72 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
Spawns the given module and function passing the given args
|
||||
according to the given options.
|
||||
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any]) :: pid
|
||||
def spawn(mod, fun, args) do
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
The result depends on the given options. In particular,
|
||||
if `:monitor` is given as an option, it will return a tuple
|
||||
containing the pid and the monitoring reference, otherwise
|
||||
just the spawned process pid.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
|
||||
@spec spawn(module, atom, list, spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(mod, fun, args, opts) do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
A link is created between the calling process and the new
|
||||
process, atomically.
|
||||
@doc false
|
||||
def spawn(fun) do
|
||||
# IO.write :stderr, "Process.spawn/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link((() -> any)) :: pid
|
||||
@doc false
|
||||
def spawn(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(fun) do
|
||||
# IO.write :stderr, "Process.spawn_link/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. A link is created between the calling process
|
||||
and the new process, atomically. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(module, atom, [any]) :: pid
|
||||
@doc false
|
||||
def spawn_link(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_link/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
and reference for a monitor created to the new process.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor((() -> any)) :: {pid, reference}
|
||||
@doc false
|
||||
def spawn_monitor(fun) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
A new process is started by the application of `module.function(args)`
|
||||
and the process is monitored at the same time. Returns the pid and a
|
||||
reference for the monitor. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
|
||||
@doc false
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self() do
|
||||
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def send(dest, msg) do
|
||||
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The calling process starts monitoring the item given.
|
||||
It returns the monitor reference.
|
||||
@@ -326,7 +320,9 @@ defmodule Process do
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
||||
is no longer alive, (check with `alive?/1`) or if the name is
|
||||
already registered (check with `registered?/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) do
|
||||
@@ -428,7 +424,7 @@ defmodule Process do
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@compile { :inline, nillify: 1 }
|
||||
@compile {:inline, nillify: 1}
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
+87
-95
@@ -33,13 +33,32 @@ defmodule Protocol do
|
||||
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
|
||||
# == Deprecated records handling ==
|
||||
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
lc { guard, body } inlist bodies do
|
||||
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
@@ -47,27 +66,37 @@ defmodule Protocol do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Handle special Record type
|
||||
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
|
||||
|
||||
# Handle special Any type
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
try do
|
||||
__MODULE__.Any.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
|
||||
nil
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:name)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
@@ -90,7 +119,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
@@ -124,7 +153,7 @@ defmodule Protocol do
|
||||
@doc false
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
{:module, ^module} -> nil
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
@@ -139,87 +168,50 @@ defmodule Protocol do
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def builtin do
|
||||
[ Tuple, Atom, List, BitString, Integer, Float,
|
||||
Function, PID, Port, Reference, Any ]
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def impl_for(current) do
|
||||
arg = quote(do: arg)
|
||||
all = [Record|builtin]
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
{ arg,
|
||||
lc(mod inlist all, do: impl_for(current, mod, arg)),
|
||||
rec_impl_for(current, arg) }
|
||||
end
|
||||
fallback = quote do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
defp rec_impl_for(current, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
quote do
|
||||
target = Module.concat(unquote(current), unquote(arg))
|
||||
try do
|
||||
target.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
false ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp impl_for(current, Record, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
dispatch = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true -> rec_impl_for(atom)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
{ is_record(unquote(arg)), unquote(dispatch) }
|
||||
end
|
||||
end
|
||||
|
||||
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
|
||||
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
|
||||
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
|
||||
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
|
||||
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
|
||||
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
|
||||
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
|
||||
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
|
||||
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
|
||||
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
|
||||
|
||||
defp impl_for(_current, Any, _arg) do
|
||||
{ true, quote(do: any_impl_for) }
|
||||
end
|
||||
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, arg) do
|
||||
quote do
|
||||
{ :erlang.unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod))) }
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to dispatch to a given target, fallbacks to the
|
||||
# given `fallback` implementation if the target does not exist.
|
||||
defp with_fallback(target) when is_atom(target) do
|
||||
quote do
|
||||
try do
|
||||
unquote(target).__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
|
||||
any_impl_for
|
||||
end
|
||||
end
|
||||
{arg, impl_for}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -227,18 +219,18 @@ defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
end
|
||||
|
||||
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = lc i inlist :lists.seq(2, arity),
|
||||
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
quote do
|
||||
@@ -270,17 +262,17 @@ defmodule Protocol.DSL do
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{ :module, Kernel.Typespec } ->
|
||||
tuple = { name, arity }
|
||||
{:module, Kernel.Typespec} ->
|
||||
tuple = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
found = for {k, v} <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
{ :error, _ } ->
|
||||
{:error, _} ->
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,15 +62,15 @@ defmodule Protocol.Consolidation do
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
lc path inlist paths,
|
||||
file inlist list_dir(path),
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{ :ok, files } -> files
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
@@ -78,14 +78,14 @@ defmodule Protocol.Consolidation do
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
|
||||
extract_from_beam(Path.join(path, file), callback)
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, { module, [attributes: attributes] } } ->
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
@@ -95,7 +95,7 @@ defmodule Protocol.Consolidation do
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
@@ -121,10 +121,13 @@ defmodule Protocol.Consolidation do
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{ :ok, binary } |
|
||||
{ :error, :not_a_protocol } |
|
||||
{ :error, :no_beam_info }
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
@@ -133,16 +136,16 @@ defmodule Protocol.Consolidation do
|
||||
# Ensure the given module is loaded and is a protocol.
|
||||
defp ensure_protocol(protocol) do
|
||||
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
|
||||
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
|
||||
attributes: attributes] } } ->
|
||||
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
|
||||
attributes: attributes]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{ :ok, { protocol, any, abstract_code } }
|
||||
{:ok, {protocol, any, abstract_code}}
|
||||
_ ->
|
||||
{ :error, :not_a_protocol }
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{ :error, :no_beam_info }
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -155,38 +158,40 @@ defmodule Protocol.Consolidation do
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({ protocol, any, code }, types) do
|
||||
defp change_debug_info({protocol, any, code}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
records = types -- Protocol.builtin
|
||||
builtin = Protocol.builtin -- (Protocol.builtin -- types)
|
||||
builtin = if records != [], do: [Record|builtin], else: builtin
|
||||
change_impl_for(code, protocol, builtin, records, false, [])
|
||||
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
|
||||
structs = types -- all
|
||||
change_impl_for(code, protocol, types, structs, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
|
||||
attr = [fallback_to_any: Any in builtin, consolidated: true]
|
||||
change_impl_for(t, protocol, builtin, records, true,
|
||||
[{ :attribute, line, :protocol, attr }|acc])
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
|
||||
unless Any in builtin do
|
||||
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
|
||||
end
|
||||
clauses = for {guard, mod} <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :impl_for, 1, clauses }|acc])
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
|
||||
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
@@ -195,72 +200,50 @@ defmodule Protocol.Consolidation do
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{ :ok, { protocol, Enum.reverse(acc) } }
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{ :error, :not_a_protocol }
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
|
||||
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
|
||||
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
|
||||
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
|
||||
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
|
||||
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
|
||||
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
|
||||
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
|
||||
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
|
||||
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
|
||||
|
||||
defp clause_for(Any, protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, Module.concat(protocol, Any) }]}
|
||||
end
|
||||
|
||||
defp clause_for(Record, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :x}],
|
||||
[[{:op, line, :andalso,
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
|
||||
[{:var, line, :x}]},
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]
|
||||
}]},
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :rec_impl_for},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]}]}]}
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp record_clause_for(other, protocol, line) do
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, value }]}
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({ protocol, code }) do
|
||||
defp compile({protocol, code}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
|
||||
{ :ok, binary }
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
{:ok, binary}
|
||||
end
|
||||
end
|
||||
|
||||
+43
-24
@@ -1,34 +1,53 @@
|
||||
defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
|
||||
A Range are represented internally as a struct. However,
|
||||
the most common form of creating and matching on ranges
|
||||
is via the `../2` macro, auto-imported from Kernel:
|
||||
|
||||
iex> range = 1..3
|
||||
1..3
|
||||
iex> first .. last = range
|
||||
iex> first
|
||||
1
|
||||
iex> last
|
||||
3
|
||||
|
||||
"""
|
||||
|
||||
@type t :: { Range, any, any }
|
||||
@type t(first, last) :: { Range, first, last }
|
||||
defstruct first: nil, last: nil
|
||||
|
||||
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
def new(first, last) do
|
||||
{ Range, first, last }
|
||||
%Range{first: first, last: last}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item of the range.
|
||||
"""
|
||||
def first({ Range, first, _ }) do
|
||||
first
|
||||
end
|
||||
Returns true if the given argument is a range.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Range.range?(1..3)
|
||||
true
|
||||
|
||||
iex> Range.range?(0)
|
||||
false
|
||||
|
||||
@doc """
|
||||
Returns the last item of the range.
|
||||
"""
|
||||
def last({ Range, _, last }) do
|
||||
last
|
||||
end
|
||||
def range?(%Range{}), do: true
|
||||
def range?(_), do: false
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@moduledoc """
|
||||
A protocol used for iterating range elements.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns the function that calculates the next item.
|
||||
"""
|
||||
@@ -45,36 +64,36 @@ defimpl Enumerable, for: Range do
|
||||
reduce(first, last, acc, fun, Range.Iterator.next(first, range), last >= first)
|
||||
end
|
||||
|
||||
defp reduce(_x, _y, { :halt, acc }, _fun, _next, _up) do
|
||||
{ :halted, acc }
|
||||
defp reduce(_x, _y, {:halt, acc}, _fun, _next, _up) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :suspend, acc }, fun, next, up) do
|
||||
{ :suspended, acc, &reduce(x, y, &1, fun, next, up) }
|
||||
defp reduce(x, y, {:suspend, acc}, fun, next, up) do
|
||||
{:suspended, acc, &reduce(x, y, &1, fun, next, up)}
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :cont, acc }, fun, next, true) when x <= y do
|
||||
defp reduce(x, y, {:cont, acc}, fun, next, true) when x <= y do
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, true)
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :cont, acc }, fun, next, false) when x >= y do
|
||||
defp reduce(x, y, {:cont, acc}, fun, next, false) when x >= y do
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, false)
|
||||
end
|
||||
|
||||
defp reduce(_, _, { :cont, acc }, _fun, _next, _up) do
|
||||
{ :done, acc }
|
||||
defp reduce(_, _, {:cont, acc}, _fun, _next, _up) do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first .. last, value) do
|
||||
if first <= last do
|
||||
{ :ok, first <= value and value <= last }
|
||||
{:ok, first <= value and value <= last}
|
||||
else
|
||||
{ :ok, last <= value and value <= first }
|
||||
{:ok, last <= value and value <= first}
|
||||
end
|
||||
end
|
||||
|
||||
def count(first .. _ = range) do
|
||||
{ :ok, Range.Iterator.count(first, range) }
|
||||
{:ok, Range.Iterator.count(first, range)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+150
-875
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
defmodule Record.Backend do
|
||||
# Callback functions invoked by defrecord, defrecordp and friends.
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Normalizes a list of record or struct fields to have default values.
|
||||
"""
|
||||
def default_fields(type, fields) do
|
||||
Enum.map(fields, fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a keywords list into fields and types.
|
||||
|
||||
This logic is shared by records and structs.
|
||||
"""
|
||||
def split_fields_and_types(kv) do
|
||||
if Keyword.keyword?(kv) do
|
||||
split_fields_and_types(kv, [], [])
|
||||
else
|
||||
{kv, []}
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([field|t], fields, types) do
|
||||
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([], fields, types) do
|
||||
{:lists.reverse(fields), :lists.reverse(types)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from record/0 and record/1 macros.
|
||||
"""
|
||||
def access(atom, fields, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(atom, fields, args)
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from the record/2 macro.
|
||||
"""
|
||||
def access(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
Keyword.keyword?(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the index of field.
|
||||
"""
|
||||
def index(atom, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new record with the given default fields and keyword values.
|
||||
"""
|
||||
def create(atom, fields, keyword, caller) do
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> {:_, [], nil}
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a record given by var with the given keyword.
|
||||
"""
|
||||
def update(atom, fields, var, keyword, caller) do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a record key from the given var.
|
||||
"""
|
||||
def get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
@@ -0,0 +1,526 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{:ok, fields, types, def_type} ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
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
|
||||
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
def deffunctions(values, env) do
|
||||
values = for value <- values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
values = for value <- values do
|
||||
{name, default} = convert_value(value)
|
||||
{name, default, find_spec(types, name)}
|
||||
end
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = for value <- values do
|
||||
{key, value} = convert_value(value)
|
||||
{key, Macro.escape(value)}
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = {name, length(args)}
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
functions =
|
||||
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
|
||||
[tuple|functions]
|
||||
else
|
||||
List.delete(functions, tuple)
|
||||
end
|
||||
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
def access(atom, fields, arg, _caller) when is_atom(arg) do
|
||||
if index = find_index(fields, arg, 0) do
|
||||
index + 1
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end
|
||||
|
||||
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
defp reflection(values) do
|
||||
quoted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
defp initializer(values) do
|
||||
defaults = for {_, value} <- values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: {__MODULE__, unquote_splicing(defaults)}
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: {__MODULE__, unquote_splicing(atom_selective)}
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: {__MODULE__, unquote_splicing(string_selective)}
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp conversions(values) do
|
||||
sorted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
{k, quote(do: :erlang.element(unquote(index + 2), record))}
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp accessors([{:__exception__, _}|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{key, _default}|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp accessors([], _i) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
atom_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
|
||||
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_optimizable do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile {unquote(__MODULE__), :__before_compile__}
|
||||
@on_definition {unquote(__MODULE__), :__on_definition__}
|
||||
end
|
||||
end
|
||||
|
||||
defp switch_recorder do
|
||||
quote do: @record_optimized false
|
||||
end
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = for {_, _, spec} <- values, do: spec
|
||||
options = for {k, _, v} <- values, do: {k, v}
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: {__MODULE__, unquote_splicing(types)}
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{key, _default, spec}|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({x, _}) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: {atom, nil}
|
||||
|
||||
defp convert_value({atom, other}) when is_atom(atom), do:
|
||||
{atom, check_value(atom, other)}
|
||||
|
||||
defp convert_value({field, _}), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
for(i <- other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
for(i <- tuple_to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == {:env, []} and
|
||||
:erlang.fun_info(other, :type) == {:type, :external} do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = for {k, v} <- types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,8 +3,8 @@ defmodule Record.Extractor do
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def retrieve(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
file = List.from_char_data!(file)
|
||||
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
@@ -12,26 +12,26 @@ defmodule Record.Extractor do
|
||||
realfile -> realfile
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
extract_record(name, realfile)
|
||||
end
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(String.to_char_list!(file))
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(List.from_char_data!(file))
|
||||
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
{ :error, _ } ->
|
||||
{:error, _} ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
extract_record name, :filename.join([libpath|path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
defp extract_record(name, file) do
|
||||
form = read_file(file)
|
||||
records = retrieve_records(form)
|
||||
records = extract_records(form)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
@@ -39,9 +39,9 @@ defmodule Record.Extractor do
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_records(form) do
|
||||
lc { :attribute, _, :record, record } inlist form, do: record
|
||||
# Parse the given file and extract all existent records.
|
||||
defp extract_records(form) do
|
||||
for {:attribute, _, :record, record} <- form, do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
@@ -49,7 +49,7 @@ defmodule Record.Extractor do
|
||||
# by using Erlang's epp_dodger.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
{ :ok, form } ->
|
||||
{:ok, form} ->
|
||||
form
|
||||
other ->
|
||||
raise "error parsing file #{file}, got: #{inspect(other)}"
|
||||
@@ -59,34 +59,34 @@ defmodule Record.Extractor do
|
||||
# Parse a tuple with name and fields and returns a
|
||||
# list of tuples where the first element is the field
|
||||
# and the second is its default value.
|
||||
defp parse_record({ _name, fields }, form) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
defp parse_record({_name, fields}, form) do
|
||||
cons = List.foldr fields, {nil, 0}, fn f, acc ->
|
||||
{:cons, 0, parse_field(f), acc}
|
||||
end
|
||||
eval_record(cons, form)
|
||||
end
|
||||
|
||||
defp parse_field({ :typed_record_field, record_field, _type }) do
|
||||
defp parse_field({:typed_record_field, record_field, _type}) do
|
||||
parse_field(record_field)
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key }) do
|
||||
{ :tuple, 0, [key, {:atom, 0, :undefined}] }
|
||||
defp parse_field({:record_field, _, key}) do
|
||||
{:tuple, 0, [key, {:atom, 0, :undefined}]}
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key, value }) do
|
||||
{ :tuple, 0, [key, value] }
|
||||
defp parse_field({:record_field, _, key, value}) do
|
||||
{:tuple, 0, [key, value]}
|
||||
end
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ { :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ cons ] } ] } ]
|
||||
[ {:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ cons ]} ]} ]
|
||||
|
||||
{ :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
|
||||
{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
|
||||
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
end
|
||||
end
|
||||
|
||||
+323
-145
@@ -1,21 +1,29 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %S"""
|
||||
Regular expressions for Elixir built on top of the `re` module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
@moduledoc ~S"""
|
||||
Regular expressions for Elixir built on top of Erlang's `re` module.
|
||||
|
||||
As the `re` module, Regex is based on PCRE
|
||||
(Perl Compatible Regular Expressions). More information can be
|
||||
found in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using `Regex.compile!/2`
|
||||
or using the special form with [`%r`](Kernel.html#sigil_r/2):
|
||||
or using the special form with [`~r`](Kernel.html#sigil_r/2):
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
~r/foo/
|
||||
|
||||
# A regular expression with case insensitive options
|
||||
%r/foo/i
|
||||
# A regular expression with case insensitive and unicode options
|
||||
~r/foo/iu
|
||||
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
A Regex is represented internally as the `Regex` struct. Therefore,
|
||||
`%Regex{}` can be used whenever there is a need to match on them.
|
||||
|
||||
## Modifiers
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
|
||||
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
|
||||
strings to be given on match
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
@@ -27,7 +35,6 @@ defmodule Regex do
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `groups` (g) - compiles with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
@@ -37,13 +44,31 @@ defmodule Regex do
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
Most of the functions in this module accept either a binary or a char list
|
||||
as subject. The result is based on the argument (a binary will return
|
||||
a binary, a char list will return a char list).
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string.
|
||||
This is the default;
|
||||
|
||||
* `:first` - only the first captured subpattern, which is always the complete
|
||||
matching part of the string. All explicitly captured subpatterns are
|
||||
discarded;
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
|
||||
captured subpatterns, but not the complete matching part of
|
||||
the string;
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
|
||||
* `:all_names` - captures all names in the Regex;
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, binary, binary, [atom] | nil }
|
||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
|
||||
@@ -51,32 +76,31 @@ defmodule Regex do
|
||||
Compiles the regular expression.
|
||||
|
||||
The given options can either be a binary with the characters
|
||||
representing the same regex options given to the `%r` sigil,
|
||||
representing the same regex options given to the `~r` sigil,
|
||||
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
It returns `{:ok, regex}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, %r"foo"}
|
||||
{:ok, ~r"foo"}
|
||||
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
|
||||
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, any}
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
{ :error, rest } ->
|
||||
{ :error, { :invalid_option, rest } }
|
||||
{:error, rest} ->
|
||||
{:error, {:invalid_option, rest}}
|
||||
|
||||
translated_options ->
|
||||
# Always use the unicode option, we don't have a latin1 legacy like
|
||||
# Erlang.
|
||||
compile(source, [:unicode|translated_options], options)
|
||||
compile(source, translated_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,12 +109,9 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp compile(source, opts, doc_opts) when is_binary(source) do
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
|
||||
case :re.compile(source, re_opts) do
|
||||
{ :ok, re_pattern } ->
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
|
||||
case :re.compile(source, opts) do
|
||||
{:ok, re_pattern} ->
|
||||
{:ok, %Regex{re_pattern: re_pattern, source: source, opts: doc_opts}}
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -102,8 +123,8 @@ defmodule Regex do
|
||||
"""
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{ :ok, regex } -> regex
|
||||
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
{:ok, regex} -> regex
|
||||
{:error, {reason, at}} -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,75 +133,95 @@ defmodule Regex do
|
||||
|
||||
## 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
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.regex?(~r/foo/)
|
||||
true
|
||||
|
||||
iex> Regex.regex?(0)
|
||||
false
|
||||
|
||||
"""
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string until the first match.
|
||||
It returns a list with all captures or `nil` if no match occurred.
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
|
||||
## 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)
|
||||
|
||||
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
case :re.run(string, compiled, [{:capture, captures, return}]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
{ :match, results } -> results
|
||||
{:match, results} -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a keyword list or `nil` if no captures
|
||||
are found. Requires the regex to be compiled with the groups option.
|
||||
Returns the given captures as a map or `nil` if no captures are
|
||||
found. The option `:return` can be set to `:index` to get indexes
|
||||
back.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
|
||||
%{"foo" => "d"}
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
|
||||
%{"bar" => "d", "foo" => "b"}
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def named_captures(regex(groups: groups) = regex, string, options \\ []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
if results, do: Enum.zip(names, results) |> Enum.into(%{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
def re_pattern(regex(re_pattern: compiled)) do
|
||||
def re_pattern(%Regex{re_pattern: compiled}) do
|
||||
compiled
|
||||
end
|
||||
|
||||
@@ -189,11 +230,11 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.source(%r(foo))
|
||||
iex> Regex.source(~r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
def source(regex(source: source)) do
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
|
||||
@@ -202,25 +243,26 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
iex> Regex.opts(~r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
def opts(regex(options: options)) do
|
||||
options
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of named groups in the regex.
|
||||
Returns a list of names in the regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(%r/(?<foo>bar)/g)
|
||||
[:foo]
|
||||
iex> Regex.names(~r/(?<foo>bar)/)
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
def groups(regex(groups: groups)) do
|
||||
groups
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -229,126 +271,276 @@ defmodule Regex do
|
||||
where each entry in the primary list represents a match and each
|
||||
entry in the secondary list represents the captured contents.
|
||||
|
||||
The captured contents defaults to `:all`, which includes the whole
|
||||
regex match and each capture.
|
||||
## Options
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
|
||||
[["cd", "d"], ["ce", "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")
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
options = [{:capture, captures, return}, :global]
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
options = [{ :capture, captures, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:match -> []
|
||||
:nomatch -> []
|
||||
{ :match, results } -> results
|
||||
{:match, results} -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:parts` - when specified, splits the string into the
|
||||
given number of parts. If not specified, `:parts`
|
||||
is defaulted to `:infinity`, which will split the
|
||||
string into the maximum number of parts possible
|
||||
based on the given pattern.
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result;
|
||||
|
||||
## Examples
|
||||
iex> Regex.split(%r/-/, "a-b-c")
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c")
|
||||
["a","b","c"]
|
||||
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
|
||||
iex> Regex.split(~r/-/, "abc")
|
||||
["abc"]
|
||||
iex> Regex.split(%r//, "abc")
|
||||
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
iex> Regex.split(%r//, "abc", trim: true)
|
||||
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
"""
|
||||
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in Regex.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
parts =
|
||||
cond do
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> p
|
||||
true -> :infinity
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [return: return, parts: parts]
|
||||
opts = [return: :binary, parts: parts]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
Inside the replacement, you can either give `&` to access the
|
||||
whole regular expression or `\N`, where `N` is in integer to access
|
||||
a specific matching parens. You can also set `:global` to `false`
|
||||
if you want to replace just the first occurrence.
|
||||
The replacement can be either a string or a function. The string
|
||||
is used as a replacement for every match and it allows specific
|
||||
captures to be accessed via `\N`, where `N` is the capture. In
|
||||
case `\0` is used, the whole match is inserted.
|
||||
|
||||
When the replacement is a function, the function may have arity
|
||||
N where each argument maps to a capture, with the first argument
|
||||
being the whole match. If the function expects more arguments
|
||||
than captures found, the remaining arguments will receive `""`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.replace(%r/d/, "abc", "d")
|
||||
iex> Regex.replace(~r/d/, "abc", "d")
|
||||
"abc"
|
||||
iex> Regex.replace(%r/b/, "abc", "d")
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "d")
|
||||
"adc"
|
||||
iex> Regex.replace(%r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(%r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\0]")
|
||||
"a[b]c"
|
||||
|
||||
iex> Regex.replace(~r/a(b|d)c/, "abcadc", "[\\1]")
|
||||
"[b][d]"
|
||||
|
||||
iex> Regex.replace(~r/a(b|d)c/, "abcadc", fn _, x -> "[#{x}]" end)
|
||||
"[b][d]"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options \\ []) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [{ :return, return }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex, string, replacement, options) when is_binary(replacement) do
|
||||
do_replace(regex, string, precompile_replacement(replacement), options)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
def replace(regex, string, replacement, options) when is_function(replacement) do
|
||||
{:arity, arity} = :erlang.fun_info(replacement, :arity)
|
||||
do_replace(regex, string, {replacement, arity}, options)
|
||||
end
|
||||
|
||||
defp do_replace(%Regex{re_pattern: compiled}, string, replacement, options) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
opts = [{:capture, :all, :index}|opts]
|
||||
|
||||
case :re.run(string, compiled, opts) do
|
||||
:nomatch ->
|
||||
string
|
||||
{:match, [mlist|t]} when is_list(mlist) ->
|
||||
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
|
||||
{:match, slist} ->
|
||||
apply_list(string, replacement, [slist]) |> iodata_to_binary
|
||||
end
|
||||
end
|
||||
|
||||
defp precompile_replacement(""),
|
||||
do: []
|
||||
|
||||
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
[<<x, head :: binary>> | t]
|
||||
other ->
|
||||
[<<x>> | other]
|
||||
end
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
|
||||
{ns, rest} = pick_int(rest)
|
||||
[list_to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<x, rest :: binary>>) do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
[<<x, head :: binary>> | t]
|
||||
other ->
|
||||
[<<x>> | other]
|
||||
end
|
||||
end
|
||||
|
||||
defp pick_int(<<x, rest :: binary>>) when x in ?0..?9 do
|
||||
{found, rest} = pick_int(rest)
|
||||
{[x|found], rest}
|
||||
end
|
||||
|
||||
defp pick_int(bin) do
|
||||
{[], bin}
|
||||
end
|
||||
|
||||
defp apply_list(string, replacement, list) do
|
||||
apply_list(string, string, 0, replacement, list)
|
||||
end
|
||||
|
||||
defp apply_list(_, "", _, _, []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp apply_list(_, string, _, _, []) do
|
||||
string
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||
length = mpos - pos
|
||||
<<untouched :: [size(length), binary], rest :: binary>> = string
|
||||
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
|
||||
<<_ :: [size(length), binary], rest :: binary>> = string
|
||||
new_data = apply_replace(whole, replacement, head)
|
||||
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
|
||||
end
|
||||
|
||||
defp apply_replace(string, {fun, arity}, indexes) do
|
||||
apply(fun, get_indexes(string, indexes, arity))
|
||||
end
|
||||
|
||||
defp apply_replace(_, [bin], _) when is_binary(bin) do
|
||||
bin
|
||||
end
|
||||
|
||||
defp apply_replace(string, repl, indexes) do
|
||||
indexes = list_to_tuple(indexes)
|
||||
|
||||
for part <- repl do
|
||||
cond do
|
||||
is_binary(part) ->
|
||||
part
|
||||
part > tuple_size(indexes) ->
|
||||
""
|
||||
true ->
|
||||
get_index(string, elem(indexes, part))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_index(_string, {pos, _len}) when pos < 0 do
|
||||
""
|
||||
end
|
||||
|
||||
defp get_index(string, {pos, len}) do
|
||||
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
|
||||
res
|
||||
end
|
||||
|
||||
defp get_indexes(_string, _, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp get_indexes(string, [], arity) do
|
||||
[""|get_indexes(string, [], arity - 1)]
|
||||
end
|
||||
|
||||
defp get_indexes(string, [h|t], arity) do
|
||||
[get_index(string, h)|get_indexes(string, t, arity - 1)]
|
||||
end
|
||||
|
||||
{:ok, pattern} = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
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) }])
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) when is_binary(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, {:return, :binary}])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
@@ -365,32 +557,18 @@ defmodule Regex do
|
||||
|
||||
# Private Helpers
|
||||
|
||||
defp return_for(element) when is_binary(element), do: :binary
|
||||
defp return_for(element) when is_list(element), do: :list
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
defp translate_options(<<?g, t :: binary>>) do
|
||||
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
case :re.run(source, @groups_pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
end
|
||||
end
|
||||
defp translate_options(rest), do: {:error, rest}
|
||||
end
|
||||
|
||||
+54
-58
@@ -1,5 +1,5 @@
|
||||
defmodule Set do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
@@ -12,44 +12,34 @@ defmodule Set do
|
||||
|
||||
HashSet.new #=> creates an empty HashSet
|
||||
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
In the examples below, `set_impl` means a specific
|
||||
`Set` implementation, for example `HashSet`.
|
||||
|
||||
## Protocols
|
||||
|
||||
Sets are required to implement the `Enumerable` protocol,
|
||||
allowing one to write:
|
||||
|
||||
Enum.each(set, fn k ->
|
||||
IO.inspect k
|
||||
end)
|
||||
Sets are required to implement both `Enumerable` and `Collectable`
|
||||
protocols.
|
||||
|
||||
## Match
|
||||
|
||||
Sets are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
Sets are required to implement all operations using the match (`===`)
|
||||
operator.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
@type t :: map
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> any)) :: t
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback intersection(t, t) :: t
|
||||
defcallback member?(t, value) :: boolean
|
||||
defcallback put(t, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer
|
||||
defcallback subset?(t, t) :: boolean
|
||||
defcallback to_list(t) :: list()
|
||||
@@ -57,10 +47,14 @@ defmodule Set do
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
if is_tuple(unquote(set)) do
|
||||
elem(unquote(set), 0)
|
||||
else
|
||||
unsupported_set(unquote(set))
|
||||
case unquote(set) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Set module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -70,13 +64,14 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> Enum.sort
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 4) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> Enum.sort
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 2) |> Enum.sort
|
||||
[1, 3]
|
||||
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(set, value) do
|
||||
@@ -92,7 +87,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
iex> Set.difference(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[1]
|
||||
|
||||
"""
|
||||
@@ -104,8 +99,8 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.difference(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.delete(acc, v) }
|
||||
target2.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.delete(acc, v)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -119,9 +114,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
true
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
|
||||
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -133,18 +129,16 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.disjoint?(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, true }, fn member, acc ->
|
||||
target2.reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty set of the same type as `set`.
|
||||
"""
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
@@ -159,10 +153,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
|
||||
true
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -192,10 +186,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
|
||||
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([3,4], set_impl.new)) |> Enum.sort
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -207,8 +201,8 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, { :cont, target1.empty(set1) }, fn v, acc ->
|
||||
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -218,10 +212,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
|
||||
true
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -235,10 +229,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@@ -252,7 +246,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.size(HashSet.new([1, 2, 3]))
|
||||
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -270,10 +264,12 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
|
||||
true
|
||||
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
|
||||
|
||||
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
@@ -292,7 +288,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
|
||||
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -310,7 +306,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
iex> Set.union(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
@@ -322,17 +318,17 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.union(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.put(acc, v) }
|
||||
target2.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.put(acc, v)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_subset?(target1, target2, set1, set2) do
|
||||
target1.reduce(set1, { :cont, true }, fn member, acc ->
|
||||
target1.reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
+195
-183
@@ -81,68 +81,59 @@ defmodule Stream do
|
||||
|
||||
This module also provides many convenience functions for creating streams,
|
||||
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
|
||||
|
||||
Note the functions in this module are guaranteed to return enumerables.
|
||||
Since enumerables can have different shapes (structs, anonymous functions,
|
||||
and so on), the functions in this module may return any of those shapes
|
||||
and that it may change at any time. For example, a function that today
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
defrecord Lazy, enum: nil, funs: [], accs: [], after: [], last: nil
|
||||
defmodule Lazy do
|
||||
@moduledoc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
@compile :inline_list_funs
|
||||
|
||||
def reduce(lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fn x, [acc] ->
|
||||
{ reason, acc } = fun.(x, acc)
|
||||
{ reason, [acc] }
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc]}
|
||||
end)
|
||||
end
|
||||
|
||||
def count(_lazy) do
|
||||
{ :error, __MODULE__ }
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_lazy, _value) do
|
||||
{ :error, __MODULE__ }
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, last: last, after: after_funs], acc, fun) do
|
||||
defp do_reduce(%Lazy{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed), after_funs,
|
||||
last && { last, fun }, :lists.reverse(accs), acc)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && {done, fun}, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, [], last, accs, { command, acc }) do
|
||||
handle_reduce reduce.({ command, [acc|accs] }), [], last
|
||||
end
|
||||
|
||||
defp do_each(reduce, after_funs, last, accs, { command, acc }) do
|
||||
try do
|
||||
handle_reduce reduce.({ command, [acc|accs] }), after_funs, last
|
||||
catch
|
||||
kind, reason ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_reduce(res, after_funs, last) do
|
||||
case res do
|
||||
{ :suspended, [acc|accs], continuation } ->
|
||||
{ :suspended, acc, &do_each(continuation, after_funs, last, accs, &1) }
|
||||
{ :halted, [acc|_] } ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
{ :halted, acc }
|
||||
{ :done, [acc|_] = accs } ->
|
||||
case last do
|
||||
defp do_each(reduce, done, accs, {command, acc}) do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
{ :done, acc }
|
||||
{ last, fun } ->
|
||||
res = case last.(fun).(accs) do
|
||||
{ :cont, [acc|_] } -> { :done, acc }
|
||||
{ :halt, [acc|_] } -> { :halted, acc }
|
||||
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, &1 |> elem(1) }) }
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
res
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -166,33 +157,13 @@ defmodule Stream do
|
||||
|
||||
defmacrop cont_with_acc(f, entry, h, n, t) do
|
||||
quote do
|
||||
{ reason, [h|t] } = unquote(f).(unquote(entry), [unquote(h)|unquote(t)])
|
||||
{ reason, [h,unquote(n)|t] }
|
||||
{reason, [h|t]} = unquote(f).(unquote(entry), [unquote(h)|unquote(t)])
|
||||
{reason, [h,unquote(n)|t]}
|
||||
end
|
||||
end
|
||||
|
||||
## Transformers
|
||||
|
||||
@doc """
|
||||
Executes the given function when the stream is done, halted
|
||||
or an error happened during streaming. Useful for resource
|
||||
clean up.
|
||||
|
||||
Callbacks registered later will be executed earlier.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3]
|
||||
iex> Process.get(:done)
|
||||
true
|
||||
|
||||
"""
|
||||
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
|
||||
def unquote(:after)(Lazy[after: funs] = lazy, fun), do: lazy.after([fun|funs])
|
||||
def unquote(:after)(enum, fun), do: Lazy[enum: enum, after: [fun]]
|
||||
|
||||
@doc """
|
||||
Shortcut to `chunk(enum, n, n)`.
|
||||
"""
|
||||
@@ -216,10 +187,13 @@ defmodule Stream do
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
|
||||
[[1, 2, 3], [3, 4, 5]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
|
||||
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
@@ -228,14 +202,14 @@ defmodule Stream do
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, { [], 0 },
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
fn(f1) -> &do_chunk(&1, n, pad, f1) end
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, { buffer, count } = old, t) = acc, n, pad, f1) do
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
@@ -262,10 +236,10 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(h, { buffer, _ }, t), f1) do
|
||||
defp do_chunk_by(acc(h, {buffer, _}, t), f1) do
|
||||
cont_with_acc(f1, :lists.reverse(buffer), h, nil, t)
|
||||
end
|
||||
|
||||
@@ -296,12 +270,12 @@ defmodule Stream do
|
||||
def drop(enum, n) when n < 0 do
|
||||
n = abs(n)
|
||||
|
||||
lazy enum, { 0, [], [] }, fn(f1) ->
|
||||
lazy enum, {0, [], []}, fn(f1) ->
|
||||
fn
|
||||
entry, [h, { count, buf1, [] } | t] ->
|
||||
entry, [h, {count, buf1, []} | t] ->
|
||||
do_drop(:cont, n, entry, h, count, buf1, [], t)
|
||||
entry, [h, { count, buf1, [next|buf2] } | t] ->
|
||||
{ reason, [h|t] } = f1.(next, [h|t])
|
||||
entry, [h, {count, buf1, [next|buf2]} | t] ->
|
||||
{reason, [h|t]} = f1.(next, [h|t])
|
||||
do_drop(reason, n, entry, h, count, buf1, buf2, t)
|
||||
end
|
||||
end
|
||||
@@ -311,9 +285,9 @@ defmodule Stream do
|
||||
buf1 = [entry|buf1]
|
||||
count = count + 1
|
||||
if count == n do
|
||||
{ reason, [h, { 0, [], :lists.reverse(buf1) }|t] }
|
||||
{reason, [h, {0, [], :lists.reverse(buf1)}|t]}
|
||||
else
|
||||
{ reason, [h, { count, buf1, buf2 }|t] }
|
||||
{reason, [h, {count, buf1, buf2}|t]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -373,7 +347,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec flat_map(Enumerable.t, (element -> Enumerable.t)) :: Enumerable.t
|
||||
def flat_map(enum, mapper) do
|
||||
transform(enum, nil, fn val, nil -> { mapper.(val), nil } end)
|
||||
transform(enum, nil, fn val, nil -> {mapper.(val), nil} end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -410,6 +384,44 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
This function is often used with `run/1` since any evaluation
|
||||
is delayed until the stream is executed. See `run/1` for an example.
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t) :: Enumerable.t
|
||||
def into(enum, collectable, transform \\ fn x -> x end) do
|
||||
&do_into(enum, collectable, transform, &1, &2)
|
||||
end
|
||||
|
||||
defp do_into(enum, collectable, transform, acc, fun) do
|
||||
{initial, into} = Collectable.into(collectable)
|
||||
composed = fn x, [acc|collectable] ->
|
||||
collectable = into.(collectable, {:cont, transform.(x)})
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc|collectable]}
|
||||
end
|
||||
do_into(&Enumerable.reduce(enum, &1, composed), initial, into, acc)
|
||||
end
|
||||
|
||||
defp do_into(reduce, collectable, into, {command, acc}) do
|
||||
try do
|
||||
reduce.({command, [acc|collectable]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
into.(collectable, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:suspended, [acc|collectable], continuation} ->
|
||||
{:suspended, acc, &do_into(continuation, collectable, into, &1)}
|
||||
{reason, [acc|collectable]} ->
|
||||
into.(collectable, :done)
|
||||
{reason, acc}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on
|
||||
enumeration.
|
||||
@@ -455,14 +467,14 @@ defmodule Stream do
|
||||
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> File.stream_to!("new")
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
"""
|
||||
@spec run(Enumerable.t) :: :ok
|
||||
def run(stream) do
|
||||
Enumerable.reduce(stream, { :cont, nil }, fn(_, _) -> { :cont, nil } end)
|
||||
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -526,7 +538,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: Lazy[enum: []]
|
||||
def take(_enum, 0), do: %Lazy{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
@@ -537,15 +549,15 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_take(enum, n, acc, f) do
|
||||
{ _, { _count, buf1, buf2 } } =
|
||||
Enumerable.reduce(enum, { :cont, { 0, [], [] } }, fn
|
||||
entry, { count, buf1, buf2 } ->
|
||||
{_, {_count, buf1, buf2}} =
|
||||
Enumerable.reduce(enum, {:cont, {0, [], []}}, fn
|
||||
entry, {count, buf1, buf2} ->
|
||||
buf1 = [entry|buf1]
|
||||
count = count + 1
|
||||
if count == n do
|
||||
{ :cont, { 0, [], buf1 } }
|
||||
{:cont, {0, [], buf1}}
|
||||
else
|
||||
{ :cont, { count, buf1, buf2 } }
|
||||
{:cont, {count, buf1, buf2}}
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -578,7 +590,7 @@ defmodule Stream do
|
||||
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
|
||||
end
|
||||
|
||||
def take_every(_enum, 0), do: Lazy[enum: []]
|
||||
def take_every(_enum, 0), do: %Lazy{enum: []}
|
||||
|
||||
@doc """
|
||||
Lazily takes elements of the enumerable while the given
|
||||
@@ -596,7 +608,6 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -618,14 +629,14 @@ defmodule Stream do
|
||||
iex> enum = 1..100
|
||||
iex> n = 3
|
||||
iex> stream = Stream.transform(enum, 0, fn i, acc ->
|
||||
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
...> if acc < n, do: {[i], acc + 1}, else: {:halt, acc}
|
||||
...> end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec transform(Enumerable.t, acc, fun) :: Enumerable.t when
|
||||
fun: (element, acc -> { Enumerable.t, acc } | { :halt, acc }),
|
||||
fun: (element, acc -> {Enumerable.t, acc} | {:halt, acc}),
|
||||
acc: any
|
||||
def transform(enum, acc, reducer) do
|
||||
&do_transform(enum, acc, reducer, &1, &2)
|
||||
@@ -639,27 +650,27 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do
|
||||
case next.({ :cont, next_acc }) do
|
||||
{ :suspended, [val|next_acc], next } ->
|
||||
case next.({:cont, next_acc}) do
|
||||
{:suspended, [val|next_acc], next} ->
|
||||
try do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ [], user_acc } ->
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
{ list, user_acc } when is_list(list) ->
|
||||
{list, user_acc} when is_list(list) ->
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
|
||||
{ :halt, _user_acc } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, elem(inner_acc, 1) }
|
||||
{ other, user_acc } ->
|
||||
{:halt, _user_acc} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, elem(inner_acc, 1)}
|
||||
{other, user_acc} ->
|
||||
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
|
||||
end
|
||||
{ reason, _ } ->
|
||||
{ reason, elem(inner_acc, 1) }
|
||||
{reason, _} ->
|
||||
{reason, elem(inner_acc, 1)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -668,16 +679,16 @@ defmodule Stream do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ :done, acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
|
||||
{ :halted, acc } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, acc }
|
||||
{ :suspended, acc, c } ->
|
||||
{ :suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
|
||||
{:done, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:halted, acc} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, acc}
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -685,29 +696,29 @@ defmodule Stream do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
{ :stream_transform, h } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, h }
|
||||
{:stream_transform, h} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, h}
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ _, acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
|
||||
{ :suspended, acc, c } ->
|
||||
{ :suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
|
||||
{_, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_each(x, acc, f) do
|
||||
case f.(x, acc) do
|
||||
{ :halt, h } -> throw({ :stream_transform, h })
|
||||
{ _, _ } = o -> o
|
||||
{:halt, h} -> throw({:stream_transform, h})
|
||||
{_, _} = o -> o
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_step(x, acc) do
|
||||
{ :suspend, [x|acc] }
|
||||
{:suspend, [x|acc]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -807,19 +818,19 @@ defmodule Stream do
|
||||
right_fun = &Enumerable.reduce(right, &1, step)
|
||||
|
||||
# Return a function as a lazy enumerator.
|
||||
&do_zip([{ left_fun, [] }, { right_fun, [] }], &1, &2)
|
||||
&do_zip([{left_fun, []}, {right_fun, []}], &1, &2)
|
||||
end
|
||||
|
||||
defp do_zip(zips, { :halt, acc }, _fun) do
|
||||
defp do_zip(zips, {:halt, acc}, _fun) do
|
||||
do_zip_close(zips)
|
||||
{ :halted, acc }
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_zip(zips, { :suspend, acc }, fun) do
|
||||
{ :suspended, acc, &do_zip(zips, &1, fun) }
|
||||
defp do_zip(zips, {:suspend, acc}, fun) do
|
||||
{:suspended, acc, &do_zip(zips, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_zip(zips, { :cont, acc }, callback) do
|
||||
defp do_zip(zips, {:cont, acc}, callback) do
|
||||
try do
|
||||
do_zip(zips, acc, callback, [], [])
|
||||
catch
|
||||
@@ -827,36 +838,36 @@ defmodule Stream do
|
||||
do_zip_close(zips)
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ :next, buffer, acc } ->
|
||||
{:next, buffer, acc} ->
|
||||
do_zip(buffer, acc, callback)
|
||||
{ :done, _ } = o ->
|
||||
{:done, _} = o ->
|
||||
o
|
||||
end
|
||||
end
|
||||
|
||||
defp do_zip([{ fun, fun_acc }|t], acc, callback, list, buffer) do
|
||||
case fun.({ :cont, fun_acc }) do
|
||||
{ :suspended, [i|fun_acc], fun } ->
|
||||
do_zip(t, acc, callback, [i|list], [{ fun, fun_acc }|buffer])
|
||||
{ _, _ } ->
|
||||
defp do_zip([{fun, fun_acc}|t], acc, callback, list, buffer) do
|
||||
case fun.({:cont, fun_acc}) do
|
||||
{:suspended, [i|fun_acc], fun} ->
|
||||
do_zip(t, acc, callback, [i|list], [{fun, fun_acc}|buffer])
|
||||
{_, _} ->
|
||||
do_zip_close(:lists.reverse(buffer) ++ t)
|
||||
{ :done, acc }
|
||||
{:done, acc}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_zip([], acc, callback, list, buffer) do
|
||||
zipped = list_to_tuple(:lists.reverse(list))
|
||||
{ :next, :lists.reverse(buffer), callback.(zipped, acc) }
|
||||
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
|
||||
end
|
||||
|
||||
defp do_zip_close([]), do: :ok
|
||||
defp do_zip_close([{ fun, acc }|t]) do
|
||||
fun.({ :halt, acc })
|
||||
defp do_zip_close([{fun, acc}|t]) do
|
||||
fun.({:halt, acc})
|
||||
do_zip_close(t)
|
||||
end
|
||||
|
||||
defp do_zip_step(x, acc) do
|
||||
{ :suspend, [x|acc] }
|
||||
{:suspend, [x|acc]}
|
||||
end
|
||||
|
||||
## Sources
|
||||
@@ -880,22 +891,22 @@ defmodule Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_cycle(_reduce, _cycle, { :halt, acc }) do
|
||||
{ :halted, acc }
|
||||
defp do_cycle(_reduce, _cycle, {:halt, acc}) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_cycle(reduce, cycle, { :suspend, acc }) do
|
||||
{ :suspended, acc, &do_cycle(reduce, cycle, &1) }
|
||||
defp do_cycle(reduce, cycle, {:suspend, acc}) do
|
||||
{:suspended, acc, &do_cycle(reduce, cycle, &1)}
|
||||
end
|
||||
|
||||
defp do_cycle(reduce, cycle, acc) do
|
||||
case reduce.(acc) do
|
||||
{ :done, acc } ->
|
||||
do_cycle(cycle, cycle, { :cont, acc })
|
||||
{ :halted, acc } ->
|
||||
{ :halted, acc }
|
||||
{ :suspended, acc, continuation } ->
|
||||
{ :suspended, acc, &do_cycle(continuation, cycle, &1) }
|
||||
{:done, acc} ->
|
||||
do_cycle(cycle, cycle, {:cont, acc})
|
||||
{:halted, acc} ->
|
||||
{:halted, acc}
|
||||
{:suspended, acc, continuation} ->
|
||||
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -911,12 +922,12 @@ defmodule Stream do
|
||||
"""
|
||||
@spec iterate(element, (element -> element)) :: Enumerable.t
|
||||
def iterate(start_value, next_fun) do
|
||||
unfold({ :ok, start_value}, fn
|
||||
{ :ok, value } ->
|
||||
{ value, { :next, value } }
|
||||
{ :next, value } ->
|
||||
unfold({:ok, start_value}, fn
|
||||
{:ok, value} ->
|
||||
{value, {:next, value}}
|
||||
{:next, value} ->
|
||||
next = next_fun.(value)
|
||||
{ next, { :next, next } }
|
||||
{next, {:next, next}}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -934,15 +945,15 @@ defmodule Stream do
|
||||
&do_repeatedly(generator_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, { :suspend, acc }, fun) do
|
||||
{ :suspended, acc, &do_repeatedly(generator_fun, &1, fun) }
|
||||
defp do_repeatedly(generator_fun, {:suspend, acc}, fun) do
|
||||
{:suspended, acc, &do_repeatedly(generator_fun, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_repeatedly(_generator_fun, { :halt, acc }, _fun) do
|
||||
{ :halted, acc }
|
||||
defp do_repeatedly(_generator_fun, {:halt, acc}, _fun) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, { :cont, acc }, fun) do
|
||||
defp do_repeatedly(generator_fun, {:cont, acc}, fun) do
|
||||
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
|
||||
end
|
||||
|
||||
@@ -966,32 +977,32 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> { data, file }
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
|
||||
"""
|
||||
@spec resource((() -> acc), (acc -> { element, acc } | nil), (acc -> term)) :: Enumerable.t
|
||||
@spec resource((() -> acc), (acc -> {element, acc} | nil), (acc -> term)) :: Enumerable.t
|
||||
def resource(start_fun, next_fun, after_fun) do
|
||||
&do_resource(start_fun.(), next_fun, &1, &2, after_fun)
|
||||
end
|
||||
|
||||
defp do_resource(next_acc, next_fun, { :suspend, acc }, fun, after_fun) do
|
||||
{ :suspended, acc, &do_resource(next_acc, next_fun, &1, fun, after_fun) }
|
||||
defp do_resource(next_acc, next_fun, {:suspend, acc}, fun, after_fun) do
|
||||
{:suspended, acc, &do_resource(next_acc, next_fun, &1, fun, after_fun)}
|
||||
end
|
||||
|
||||
defp do_resource(next_acc, _next_fun, { :halt, acc }, _fun, after_fun) do
|
||||
defp do_resource(next_acc, _next_fun, {:halt, acc}, _fun, after_fun) do
|
||||
after_fun.(next_acc)
|
||||
{ :halted, acc }
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_resource(next_acc, next_fun, { :cont, acc }, fun, after_fun) do
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{ v, next_acc } -> { fun.(v, acc), next_acc }
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -1000,8 +1011,8 @@ defmodule Stream do
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{ :done, acc }
|
||||
{ acc, next_acc } ->
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
end
|
||||
end
|
||||
@@ -1017,43 +1028,44 @@ defmodule Stream do
|
||||
|
||||
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
|
||||
[5, 4, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec unfold(acc, (acc -> { element, acc } | nil)) :: Enumerable.t
|
||||
@spec unfold(acc, (acc -> {element, acc} | nil)) :: Enumerable.t
|
||||
def unfold(next_acc, next_fun) do
|
||||
&do_unfold(next_acc, next_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_unfold(next_acc, next_fun, { :suspend, acc }, fun) do
|
||||
{ :suspended, acc, &do_unfold(next_acc, next_fun, &1, fun) }
|
||||
defp do_unfold(next_acc, next_fun, {:suspend, acc}, fun) do
|
||||
{:suspended, acc, &do_unfold(next_acc, next_fun, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_unfold(_next_acc, _next_fun, { :halt, acc }, _fun) do
|
||||
{ :halted, acc }
|
||||
defp do_unfold(_next_acc, _next_fun, {:halt, acc}, _fun) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_unfold(next_acc, next_fun, { :cont, acc }, fun) do
|
||||
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
|
||||
case next_fun.(next_acc) do
|
||||
nil -> { :done, acc }
|
||||
{ v, next_acc } -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
|
||||
nil -> {:done, acc}
|
||||
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile { :inline, lazy: 2, lazy: 3, lazy: 4 }
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(Lazy[funs: funs] = lazy, fun),
|
||||
do: lazy.funs([fun|funs])
|
||||
defp lazy(%Lazy{funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: Lazy[enum: enum, funs: [fun]]
|
||||
do: %Lazy{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(Lazy[funs: funs, accs: accs] = lazy, acc, fun),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs])
|
||||
defp lazy(%Lazy{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc]]
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc]}
|
||||
|
||||
defp lazy(Lazy[last: nil, funs: funs, accs: accs] = lazy, acc, fun, last),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs]).last(last)
|
||||
defp lazy(enum, acc, fun, last),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc], last: last]
|
||||
defp lazy(%Lazy{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs], done: done}
|
||||
defp lazy(enum, acc, fun, done),
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc], done: done}
|
||||
end
|
||||
|
||||
@@ -4,22 +4,22 @@ defmodule Stream.Reducers do
|
||||
|
||||
defmacro chunk(n, step, limit, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, { buffer, count }, t) ->
|
||||
fn entry, acc(h, {buffer, count}, t) ->
|
||||
buffer = [entry|buffer]
|
||||
count = count + 1
|
||||
|
||||
new =
|
||||
if count >= unquote(limit) do
|
||||
left = count - unquote(step)
|
||||
{ Enum.take(buffer, left), left }
|
||||
{Enum.take(buffer, left), left}
|
||||
else
|
||||
{ buffer, count }
|
||||
{buffer, count}
|
||||
end
|
||||
|
||||
if count == unquote(n) do
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, new, t)
|
||||
else
|
||||
{ :cont, acc(h, new, t) }
|
||||
{:cont, acc(h, new, t)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -28,15 +28,15 @@ defmodule Stream.Reducers do
|
||||
defmacro chunk_by(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, { buffer, value }, t) ->
|
||||
entry, acc(h, {buffer, value}, t) ->
|
||||
new_value = unquote(callback).(entry)
|
||||
if new_value == value do
|
||||
{ :cont, acc(h, { [entry|buffer], value }, t) }
|
||||
{:cont, acc(h, {[entry|buffer], value}, t)}
|
||||
else
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, { [entry], new_value }, t)
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, {[entry], new_value}, t)
|
||||
end
|
||||
entry, acc(h, nil, t) ->
|
||||
{ :cont, acc(h, { [entry], unquote(callback).(entry) }, t) }
|
||||
{:cont, acc(h, {[entry], unquote(callback).(entry)}, t)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,7 +45,7 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn
|
||||
_entry, acc(h, n, t) when n > 0 ->
|
||||
{ :cont, acc(h, n-1, t) }
|
||||
{:cont, acc(h, n-1, t)}
|
||||
entry, acc(h, n, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, n, t)
|
||||
end
|
||||
@@ -56,7 +56,7 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn entry, acc(h, bool, t) = orig ->
|
||||
if bool and unquote(callback).(entry) do
|
||||
{ :cont, orig }
|
||||
{:cont, orig}
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, false, t)
|
||||
end
|
||||
@@ -70,7 +70,7 @@ defmodule Stream.Reducers do
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -82,7 +82,7 @@ defmodule Stream.Reducers do
|
||||
if unquote(filter).(entry) do
|
||||
cont(unquote(f), unquote(mapper).(entry), acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -102,7 +102,7 @@ defmodule Stream.Reducers do
|
||||
unless unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -112,10 +112,10 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, :first, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, { :ok, entry }, t)
|
||||
entry, acc(h, { :ok, acc }, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, {:ok, entry}, t)
|
||||
entry, acc(h, {:ok, acc}, t) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
|
||||
cont_with_acc(unquote(f), value, h, {:ok, value}, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -135,7 +135,7 @@ defmodule Stream.Reducers do
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{ :halt, orig }
|
||||
{:halt, orig}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -148,7 +148,7 @@ defmodule Stream.Reducers do
|
||||
when n === unquote(nth) ->
|
||||
cont_with_acc(unquote(f), entry, h, 1, t)
|
||||
entry, acc(h, n, t) ->
|
||||
{ :cont, acc(h, n+1, t) }
|
||||
{:cont, acc(h, n+1, t)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -159,7 +159,7 @@ defmodule Stream.Reducers do
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :halt, acc }
|
||||
{:halt, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -170,7 +170,7 @@ defmodule Stream.Reducers do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
if :lists.member(value, prev) do
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, [value|prev], t)
|
||||
end
|
||||
@@ -181,7 +181,7 @@ defmodule Stream.Reducers do
|
||||
defmacro with_index(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, counter, t)) ->
|
||||
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
|
||||
cont_with_acc(unquote(f), {entry, counter}, h, counter + 1, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+233
-138
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [length: 1]
|
||||
|
||||
defmodule String do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
@@ -47,7 +47,7 @@ defmodule String do
|
||||
by the letter "e" followed by the accent ́:
|
||||
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
iex> byte_size(string)
|
||||
3
|
||||
iex> String.length(string)
|
||||
1
|
||||
@@ -83,13 +83,14 @@ defmodule String do
|
||||
|
||||
iex> ?j
|
||||
106
|
||||
|
||||
iex> ?é
|
||||
233
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
...> eacute
|
||||
iex> eacute
|
||||
233
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
@@ -159,8 +160,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> String.split("foo" <> <<194, 133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
|
||||
@@ -168,13 +171,14 @@ defmodule String do
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
default, unless the `global` option is set to `false`.
|
||||
default, but can be controlled via the `parts: num` option.
|
||||
If you pass `parts: :infinity`, it will return all possible parts.
|
||||
|
||||
Empty strings are only removed from the result if the
|
||||
`trim` option is set to `true`.
|
||||
@@ -185,8 +189,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
|
||||
iex> String.split("a,b,c", ",", parts: 2)
|
||||
["a", "b,c"]
|
||||
|
||||
iex> String.split(" a b c ", " ", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
@@ -197,22 +203,27 @@ defmodule String do
|
||||
|
||||
A regular expression:
|
||||
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
iex> String.split("a,b,c", ~r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
|
||||
iex> String.split("a,b,c", ~r{,}, parts: 2)
|
||||
["a", "b,c"]
|
||||
iex> String.split(" a b c ", %r{\s}, trim: true)
|
||||
|
||||
iex> String.split(" a b c ", ~r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty patterns returns codepoints:
|
||||
|
||||
iex> String.split("abc", %r{})
|
||||
iex> String.split("abc", ~r{})
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
iex> String.split("abc", "", global: false)
|
||||
|
||||
iex> String.split("abc", "", parts: 2)
|
||||
["a", "bc"]
|
||||
|
||||
"""
|
||||
@@ -222,23 +233,93 @@ defmodule String do
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, %r"", options)
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
end
|
||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
else
|
||||
splits
|
||||
splits = case options[:parts] do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
num ->
|
||||
if options[:global] != false or num == 0 do
|
||||
:binary.split(binary, pattern, [:global])
|
||||
else
|
||||
:binary.split(binary, pattern, [])
|
||||
end
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp split_parts("", _pattern, _num), do: [""]
|
||||
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
[head|tail] = :binary.split(binary, pattern)
|
||||
case tail do
|
||||
[] -> Enum.reverse([head|parts])
|
||||
[str] -> split_parts(str, pattern, num - 1, [head|parts])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a string into two at the specified offset. When the offset given is
|
||||
negative, location is counted from the end of the string.
|
||||
|
||||
The offset is capped to the length of the string.
|
||||
|
||||
Returns a tuple with two elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.split_at "sweetelixir", 5
|
||||
{"sweet", "elixir"}
|
||||
|
||||
iex> String.split_at "sweetelixir", -6
|
||||
{"sweet", "elixir"}
|
||||
|
||||
iex> String.split_at "abc", 0
|
||||
{"", "abc"}
|
||||
|
||||
iex> String.split_at "abc", 1000
|
||||
{"abc", ""}
|
||||
|
||||
iex> String.split_at "abc", -1000
|
||||
{"", "abc"}
|
||||
|
||||
"""
|
||||
@spec split_at(t, integer) :: {t, t}
|
||||
def split_at(string, offset)
|
||||
|
||||
def split_at(binary, index) when index == 0, do:
|
||||
{"", binary}
|
||||
|
||||
def split_at(binary, index) when index > 0, do:
|
||||
do_split_at(next_grapheme(binary), 0, index, "")
|
||||
|
||||
def split_at(binary, index) when index < 0, do:
|
||||
do_split_at(next_grapheme(binary), 0, max(0, byte_size(binary)+index), "")
|
||||
|
||||
defp do_split_at(nil, _, _, acc), do:
|
||||
{acc, ""}
|
||||
|
||||
defp do_split_at({grapheme, rest}, current_pos, target_pos, acc) when current_pos < target_pos, do:
|
||||
do_split_at(next_grapheme(rest), current_pos+1, target_pos, acc <> grapheme)
|
||||
|
||||
defp do_split_at({grapheme, rest}, pos, pos, acc), do:
|
||||
{acc, grapheme <> rest}
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to uppercase.
|
||||
|
||||
@@ -246,8 +327,10 @@ defmodule String do
|
||||
|
||||
iex> String.upcase("abcd")
|
||||
"ABCD"
|
||||
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
|
||||
@@ -262,8 +345,10 @@ defmodule String do
|
||||
|
||||
iex> String.downcase("ABCD")
|
||||
"abcd"
|
||||
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
|
||||
@@ -284,15 +369,17 @@ defmodule String do
|
||||
|
||||
iex> String.capitalize("abcd")
|
||||
"Abcd"
|
||||
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
def capitalize(string) when is_binary(string) do
|
||||
{ char, rest } = String.Unicode.titlecase_once(string)
|
||||
{char, rest} = String.Unicode.titlecase_once(string)
|
||||
char <> downcase(rest)
|
||||
end
|
||||
|
||||
@@ -333,7 +420,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
def rstrip(string, char) do
|
||||
def rstrip(string, char) when is_integer(char) do
|
||||
do_rstrip(string, "", char)
|
||||
end
|
||||
|
||||
@@ -373,11 +460,11 @@ defmodule String do
|
||||
|
||||
@spec lstrip(t, char) :: t
|
||||
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) do
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
end
|
||||
|
||||
def lstrip(other, _char) do
|
||||
def lstrip(other, char) when is_integer(char) do
|
||||
other
|
||||
end
|
||||
|
||||
@@ -413,7 +500,7 @@ defmodule String do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new string of length `len` with `subject` right justified and
|
||||
padded with `padding`. If `padding` is not present, it defaults to
|
||||
whitespace. When `len` is less than the length of `subject`, `subject` is
|
||||
@@ -423,6 +510,7 @@ defmodule String do
|
||||
|
||||
iex> String.rjust("abc", 5)
|
||||
" abc"
|
||||
|
||||
iex> String.rjust("abc", 5, ?-)
|
||||
"--abc"
|
||||
|
||||
@@ -438,7 +526,7 @@ defmodule String do
|
||||
do_justify(subject, len, padding, :right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new string of length `len` with `subject` left justified and padded
|
||||
with `padding`. If `padding` is not present, it defaults to whitespace. When
|
||||
`len` is less than the length of `subject`, `subject` is returned.
|
||||
@@ -447,6 +535,7 @@ defmodule String do
|
||||
|
||||
iex> String.ljust("abc", 5)
|
||||
"abc "
|
||||
|
||||
iex> String.ljust("abc", 5, ?-)
|
||||
"abc--"
|
||||
|
||||
@@ -482,7 +571,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` by `replacement`. By default, it replaces
|
||||
all entries, except if the `global` option is set to `false`.
|
||||
@@ -493,13 +582,14 @@ defmodule String do
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-")
|
||||
"a-b-c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
|
||||
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
|
||||
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
|
||||
Notice we had to escape the escape character `\`. By giving `&`,
|
||||
@@ -510,8 +600,10 @@ defmodule String do
|
||||
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
@@ -519,21 +611,19 @@ defmodule String do
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options \\ [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
end
|
||||
|
||||
def replace(subject, pattern, replacement, options) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
else
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
if insert = Keyword.get(options, :insert_replaced) do
|
||||
opts = [{:insert_replaced, insert}|opts]
|
||||
end
|
||||
|
||||
@@ -547,8 +637,10 @@ defmodule String do
|
||||
|
||||
iex> String.reverse("abcd")
|
||||
"dcba"
|
||||
|
||||
iex> String.reverse("hello world")
|
||||
"dlrow olleh"
|
||||
|
||||
iex> String.reverse("hello ∂og")
|
||||
"go∂ olleh"
|
||||
|
||||
@@ -562,7 +654,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -571,8 +663,10 @@ defmodule String do
|
||||
|
||||
iex> String.duplicate("abc", 0)
|
||||
""
|
||||
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
|
||||
iex> String.duplicate("abc", 2)
|
||||
"abcabc"
|
||||
|
||||
@@ -589,8 +683,10 @@ defmodule String do
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
|
||||
iex> String.codepoints("ἅἪῼ")
|
||||
["ἅ","Ἢ","ῼ"]
|
||||
|
||||
@@ -613,24 +709,27 @@ defmodule String do
|
||||
## Examples
|
||||
|
||||
iex> String.next_codepoint("josé")
|
||||
{ "j", "osé" }
|
||||
{"j", "osé"}
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@compile {:inline, next_codepoint: 1}
|
||||
@spec next_codepoint(t) :: {codepoint, t} | nil
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
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
|
||||
|
||||
@@ -643,7 +742,7 @@ defmodule String do
|
||||
?\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
|
||||
for noncharacter <- noncharacters do
|
||||
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
|
||||
end
|
||||
|
||||
@@ -651,7 +750,7 @@ defmodule String do
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Checks whether `str` is a valid character.
|
||||
|
||||
All characters are codepoints, but some codepoints
|
||||
@@ -664,8 +763,10 @@ defmodule String do
|
||||
|
||||
iex> String.valid_character?("a")
|
||||
true
|
||||
|
||||
iex> String.valid_character?("ø")
|
||||
true
|
||||
|
||||
iex> String.valid_character?("\x{ffff}")
|
||||
false
|
||||
|
||||
@@ -699,11 +800,11 @@ defmodule String do
|
||||
## Examples
|
||||
|
||||
iex> String.next_grapheme("josé")
|
||||
{ "j", "osé" }
|
||||
{"j", "osé"}
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | nil
|
||||
@compile {:inline, next_grapheme: 1}
|
||||
@spec next_grapheme(t) :: {grapheme, t} | nil
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
@@ -714,6 +815,7 @@ defmodule String do
|
||||
|
||||
iex> String.first("elixir")
|
||||
"e"
|
||||
|
||||
iex> String.first("եոգլի")
|
||||
"ե"
|
||||
|
||||
@@ -721,7 +823,7 @@ defmodule String do
|
||||
@spec first(t) :: grapheme | nil
|
||||
def first(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
{char, _} -> char
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
@@ -734,6 +836,7 @@ defmodule String do
|
||||
|
||||
iex> String.last("elixir")
|
||||
"r"
|
||||
|
||||
iex> String.last("եոգլի")
|
||||
"ի"
|
||||
|
||||
@@ -756,6 +859,7 @@ defmodule String do
|
||||
|
||||
iex> String.length("elixir")
|
||||
6
|
||||
|
||||
iex> String.length("եոգլի")
|
||||
5
|
||||
|
||||
@@ -779,12 +883,16 @@ defmodule String do
|
||||
|
||||
iex> String.at("elixir", 0)
|
||||
"e"
|
||||
|
||||
iex> String.at("elixir", 1)
|
||||
"l"
|
||||
|
||||
iex> String.at("elixir", 10)
|
||||
nil
|
||||
|
||||
iex> String.at("elixir", -1)
|
||||
"r"
|
||||
|
||||
iex> String.at("elixir", -10)
|
||||
nil
|
||||
|
||||
@@ -822,18 +930,25 @@ defmodule String do
|
||||
|
||||
iex> String.slice("elixir", 1, 3)
|
||||
"lix"
|
||||
|
||||
iex> String.slice("elixir", 1, 10)
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
|
||||
iex> String.slice("a", 1, 1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
|
||||
@@ -870,26 +985,34 @@ defmodule String do
|
||||
|
||||
iex> String.slice("elixir", 1..3)
|
||||
"lix"
|
||||
|
||||
iex> String.slice("elixir", 1..10)
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", 2..-1)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -4..6)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
|
||||
@@ -913,7 +1036,7 @@ defmodule String do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first > 0 do
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
end
|
||||
end
|
||||
@@ -953,8 +1076,10 @@ defmodule String do
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
true
|
||||
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
true
|
||||
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
false
|
||||
|
||||
@@ -989,8 +1114,10 @@ defmodule String do
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
true
|
||||
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
true
|
||||
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
false
|
||||
|
||||
@@ -1025,9 +1152,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.match?("foo", %r/foo/)
|
||||
iex> String.match?("foo", ~r/foo/)
|
||||
true
|
||||
iex> String.match?("bar", %r/foo/)
|
||||
|
||||
iex> String.match?("bar", ~r/foo/)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -1045,8 +1173,10 @@ defmodule String do
|
||||
|
||||
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
|
||||
|
||||
@@ -1073,126 +1203,91 @@ defmodule String do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
@doc false
|
||||
def to_char_list(list) do
|
||||
IO.write :stderr, "String.to_char_list/1 is deprecated, please use List.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data(list)
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
@doc false
|
||||
def to_char_list!(list) do
|
||||
IO.write :stderr, "String.to_char_list!/1 is deprecated, please use List.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data!(list)
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
@doc false
|
||||
def from_char_list(list) do
|
||||
IO.write :stderr, "String.from_char_list/1 is deprecated, please use String.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_list!(list) do
|
||||
IO.write :stderr, "String.from_char_list!/1 is deprecated, please use String.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data!(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string into a char list converting each codepoint to its
|
||||
respective integer value.
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_char_list("æß")
|
||||
{ :ok, 'æß' }
|
||||
iex> String.to_char_list("abc")
|
||||
{ :ok, 'abc' }
|
||||
iex> String.from_char_data([0x00E6, 0x00DF])
|
||||
{:ok, "æß"}
|
||||
|
||||
iex> String.from_char_data([0x0061, "bc"])
|
||||
{:ok, "abc"}
|
||||
|
||||
"""
|
||||
@spec to_char_list(String.t) :: { :ok, char_list } | { :error, list, binary } | { :incomplete, list, binary }
|
||||
def to_char_list(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
{ :ok, result }
|
||||
|
||||
{ :error, _, _ } = error ->
|
||||
error
|
||||
|
||||
{ :incomplete, _, _ } = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
|
||||
def from_char_data(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string into a char list converting each codepoint to its
|
||||
respective integer value.
|
||||
|
||||
In case the conversion fails or is incomplete,
|
||||
it raises a `String.UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_char_list!("æß")
|
||||
'æß'
|
||||
iex> String.to_char_list!("abc")
|
||||
'abc'
|
||||
|
||||
"""
|
||||
@spec to_char_list!(String.t) :: char_list | no_return
|
||||
def to_char_list!(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{ :error, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{ :incomplete, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integer codepoints to a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_list([0x00E6, 0x00DF])
|
||||
{ :ok, "æß" }
|
||||
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
|
||||
{ :ok, "abc" }
|
||||
|
||||
"""
|
||||
@spec from_char_list(char_list) :: { :ok, String.t } | { :error, binary, binary } | { :incomplete, binary, binary }
|
||||
def from_char_list(list) when is_list(list) do
|
||||
def from_char_data(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
{ :ok, result }
|
||||
{:ok, result}
|
||||
|
||||
{ :error, _, _ } = error ->
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{ :incomplete, _, _ } = incomplete ->
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integer codepoints to a string.
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
In case the conversion fails, it raises a `String.UnicodeConversionError`.
|
||||
In case the conversion fails, it raises a `UnicodeConversionError`.
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_list!([0x00E6, 0x00DF])
|
||||
iex> String.from_char_data!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
|
||||
|
||||
iex> String.from_char_data!([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec from_char_list!(char_list) :: String.t | no_return
|
||||
def from_char_list!(list) when is_list(list) do
|
||||
@spec from_char_data!(char_data) :: String.t | no_return
|
||||
def from_char_data!(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data!(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{ :error, encoded, rest } ->
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{ :incomplete, encoded, rest } ->
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defprotocol String.Chars do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
The String.Chars protocol is responsible for
|
||||
converting a structure to a Binary (only if applicable).
|
||||
The only function required to be implemented is
|
||||
@@ -58,11 +58,12 @@ defimpl String.Chars, for: List do
|
||||
|
||||
iex> to_string('foo')
|
||||
"foo"
|
||||
|
||||
iex> to_string(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_string(char_list), do: String.from_char_list!(char_list)
|
||||
def to_string(char_list), do: String.from_char_data!(char_list)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Integer do
|
||||
@@ -79,6 +80,6 @@ defimpl String.Chars, for: Float do
|
||||
Simply converts the float to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
defmodule StringIO do
|
||||
@moduledoc """
|
||||
This module provides an IO device that wraps a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, pid} = StringIO.open("foo")
|
||||
iex> IO.read(pid, 2)
|
||||
"fo"
|
||||
|
||||
"""
|
||||
|
||||
use GenServer.Behaviour
|
||||
|
||||
@doc """
|
||||
Creates an IO device.
|
||||
|
||||
If the `:capture_prompt` option is set to `true`,
|
||||
prompts (specified as arguments to `IO.get*` functions)
|
||||
are captured.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, pid} = StringIO.open("foo")
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{"", ""}
|
||||
|
||||
iex> {:ok, pid} = StringIO.open("foo", capture_prompt: true)
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{"", ">"}
|
||||
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns current buffers.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, pid} = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.contents(pid)
|
||||
{"in", "out"}
|
||||
|
||||
"""
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the IO device and returns remaining buffers.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, pid} = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.close(pid)
|
||||
{:ok, {"in", "out"}}
|
||||
|
||||
"""
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
def init({string, options}) do
|
||||
capture_prompt = options[:capture_prompt] || false
|
||||
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
|
||||
end
|
||||
|
||||
def handle_info({:io_request, from, reply_as, req}, s) do
|
||||
s = io_request(from, reply_as, req, s)
|
||||
{:noreply, s}
|
||||
end
|
||||
|
||||
def handle_info(msg, s) do
|
||||
super(msg, s)
|
||||
end
|
||||
|
||||
def handle_call(:contents, _from, %{input: input, output: output} = s) do
|
||||
{:reply, {input, output}, s}
|
||||
end
|
||||
|
||||
def handle_call(:close, _from, %{input: input, output: output} = s) do
|
||||
{:stop, :normal, {:ok, {input, output}}, s}
|
||||
end
|
||||
|
||||
def handle_call(request, from, s) do
|
||||
super(request, from, s)
|
||||
end
|
||||
|
||||
defp io_request(from, reply_as, req, s) do
|
||||
{reply, s} = io_request(req, s)
|
||||
io_reply(from, reply_as, to_reply(reply))
|
||||
s
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, chars}, %{output: output} = s) do
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
|
||||
chars = apply(m, f, as)
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, _encoding, chars}, s) do
|
||||
io_request({:put_chars, chars}, s)
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, _encoding, mod, func, args}, s) do
|
||||
io_request({:put_chars, mod, func, args}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_chars, prompt, n}, s) when n >= 0 do
|
||||
io_request({:get_chars, :latin1, prompt, n}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_chars, encoding, prompt, n}, s) when n >= 0 do
|
||||
get_chars(encoding, prompt, n, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_line, prompt}, s) do
|
||||
io_request({:get_line, :latin1, prompt}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_line, encoding, prompt}, s) do
|
||||
get_line(encoding, prompt, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_until, prompt, mod, fun, args}, s) do
|
||||
io_request({:get_until, :latin1, prompt, mod, fun, args}, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_until, encoding, prompt, mod, fun, args}, s) do
|
||||
get_until(encoding, prompt, mod, fun, args, s)
|
||||
end
|
||||
|
||||
defp io_request({:get_password, encoding}, s) do
|
||||
get_line(encoding, "", s)
|
||||
end
|
||||
|
||||
defp io_request({:setopts, _opts}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request(:getopts, s) do
|
||||
{{:ok, [binary: true, encoding: :unicode]}, s}
|
||||
end
|
||||
|
||||
defp io_request({:get_geometry, :columns}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request({:get_geometry, :rows}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request({:requests, reqs}, s) do
|
||||
io_requests(reqs, {:ok, s})
|
||||
end
|
||||
|
||||
defp io_request(_, s) do
|
||||
{{:error, :request}, s}
|
||||
end
|
||||
|
||||
## get_chars
|
||||
|
||||
defp get_chars(encoding, prompt, n,
|
||||
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
|
||||
case do_get_chars(input, encoding, n) do
|
||||
{:error, _} = error ->
|
||||
{error, s}
|
||||
{result, input} ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_chars("", _encoding, _n) do
|
||||
{:eof, ""}
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
|
||||
{input, ""}
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
defp do_get_chars(input, encoding, n) do
|
||||
try do
|
||||
case :file_io_server.count_and_find(input, n, encoding) do
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
{_buf_count, split_pos} ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
{chars, rest}
|
||||
end
|
||||
catch
|
||||
:exit, :invalid_unicode ->
|
||||
{:error, :invalid_unicode}
|
||||
end
|
||||
end
|
||||
|
||||
## get_line
|
||||
|
||||
defp get_line(encoding, prompt,
|
||||
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{:error, _, _} ->
|
||||
{{:error, :collect_line}, s}
|
||||
{:incomplete, _, _} ->
|
||||
{{:error, :collect_line}, s}
|
||||
chars ->
|
||||
{result, input} = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_line('', _encoding) do
|
||||
{:eof, ""}
|
||||
end
|
||||
|
||||
defp do_get_line(chars, encoding) do
|
||||
{line, rest} = collect_line(chars)
|
||||
{:unicode.characters_to_binary(line, encoding),
|
||||
:unicode.characters_to_binary(rest, encoding)}
|
||||
end
|
||||
|
||||
## get_until
|
||||
|
||||
defp get_until(encoding, prompt, mod, fun, args,
|
||||
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{:error, _, _} ->
|
||||
{:error, s}
|
||||
{:incomplete, _, _} ->
|
||||
{:error, s}
|
||||
chars ->
|
||||
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
|
||||
end
|
||||
|
||||
input =
|
||||
case input do
|
||||
:eof -> ""
|
||||
_ -> :unicode.characters_to_binary(input, encoding)
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)
|
||||
|
||||
defp do_get_until('', encoding, mod, fun, args, continuation, count) do
|
||||
case apply(mod, fun, [continuation, :eof | args]) do
|
||||
{:done, result, rest} ->
|
||||
{result, rest, count + 1}
|
||||
{:more, next_continuation} ->
|
||||
do_get_until('', encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
{line, rest} = collect_line(chars)
|
||||
|
||||
case apply(mod, fun, [continuation, line | args]) do
|
||||
{:done, result, rest1} ->
|
||||
unless rest1 == :eof do
|
||||
rest = rest1 ++ rest
|
||||
end
|
||||
{result, rest, count + 1}
|
||||
{:more, next_continuation} ->
|
||||
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
## io_requests
|
||||
|
||||
defp io_requests([r|rs], {:ok, s}) do
|
||||
io_requests(rs, io_request(r, s))
|
||||
end
|
||||
|
||||
defp io_requests(_, result) do
|
||||
result
|
||||
end
|
||||
|
||||
## helpers
|
||||
|
||||
defp collect_line(chars) do
|
||||
collect_line(chars, [])
|
||||
end
|
||||
|
||||
defp collect_line([], stack) do
|
||||
{:lists.reverse(stack), []}
|
||||
end
|
||||
|
||||
defp collect_line([?\r, ?\n | rest], stack) do
|
||||
{:lists.reverse([?\n|stack]), rest}
|
||||
end
|
||||
|
||||
defp collect_line([?\n | rest], stack) do
|
||||
{:lists.reverse([?\n|stack]), rest}
|
||||
end
|
||||
|
||||
defp collect_line([h|t], stack) do
|
||||
collect_line(t, [h|stack])
|
||||
end
|
||||
|
||||
defp io_reply(from, reply_as, reply) do
|
||||
send from, {:io_reply, reply_as, reply}
|
||||
end
|
||||
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_data!(list)
|
||||
defp to_reply(other), do: other
|
||||
end
|
||||
@@ -25,7 +25,7 @@ defmodule Supervisor.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = :supervisor.start_link(MyServer, [])
|
||||
{:ok, pid} = :supervisor.start_link(MyServer, [])
|
||||
|
||||
"""
|
||||
|
||||
@@ -87,7 +87,7 @@ defmodule Supervisor.Behaviour do
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
{ :ok, { { strategy, maxR, maxS }, children } }
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
@child_doc """
|
||||
@@ -122,7 +122,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{ :shutdown, term }`;
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
|
||||
## Shutdown values
|
||||
|
||||
@@ -178,7 +178,7 @@ defmodule Supervisor.Behaviour do
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{ id, { module, function, args },
|
||||
restart, shutdown, type, modules }
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
end
|
||||
|
||||
+54
-52
@@ -1,13 +1,4 @@
|
||||
defmodule System do
|
||||
defexception NoHomeError,
|
||||
message: "could not find the user home, please set the HOME environment variable"
|
||||
|
||||
defexception NoTmpDirError,
|
||||
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
|
||||
|
||||
defexception NoAccessCwdError,
|
||||
message: "could not get a current working directory, the current location is not accessible"
|
||||
|
||||
@moduledoc """
|
||||
The System module provides access to variables used or
|
||||
maintained by the VM and to functions that interact directly
|
||||
@@ -20,7 +11,7 @@ defmodule System do
|
||||
|
||||
defp read_stripped(path) do
|
||||
case :file.read_file(path) do
|
||||
{ :ok, binary } ->
|
||||
{:ok, binary} ->
|
||||
strip_re(binary, "^\s+|\s+$")
|
||||
_ -> ""
|
||||
end
|
||||
@@ -29,7 +20,7 @@ defmodule System do
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
|
||||
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
|
||||
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
@@ -40,7 +31,7 @@ defmodule System do
|
||||
defmacrop get_describe do
|
||||
dirpath = :filename.join(__DIR__, "../../../.git")
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
{:ok, _} ->
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
strip_re(data, "\n")
|
||||
@@ -53,7 +44,7 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
iolist_to_binary :httpd_util.rfc1123_date
|
||||
iodata_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -92,7 +83,7 @@ defmodule System do
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(args) do
|
||||
:elixir_code_server.cast({ :argv, args })
|
||||
:elixir_code_server.cast({:argv, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -103,7 +94,7 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{ :ok, base } -> String.from_char_list!(base)
|
||||
{:ok, base} -> String.from_char_data!(base)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
@@ -111,10 +102,11 @@ defmodule System do
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
Returns the current working directory or raises `System.NoAccessCwdError`.
|
||||
Returns the current working directory or raises `RuntimeError`.
|
||||
"""
|
||||
def cwd! do
|
||||
cwd || raise NoAccessCwdError
|
||||
cwd ||
|
||||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -125,7 +117,7 @@ defmodule System do
|
||||
"""
|
||||
def user_home do
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> get_windows_home
|
||||
{:win32, _} -> get_windows_home
|
||||
_ -> get_unix_home
|
||||
end
|
||||
end
|
||||
@@ -133,11 +125,12 @@ defmodule System do
|
||||
@doc """
|
||||
User home directory, exception on error.
|
||||
|
||||
Same as `user_home/0` but raises `System.NoHomeError`
|
||||
Same as `user_home/0` but raises `RuntimeError`
|
||||
instead of returning `nil` if no user home is set.
|
||||
"""
|
||||
def user_home! do
|
||||
user_home || raise NoHomeError
|
||||
user_home ||
|
||||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
|
||||
end
|
||||
|
||||
defp get_unix_home do
|
||||
@@ -154,7 +147,7 @@ defmodule System do
|
||||
)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Writable temporary directory.
|
||||
|
||||
Returns a writable temporary directory.
|
||||
@@ -179,11 +172,13 @@ defmodule System do
|
||||
@doc """
|
||||
Writable temporary directory, exception on error.
|
||||
|
||||
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
|
||||
Same as `tmp_dir/0` but raises `RuntimeError`
|
||||
instead of returning `nil` if no temp dir is set.
|
||||
"""
|
||||
def tmp_dir! do
|
||||
tmp_dir || raise NoTmpDirError
|
||||
tmp_dir ||
|
||||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
|
||||
"please set the TMPDIR environment variable"
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
@@ -194,17 +189,15 @@ defmodule System do
|
||||
end
|
||||
|
||||
defp write_tmp_dir(dir) do
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__record__(:index, :type)
|
||||
access_index = File.Stat.__record__(:index, :access)
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
String.from_char_list!(dir)
|
||||
case File.stat(dir) do
|
||||
{:ok, stat} ->
|
||||
case {stat.type, stat.access} do
|
||||
{:directory, access} when access in [:read_write, :write] ->
|
||||
String.from_char_data!(dir)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
{ :error, _ } -> nil
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -219,7 +212,7 @@ defmodule System do
|
||||
as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast { :at_exit, fun }
|
||||
:elixir_code_server.cast {:at_exit, fun}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -230,9 +223,9 @@ defmodule System do
|
||||
the result as a binary.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
Otherwise a string, correctly encoded in UTF-8, is expected.
|
||||
"""
|
||||
@spec cmd(binary) :: binary
|
||||
@spec cmd(String.t) :: String.t
|
||||
@spec cmd(char_list) :: char_list
|
||||
|
||||
def cmd(command) when is_list(command) do
|
||||
@@ -240,9 +233,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
# Notice we don't use unicode for conversion
|
||||
# because the OS is expecting and returning raw bytes
|
||||
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
|
||||
String.from_char_data! :os.cmd(List.from_char_data!(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -265,11 +256,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
# Notice we don't use unicode for conversion
|
||||
# because the OS is expecting and returning raw bytes
|
||||
case :os.find_executable(:binary.bin_to_list(program)) do
|
||||
case :os.find_executable(List.from_char_data!(program)) do
|
||||
false -> nil
|
||||
other -> :binary.list_to_bin(other)
|
||||
other -> String.from_char_data!(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -279,12 +268,12 @@ defmodule System do
|
||||
Returns a list of all environment variables. Each variable is given as a
|
||||
`{name, value}` tuple where both `name` and `value` are strings.
|
||||
"""
|
||||
@spec get_env() :: [{String.t, String.t}]
|
||||
@spec get_env() :: %{String.t => String.t}
|
||||
def get_env do
|
||||
Enum.map(:os.getenv, fn var ->
|
||||
var = String.from_char_list! var
|
||||
[k, v] = String.split var, "=", global: false
|
||||
{k, v}
|
||||
Enum.into(:os.getenv, %{}, fn var ->
|
||||
var = String.from_char_data! var
|
||||
[k, v] = String.split var, "=", parts: 2
|
||||
{k, v}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -297,9 +286,9 @@ defmodule System do
|
||||
"""
|
||||
@spec get_env(binary) :: binary | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(String.to_char_list!(varname)) do
|
||||
case :os.getenv(List.from_char_data!(varname)) do
|
||||
false -> nil
|
||||
other -> String.from_char_list!(other)
|
||||
other -> String.from_char_data!(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -312,7 +301,7 @@ defmodule System do
|
||||
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
|
||||
"""
|
||||
@spec get_pid() :: binary
|
||||
def get_pid, do: iolist_to_binary(:os.getpid)
|
||||
def get_pid, do: iodata_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Set an environment variable value.
|
||||
@@ -321,7 +310,7 @@ defmodule System do
|
||||
"""
|
||||
@spec put_env(binary, binary) :: :ok
|
||||
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
|
||||
:os.putenv :binary.bin_to_list(varname), String.to_char_list!(value)
|
||||
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -336,12 +325,25 @@ defmodule System do
|
||||
Enum.each dict, fn {key, val} -> put_env key, val end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an environment variable.
|
||||
|
||||
Removes the variable `varname` from the environment.
|
||||
"""
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(List.from_char_data!(varname))
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Last exception stacktrace.
|
||||
|
||||
Note that the Erlang VM (and therefore this function) does not
|
||||
return the current stacktrace but rather the stacktrace of the
|
||||
latest exception.
|
||||
|
||||
Inlined by the compiler into `:erlang.get_stacktrace/0`.
|
||||
"""
|
||||
def stacktrace do
|
||||
:erlang.get_stacktrace
|
||||
@@ -359,7 +361,7 @@ defmodule System do
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
|
||||
* If a char list, an erlang crash dump is produced with status as slogan,
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1;
|
||||
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
@@ -383,6 +385,6 @@ defmodule System do
|
||||
end
|
||||
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(:binary.bin_to_list(status))
|
||||
:erlang.halt(List.from_char_data!(status))
|
||||
end
|
||||
end
|
||||
|
||||
+14
-8
@@ -9,10 +9,12 @@ defmodule Tuple do
|
||||
Creates a tuple of size `size` containing the
|
||||
given `data` at every position.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Tuple.duplicate(:hello, 3)
|
||||
{ :hello, :hello, :hello }
|
||||
{:hello, :hello, :hello}
|
||||
|
||||
"""
|
||||
@spec duplicate(term, non_neg_integer) :: tuple
|
||||
@@ -27,11 +29,13 @@ defmodule Tuple do
|
||||
Raises an `ArgumentError` if `index` is greater than the
|
||||
length of `tuple`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
...> Tuple.insert_at(tuple, 0, :foo)
|
||||
{ :foo, :bar, :baz }
|
||||
iex> tuple = {:bar, :baz}
|
||||
iex> Tuple.insert_at(tuple, 0, :foo)
|
||||
{:foo, :bar, :baz}
|
||||
|
||||
"""
|
||||
@spec insert_at(tuple, non_neg_integer, term) :: tuple
|
||||
@@ -43,14 +47,16 @@ defmodule Tuple do
|
||||
Remove an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
or equal to the length of `tuple`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :foo, :bar, :baz }
|
||||
...> Tuple.delete_at(tuple, 0)
|
||||
{ :bar, :baz }
|
||||
iex> tuple = {:foo, :bar, :baz}
|
||||
iex> Tuple.delete_at(tuple, 0)
|
||||
{:bar, :baz}
|
||||
|
||||
"""
|
||||
@spec delete_at(tuple, non_neg_integer) :: tuple
|
||||
|
||||
+78
-64
@@ -3,22 +3,22 @@ defmodule URI do
|
||||
Utilities for working with and creating URIs.
|
||||
"""
|
||||
|
||||
defrecord Info, [scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil]
|
||||
defstruct scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports [
|
||||
{ "ftp", 21 },
|
||||
{ "http", 80 },
|
||||
{ "https", 443 },
|
||||
{ "ldap", 389 },
|
||||
{ "sftp", 22 },
|
||||
{ "tftp", 69 },
|
||||
]
|
||||
@ports %{
|
||||
"ftp" => 21,
|
||||
"http" => 80,
|
||||
"https" => 443,
|
||||
"ldap" => 389,
|
||||
"sftp" => 22,
|
||||
"tftp" => 69,
|
||||
}
|
||||
|
||||
Enum.each @ports, fn { scheme, port } ->
|
||||
Enum.each @ports, fn {scheme, port} ->
|
||||
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
|
||||
def default_port(unquote(scheme)), do: unquote(port)
|
||||
end
|
||||
@@ -45,16 +45,20 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
Dict.get(dict, scheme)
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Map.get(dict, scheme)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a scheme with a default port.
|
||||
|
||||
It is recommended for this function to be invoked in your
|
||||
application start callback in case you want to register
|
||||
new URIs.
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -62,13 +66,14 @@ defmodule URI do
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`. Keys and values can
|
||||
be any term that implements the `String.Chars` protocol (i.e. can be converted
|
||||
to a binary).
|
||||
keys and values are URL encoded as per `encode/1`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
|
||||
iex> hd = %{"foo" => 1, "bar" => 2}
|
||||
iex> URI.encode_query(hd)
|
||||
"bar=2&foo=1"
|
||||
|
||||
@@ -76,28 +81,22 @@ defmodule URI do
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc """
|
||||
Decodes a query string into a `HashDict`.
|
||||
Decodes a query string into a dictionary (by default uses a map).
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
map with one entry for each key-value pair. Each key and value will be a
|
||||
binary. Keys and values will be percent-unescaped.
|
||||
|
||||
Use `query_decoder/1` if you want to iterate over each value manually.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
|
||||
[{"bar", "2"}, {"foo", "1"}]
|
||||
|
||||
iex> hd = HashDict.new()
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
|
||||
["bar", "foo"]
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
|
||||
["2", "1"]
|
||||
iex> URI.decode_query("foo=1&bar=2")
|
||||
%{"bar" => "2", "foo" => "1"}
|
||||
|
||||
"""
|
||||
def decode_query(q, dict \\ HashDict.new) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -119,19 +118,27 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
{ first, next } =
|
||||
{first, next} =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> { first, rest }
|
||||
[first] -> { first, "" }
|
||||
[first, rest] -> {first, rest}
|
||||
[first] -> {first, ""}
|
||||
end
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> { decode(key), decode(value) }
|
||||
[ key ] -> { decode(key), nil }
|
||||
[ key, value ] -> {decode(key), decode(value)}
|
||||
[ key ] -> {decode(key), nil}
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
{current, next}
|
||||
end
|
||||
|
||||
defp pair({k, _}) when is_list(k) do
|
||||
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
end
|
||||
|
||||
defp pair({_, v}) when is_list(v) do
|
||||
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -143,11 +150,11 @@ defmodule URI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
|
||||
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
|
||||
|
||||
"""
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
@@ -171,23 +178,30 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
|
||||
"http://elixir-lang.com"
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.org")
|
||||
"http://elixir-lang.org"
|
||||
|
||||
"""
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>) do
|
||||
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
|
||||
def decode(uri) do
|
||||
decode(uri, uri)
|
||||
end
|
||||
|
||||
def decode(<<head, tail :: binary >>) do
|
||||
<<check_plus(head)>> <> decode(tail)
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
|
||||
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
|
||||
end
|
||||
|
||||
def decode(<<>>), do: <<>>
|
||||
def decode(<<head, tail :: binary >>, uri) do
|
||||
<<check_plus(head)>> <> decode(tail, uri)
|
||||
end
|
||||
|
||||
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n) when n in ?0..?9, do: n - ?0
|
||||
def decode(<<>>, _uri), do: <<>>
|
||||
|
||||
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
|
||||
defp hex2dec(_n, uri) do
|
||||
raise ArgumentError, message: "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
@@ -203,18 +217,18 @@ defmodule URI do
|
||||
## Examples
|
||||
|
||||
iex> URI.parse("http://elixir-lang.org/")
|
||||
URI.Info[scheme: "http", path: "/", query: nil, fragment: nil,
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80]
|
||||
%URI{scheme: "http", path: "/", query: nil, fragment: nil,
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
"""
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
{ userinfo, host, port } = split_authority(authority)
|
||||
{userinfo, host, port} = split_authority(authority)
|
||||
|
||||
if authority do
|
||||
authority = ""
|
||||
@@ -230,40 +244,40 @@ defmodule URI do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
URI.Info[
|
||||
%URI{
|
||||
scheme: scheme, path: path, query: query,
|
||||
fragment: fragment, authority: authority,
|
||||
userinfo: userinfo, host: host, port: port
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(s) do
|
||||
s = s || ""
|
||||
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
|
||||
|
||||
{ userinfo, host, port }
|
||||
{userinfo, host, port}
|
||||
end
|
||||
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
lc s inlist l do
|
||||
for s <- l do
|
||||
if size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: URI.Info do
|
||||
def to_string(URI.Info[] = uri) do
|
||||
defimpl String.Chars, for: URI do
|
||||
def to_string(uri) do
|
||||
scheme = uri.scheme
|
||||
|
||||
if scheme && (port = URI.default_port(scheme)) do
|
||||
if uri.port == port, do: uri = uri.port(nil)
|
||||
if uri.port == port, do: uri = %{uri | port: nil}
|
||||
end
|
||||
|
||||
result = ""
|
||||
|
||||
+254
-293
@@ -1,17 +1,12 @@
|
||||
defmodule Version do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
A version is a string in a specific format or a `Version`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Version` parsing and requirements follow
|
||||
[SemVer 2.0 schema](http://semver.org/) and you will get
|
||||
the most of Mix's version system by following it. In order
|
||||
to support integration with projects that may
|
||||
follow different versioning schemas, Elixir won't choke
|
||||
on unknown versions, however you won't be able to use
|
||||
Mix requirements with such unformatted versions.
|
||||
[SemVer 2.0 schema](http://semver.org/).
|
||||
|
||||
## Versions
|
||||
|
||||
@@ -27,6 +22,12 @@ defmodule Version do
|
||||
|
||||
"1.0.0-alpha.3+20130417140000"
|
||||
|
||||
## Struct
|
||||
|
||||
The version is represented by the Version struct and it has its
|
||||
fields named according to Semver: `:major`, `:minor`, `:patch`,
|
||||
`:pre` and `:build`.
|
||||
|
||||
## Requirements
|
||||
|
||||
Requirements allow you to specify which versions of a given
|
||||
@@ -52,197 +53,92 @@ defmodule Version do
|
||||
|
||||
"""
|
||||
|
||||
@type t :: String.t | Version.Schema.t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
|
||||
@type matchable :: { major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
pre :: [String.t] }
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
defstruct [:major, :minor, :patch, :pre, :build]
|
||||
|
||||
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
|
||||
defrecord Requirement, source: nil, matchspec: nil
|
||||
@type version :: String.t | t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
@type matchable :: {major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
pre :: [String.t]}
|
||||
|
||||
defexception InvalidRequirement, [:message] do
|
||||
def exception(opts) do
|
||||
message =
|
||||
if is_binary opts[:reason] do
|
||||
{ first, rest } = String.next_grapheme(opts[:reason])
|
||||
String.downcase(first) <> rest
|
||||
else
|
||||
"invalid version specification"
|
||||
end
|
||||
|
||||
InvalidRequirement[message: message]
|
||||
end
|
||||
defmodule Requirement do
|
||||
@moduledoc false
|
||||
defstruct [:source, :matchspec]
|
||||
end
|
||||
|
||||
defexception InvalidRequirement, [:message]
|
||||
defexception InvalidVersion, [:message]
|
||||
|
||||
@doc """
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersion` if `version` is not parseable.
|
||||
If given an already parsed version and requirement this function won't
|
||||
raise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.match?("2.0", ">1.0")
|
||||
iex> Version.match?("2.0.0", ">1.0.0")
|
||||
true
|
||||
iex> Version.match?("2.0", "==1.0")
|
||||
|
||||
iex> Version.match?("2.0.0", "==1.0.0")
|
||||
false
|
||||
|
||||
iex> Version.match?("foo", "==1.0.0")
|
||||
** (Version.InvalidVersion) foo
|
||||
|
||||
iex> Version.match?("2.0.0", "== ==1.0.0")
|
||||
** (Version.InvalidRequirement) == ==1.0.0
|
||||
|
||||
"""
|
||||
@spec match?(t, requirement) :: boolean
|
||||
def match?(version, requirement) when is_binary(requirement) do
|
||||
case Version.Parser.parse_requirement(requirement) do
|
||||
{ :ok, req } ->
|
||||
match?(version, req)
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
@spec match?(version, requirement) :: boolean
|
||||
def match?(vsn, req) when is_binary(req) do
|
||||
case parse_requirement(req) do
|
||||
{:ok, req} ->
|
||||
match?(vsn, req)
|
||||
:error ->
|
||||
raise InvalidRequirement, message: req
|
||||
end
|
||||
end
|
||||
|
||||
def match?(version, requirement) when is_binary(version) do
|
||||
match?(parse(version), requirement)
|
||||
end
|
||||
|
||||
def match?(Schema[] = version, Requirement[matchspec: spec]) do
|
||||
case :ets.test_ms(to_matchable(version), spec) do
|
||||
{ :ok, result } ->
|
||||
result != false
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
end
|
||||
def match?(version, %Requirement{matchspec: spec}) do
|
||||
{:ok, result} = :ets.test_ms(to_matchable(version), spec)
|
||||
result != false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a version string is compatible with [semver](http://semver.org/).
|
||||
Compares two versions. Returns `:gt` if first version is greater than
|
||||
the second and `:lt` for vice versa. If the two versions are equal `:eq`
|
||||
is returned
|
||||
|
||||
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
|
||||
If given an already parsed version this function won't raise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.valid?("2.0")
|
||||
true
|
||||
iex> Version.valid?("invalid")
|
||||
false
|
||||
iex> Version.compare("2.0.1-alpha1", "2.0.0")
|
||||
:gt
|
||||
|
||||
iex> Version.compare("2.0.1+build0", "2.0.1")
|
||||
:eq
|
||||
|
||||
iex> Version.compare("invalid", "2.0.1")
|
||||
** (Version.InvalidVersion) invalid
|
||||
|
||||
"""
|
||||
@spec valid?(String.t | Schema.t) :: boolean
|
||||
def valid?(string) when is_binary(string) do
|
||||
Version.Parser.valid_version?(string)
|
||||
end
|
||||
|
||||
def valid?(Version.Schema[major: nil]), do: false
|
||||
def valid?(Version.Schema[]), do: true
|
||||
|
||||
@spec valid_requirement?(String.t) :: boolean
|
||||
def valid_requirement?(requirement) do
|
||||
case Version.Parser.parse_requirement(requirement) do
|
||||
{ :ok, _req } ->
|
||||
true
|
||||
{ :error, _reason } ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1")
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: Schema.t
|
||||
def parse(string) when is_binary(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
|
||||
{ :error, _ } -> Version.Schema[source: string]
|
||||
end
|
||||
end
|
||||
|
||||
defp get_build(string) do
|
||||
case Regex.run(%r/\+([^\s]+)$/, string) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
[_, build] ->
|
||||
build
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a version to a `Version.matchable`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.to_matchable("2.0.1-alpha.1")
|
||||
{2, 0, 1, ["alpha", 1]}
|
||||
|
||||
"""
|
||||
@spec to_matchable(String.t | Schema.t) :: Version.matchable
|
||||
def to_matchable(Schema[major: nil, source: source]) do
|
||||
{ source, nil, nil, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
{ major, minor, patch, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Version.Parser.parse_pre(pre) }
|
||||
end
|
||||
|
||||
def to_matchable(string) do
|
||||
to_matchable(parse(string))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a matchable to a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
@spec from_matchable(Version.matchable) :: Schema.t
|
||||
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
|
||||
Version.Schema[source: source]
|
||||
end
|
||||
|
||||
def from_matchable({ major, minor, patch, pre }) do
|
||||
source = "#{major}"
|
||||
|
||||
if minor do
|
||||
source = "#{source}.#{minor}"
|
||||
|
||||
if patch do
|
||||
source = "#{source}.#{patch}"
|
||||
|
||||
case pre do
|
||||
[] ->
|
||||
pre = nil
|
||||
|
||||
list ->
|
||||
pre = Enum.join(list, ".")
|
||||
source = "#{source}-#{pre}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
end
|
||||
|
||||
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
|
||||
@spec compare(version, version) :: :gt | :eq | :lt
|
||||
def compare(vsn1, vsn2) do
|
||||
{ major1, minor1, patch1, pre1 } = to_matchable(vsn1)
|
||||
{ major2, minor2, patch2, pre2 } = to_matchable(vsn2)
|
||||
do_compare(to_matchable(vsn1), to_matchable(vsn2))
|
||||
end
|
||||
|
||||
defp do_compare({major1, minor1, patch1, pre1}, {major2, minor2, patch2, pre2}) do
|
||||
cond do
|
||||
{ major1, minor1, patch1 } > { major2, minor2, patch2 } -> :gt
|
||||
{ major1, minor1, patch1 } < { major2, minor2, patch2 } -> :lt
|
||||
{major1, minor1, patch1} > {major2, minor2, patch2} -> :gt
|
||||
{major1, minor1, patch1} < {major2, minor2, patch2} -> :lt
|
||||
pre1 == [] and pre2 != [] -> :gt
|
||||
pre1 != [] and pre2 == [] -> :lt
|
||||
pre1 > pre2 -> :gt
|
||||
@@ -251,6 +147,73 @@ defmodule Version do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version string into a `Version`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
#Version<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: {:ok, t} | :error
|
||||
def parse(string) when is_binary(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{:ok, {major, minor, patch, pre}} ->
|
||||
vsn = %Version{major: major, minor: minor, patch: patch,
|
||||
pre: pre, build: get_build(string)}
|
||||
{:ok, vsn}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version requirement string into a `Version.Requirement`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
|
||||
#Version.Requirement<== 2.0.1>
|
||||
|
||||
iex> Version.parse_requirement("== == 2.0.1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse_requirement(String.t) :: {:ok, Requirement.t} | :error
|
||||
def parse_requirement(string) when is_binary(string) do
|
||||
case Version.Parser.parse_requirement(string) do
|
||||
{:ok, spec} ->
|
||||
{:ok, %Requirement{source: string, matchspec: spec}}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchable(%Version{major: major, minor: minor, patch: patch, pre: pre}) do
|
||||
{major, minor, patch, pre}
|
||||
end
|
||||
|
||||
defp to_matchable(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{:ok, vsn} -> vsn
|
||||
:error -> raise InvalidVersion, message: string
|
||||
end
|
||||
end
|
||||
|
||||
defp get_build(string) do
|
||||
case Regex.run(~r/\+([^\s]+)$/, string) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
[_, build] ->
|
||||
build
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Parser.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@@ -318,59 +281,67 @@ defmodule Version do
|
||||
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
|
||||
end
|
||||
|
||||
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
|
||||
@version_regex ~r/^
|
||||
(\d+) # major
|
||||
(?:\.(\d+))? # minor
|
||||
(?:\.(\d+))? # patch
|
||||
(?:\-([\d\w\.\-]+))? # pre
|
||||
(?:\+([\d\w\-]+))? # build
|
||||
$/x
|
||||
|
||||
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
|
||||
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
|
||||
def parse_requirement(source) do
|
||||
lexed = lexer(source, [])
|
||||
|
||||
if valid_requirement?(lexed) do
|
||||
spec = to_matchspec(lexed)
|
||||
|
||||
case :ets.test_ms({}, spec) do
|
||||
{ :ok, _ } ->
|
||||
{ :ok, Requirement[source: source, matchspec: spec] }
|
||||
|
||||
{ :error, errors } ->
|
||||
{ :error, Enum.map(errors, fn { :error, reason } ->
|
||||
to_string(reason)
|
||||
end) }
|
||||
end
|
||||
else
|
||||
{ :error, :invalid_requirement }
|
||||
end
|
||||
to_matchspec(lexed)
|
||||
end
|
||||
|
||||
defp nillify(""), do: nil
|
||||
defp nillify(o), do: o
|
||||
|
||||
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
|
||||
def parse_version(string) when is_binary(string) do
|
||||
if valid_version?(string) do
|
||||
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
|
||||
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
|
||||
def parse_version(string, approximate? \\ false) when is_binary(string) do
|
||||
if parsed = Regex.run(@version_regex, string) do
|
||||
destructure [_, major, minor, patch, pre], parsed
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor |> nillify || "0")
|
||||
patch = binary_to_integer(patch |> nillify || "0")
|
||||
pre = pre && parse_pre(pre) || []
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor)
|
||||
patch = patch && binary_to_integer(patch)
|
||||
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
case parse_pre(pre) do
|
||||
{:ok, pre} ->
|
||||
{:ok, {major, minor, patch, pre}}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
else
|
||||
{ :error, :invalid_version }
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@spec parse_pre(String.t) :: [String.t | integer]
|
||||
def parse_pre(pre) do
|
||||
String.split(pre, ".") |> Enum.map fn piece ->
|
||||
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
|
||||
binary_to_integer(piece)
|
||||
else
|
||||
piece
|
||||
end
|
||||
defp parse_pre(nil), do: {:ok, []}
|
||||
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
|
||||
|
||||
defp parse_pre([piece|t], acc) do
|
||||
cond do
|
||||
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
|
||||
parse_pre(t, [binary_to_integer(piece)|acc])
|
||||
piece =~ ~r/^[0-9]*$/ ->
|
||||
:error
|
||||
true ->
|
||||
parse_pre(t, [piece|acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_pre([], acc) do
|
||||
{:ok, Enum.reverse(acc)}
|
||||
end
|
||||
|
||||
defp valid_requirement?([]), do: false
|
||||
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
|
||||
|
||||
@@ -379,11 +350,6 @@ defmodule Version do
|
||||
true
|
||||
end
|
||||
|
||||
# version version
|
||||
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
|
||||
false
|
||||
end
|
||||
|
||||
# or <op> | and <op>
|
||||
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
|
||||
valid_requirement?(b, next)
|
||||
@@ -399,117 +365,97 @@ defmodule Version do
|
||||
valid_requirement?(b, next)
|
||||
end
|
||||
|
||||
# <op> <version>; also checks operators work on valid versions
|
||||
# <op> <version>
|
||||
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
|
||||
if valid_version?(b) do
|
||||
valid_requirement?(b, next)
|
||||
else
|
||||
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
|
||||
valid_requirement?(b, next)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
valid_requirement?(b, next)
|
||||
end
|
||||
|
||||
defp valid_requirement?(_, _) do
|
||||
false
|
||||
end
|
||||
|
||||
@spec valid_version?(String.t) :: boolean
|
||||
def valid_version?(string) do
|
||||
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
|
||||
end
|
||||
defp approximate_upper(version) do
|
||||
case version do
|
||||
{major, _minor, nil, _} ->
|
||||
{major + 1, 0, 0, [0]}
|
||||
|
||||
defp approximate(version) do
|
||||
Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
[_, major] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, _] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, minor, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
|
||||
[_, major, minor, _, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
end)
|
||||
{major, minor, _patch, _} ->
|
||||
{major, minor + 1, 0, [0]}
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchspec(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
|
||||
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
|
||||
if valid_requirement?(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
{:ok, [{{:'$1', :'$2', :'$3', :'$4'}, [to_condition(first, rest)], [:'$_']}]}
|
||||
else
|
||||
:error
|
||||
end
|
||||
catch
|
||||
:invalid_matchspec -> :error
|
||||
end
|
||||
|
||||
defp to_condition([:'==', version | _]) do
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
version = parse_condition(version)
|
||||
{:'==', :'$_', {:const, version}}
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
{ :'/=', :'$_', { :const, version } }
|
||||
version = parse_condition(version)
|
||||
{:'/=', :'$_', {:const, version}}
|
||||
end
|
||||
|
||||
defp to_condition([:'~>', version | _]) do
|
||||
from = Version.parse(version)
|
||||
to = approximate(version)
|
||||
from = parse_condition(version, true)
|
||||
to = approximate_upper(from)
|
||||
|
||||
{ :andalso, to_condition([:'>=', to_string(from)]),
|
||||
to_condition([:'<', to_string(to)]) }
|
||||
{:andalso, to_condition([:'>=', matchable_to_string(from)]),
|
||||
to_condition([:'<', matchable_to_string(to)])}
|
||||
end
|
||||
|
||||
defp to_condition([:'>', version | _]) do
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
{major, minor, patch, pre} = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { :andalso, { :'==', { :length, :'$4' }, 0 },
|
||||
{ :'/=', length(pre), 0 } },
|
||||
{ :andalso, { :'/=', length(pre), 0 },
|
||||
{ :orelse, { :'>', { :length, :'$4' }, length(pre) },
|
||||
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
|
||||
{ :'>', :'$4', { :const, pre } } } } } } } } }
|
||||
{:orelse, {:'>', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:andalso, {:'==', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:orelse, {:andalso, {:'==', {:length, :'$4'}, 0},
|
||||
{:'/=', length(pre), 0}},
|
||||
{:andalso, {:'/=', length(pre), 0},
|
||||
{:orelse, {:'>', {:length, :'$4'}, length(pre)},
|
||||
{:andalso, {:'==', {:length, :'$4'}, length(pre)},
|
||||
{:'>', :'$4', {:const, pre}}}}}}}}
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
to_condition([:'>', version]) }
|
||||
{:orelse, {:'==', :'$_', {:const, matchable}},
|
||||
to_condition([:'>', version])}
|
||||
end
|
||||
|
||||
defp to_condition([:'<', version | _]) do
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
{major, minor, patch, pre} = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { :andalso, { :'/=', { :length, :'$4' }, 0 },
|
||||
{ :'==', length(pre), 0 } },
|
||||
{ :andalso, { :'/=', { :length, :'$4' }, 0 },
|
||||
{ :orelse, { :'<', { :length, :'$4' }, length(pre) },
|
||||
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
|
||||
{ :'<', :'$4', { :const, pre } } } } } } } } }
|
||||
{:orelse, {:'<', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:andalso, {:'==', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:orelse, {:andalso, {:'/=', {:length, :'$4'}, 0},
|
||||
{:'==', length(pre), 0}},
|
||||
{:andalso, {:'/=', {:length, :'$4'}, 0},
|
||||
{:orelse, {:'<', {:length, :'$4'}, length(pre)},
|
||||
{:andalso, {:'==', {:length, :'$4'}, length(pre)},
|
||||
{:'<', :'$4', {:const, pre}}}}}}}}
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
to_condition([:'<', version]) }
|
||||
{:orelse, {:'==', :'$_', {:const, matchable}},
|
||||
to_condition([:'<', version])}
|
||||
end
|
||||
|
||||
defp to_condition(current, []) do
|
||||
@@ -517,35 +463,50 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition(current, [:'&&', operator, version | rest]) do
|
||||
to_condition({ :andalso, current, to_condition([operator, version]) }, rest)
|
||||
to_condition({:andalso, current, to_condition([operator, version])}, rest)
|
||||
end
|
||||
|
||||
defp to_condition(current, [:'||', operator, version | rest]) do
|
||||
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
|
||||
to_condition({:orelse, current, to_condition([operator, version])}, rest)
|
||||
end
|
||||
|
||||
defp parse_condition(version, approximate? \\ false) do
|
||||
case parse_version(version, approximate?) do
|
||||
{:ok, version} -> version
|
||||
:error -> throw :invalid_matchspec
|
||||
end
|
||||
end
|
||||
|
||||
defp matchable_to_string({major, minor, patch, pre}) do
|
||||
patch = if patch, do: "#{patch}", else: "0"
|
||||
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
|
||||
"#{major}.#{minor}.#{patch}#{pre}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Version.Schema do
|
||||
def to_string(Version.Schema[source: source]) do
|
||||
source
|
||||
defimpl String.Chars, for: Version do
|
||||
def to_string(version) do
|
||||
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
|
||||
build = if build = version.build, do: "+#{build}"
|
||||
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version.Schema do
|
||||
defimpl Inspect, for: Version do
|
||||
def inspect(self, _opts) do
|
||||
"#Version.Schema<" <> to_string(self) <> ">"
|
||||
"#Version<" <> to_string(self) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Version.Requirement do
|
||||
def to_string({ _, source, _ }) do
|
||||
def to_string(%Version.Requirement{source: source}) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version.Requirement do
|
||||
def inspect({ _, source, _ }, _opts) do
|
||||
def inspect(%Version.Requirement{source: source}, _opts) do
|
||||
"#Version.Requirement<" <> source <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@ defmodule Elixir.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[ app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n" ]
|
||||
[app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{erl_first_files, ["elixir_transform"]}.
|
||||
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
@@ -23,5 +21,3 @@
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16B|R17A).*"}.
|
||||
|
||||
+93
-52
@@ -9,8 +9,9 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
-export_type([char_list/0, char_data/0, as_boolean/1]).
|
||||
-type char_list() :: string().
|
||||
-type char_data() :: unicode:chardata().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
%% OTP Application API
|
||||
@@ -21,8 +22,17 @@ 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, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
elixir_sup:start_link().
|
||||
|
||||
@@ -47,59 +57,59 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
env_for_eval(Opts) ->
|
||||
env_for_eval(#elixir_env{
|
||||
local = nil,
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
env_for_eval((elixir_env:new())#{
|
||||
local := nil,
|
||||
requires := elixir_dispatch:default_requires(),
|
||||
functions := elixir_dispatch:default_functions(),
|
||||
macros := elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{ line, RawLine } when is_integer(RawLine) -> RawLine;
|
||||
false -> Env#elixir_env.line
|
||||
{line, RawLine} when is_integer(RawLine) -> RawLine;
|
||||
false -> ?m(Env, line)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } when is_binary(RawFile) -> RawFile;
|
||||
false -> Env#elixir_env.file
|
||||
{file, RawFile} when is_binary(RawFile) -> RawFile;
|
||||
false -> ?m(Env, file)
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Env#elixir_env.local
|
||||
{delegate_locals_to, LocalOpt} -> LocalOpt;
|
||||
false -> ?m(Env, local)
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Env#elixir_env.aliases
|
||||
{aliases, AliasesOpt} -> AliasesOpt;
|
||||
false -> ?m(Env, aliases)
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Env#elixir_env.requires
|
||||
{requires, List} -> ordsets:from_list(List);
|
||||
false -> ?m(Env, requires)
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Env#elixir_env.functions
|
||||
{functions, FunctionsOpt} -> FunctionsOpt;
|
||||
false -> ?m(Env, functions)
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Env#elixir_env.macros
|
||||
{macros, MacrosOpt} -> MacrosOpt;
|
||||
false -> ?m(Env, macros)
|
||||
end,
|
||||
|
||||
Module = case lists:keyfind(module, 1, Opts) of
|
||||
{ module, ModuleOpt } when is_atom(ModuleOpt) -> ModuleOpt;
|
||||
{module, ModuleOpt} when is_atom(ModuleOpt) -> ModuleOpt;
|
||||
false -> nil
|
||||
end,
|
||||
|
||||
Env#elixir_env{
|
||||
file=File, local=Local, module=Module,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases, line=Line
|
||||
}.
|
||||
Env#{
|
||||
file := File, local := Local, module := Module,
|
||||
macros := Macros, functions := Functions,
|
||||
requires := Requires, aliases := Aliases, line := Line
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
@@ -108,7 +118,7 @@ eval(String, Binding) ->
|
||||
|
||||
eval(String, Binding, Opts) when is_list(Opts) ->
|
||||
eval(String, Binding, env_for_eval(Opts));
|
||||
eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
|
||||
eval(String, Binding, #{line := Line, file := File} = E) when
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = 'string_to_quoted!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, E).
|
||||
@@ -117,58 +127,89 @@ eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_quoted(Tree, Binding, env_for_eval(Opts));
|
||||
eval_quoted(Tree, Binding, #elixir_env{line=Line} = E) ->
|
||||
eval_quoted(Tree, Binding, #{line := Line} = E) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, E).
|
||||
|
||||
%% Handle forms evaluation. The main difference to
|
||||
%% to eval_quoted is that it does not linefy the given
|
||||
%% args.
|
||||
|
||||
eval_forms(Tree, Binding, #elixir_env{} = E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E));
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts)).
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts));
|
||||
eval_forms(Tree, Binding, E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E)).
|
||||
eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
|
||||
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
{ParsedBinding, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#{vars := [K || {K,_} <- ParsedScope#elixir_scope.vars]},
|
||||
{Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
|
||||
case Erl of
|
||||
{ atom, _, Atom } ->
|
||||
{ Atom, Binding, NewEnv, NewScope };
|
||||
{atom, _, Atom} ->
|
||||
{Atom, Binding, NewEnv, NewScope};
|
||||
_ ->
|
||||
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
|
||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
|
||||
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
|
||||
end.
|
||||
|
||||
erl_eval(Erl, ParsedBinding) ->
|
||||
% Below must be all one line for locations to be the same when the stacktrace
|
||||
% needs to be extended to the full stacktrace.
|
||||
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||
|
||||
get_stacktrace() ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
% eval_eval and eval_bits can call :erlang.raise/3 without the full
|
||||
% stacktrace. When this occurs re-add the current stacktrace so that no
|
||||
% stack information is lost.
|
||||
try
|
||||
throw(stack)
|
||||
catch
|
||||
throw:stack ->
|
||||
% Ignore stack item for current function.
|
||||
[_ | CurrentStack] = erlang:get_stacktrace(),
|
||||
get_stacktrace(Stacktrace, CurrentStack)
|
||||
end.
|
||||
|
||||
% The stacktrace did not include the current stack, re-add it.
|
||||
get_stacktrace([], CurrentStack) ->
|
||||
CurrentStack;
|
||||
% The stacktrace includes the current stack.
|
||||
get_stacktrace(CurrentStack, CurrentStack) ->
|
||||
CurrentStack;
|
||||
get_stacktrace([StackItem | Stacktrace], CurrentStack) ->
|
||||
[StackItem | get_stacktrace(Stacktrace, CurrentStack)].
|
||||
|
||||
%% Converts a quoted expression to erlang abstract format
|
||||
|
||||
quoted_to_erl(Quoted, Env) ->
|
||||
quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)).
|
||||
|
||||
quoted_to_erl(Quoted, Env, Scope) ->
|
||||
{ Expanded, NewEnv } = elixir_exp:expand(Quoted, Env),
|
||||
{ Erl, NewScope } = elixir_translator:translate(Expanded, Scope),
|
||||
{ Erl, NewEnv, NewScope }.
|
||||
{Expanded, NewEnv} = elixir_exp:expand(Quoted, Env),
|
||||
{Erl, NewScope} = elixir_translator:translate(Expanded, Scope),
|
||||
{Erl, NewEnv, NewScope}.
|
||||
|
||||
%% Converts a given string (char list) into quote expression
|
||||
|
||||
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{file, File}|Opts]) of
|
||||
{ok, _Line, Tokens} ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
{ok, Forms} -> {ok, Forms};
|
||||
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
{error, {Line, Error, Token}, _Rest, _SoFar} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
case string_to_quoted(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
{ok, Forms} ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
{error, {Line, Error, Token}} ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
|
||||
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||
|
||||
@@ -11,19 +11,19 @@ inspect(Atom) when is_atom(Atom) ->
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
|
||||
{ Aliases, MacroAliases };
|
||||
{Aliases, MacroAliases};
|
||||
store(Meta, New, Old, TKV, Aliases, MacroAliases, Lexical) ->
|
||||
record_warn(Meta, New, TKV, Lexical),
|
||||
{ store_alias(New, Old, Aliases),
|
||||
store_macro_alias(Meta, New, Old, MacroAliases) }.
|
||||
{store_alias(New, Old, Aliases),
|
||||
store_macro_alias(Meta, New, Old, MacroAliases)}.
|
||||
|
||||
store_alias(New, Old, Aliases) ->
|
||||
lists:keystore(New, 1, Aliases, { New, Old }).
|
||||
lists:keystore(New, 1, Aliases, {New, Old}).
|
||||
store_macro_alias(Meta, New, Old, Aliases) ->
|
||||
case lists:keymember(context, 1, Meta) andalso
|
||||
lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } when is_integer(Counter) ->
|
||||
lists:keystore(New, 1, Aliases, { New, { Counter, Old } });
|
||||
{counter, Counter} when is_integer(Counter) ->
|
||||
lists:keystore(New, 1, Aliases, {New, {Counter, Old}});
|
||||
_ ->
|
||||
Aliases
|
||||
end.
|
||||
@@ -31,8 +31,8 @@ store_macro_alias(Meta, New, Old, Aliases) ->
|
||||
record_warn(Meta, Ref, Opts, Lexical) ->
|
||||
Warn =
|
||||
case lists:keyfind(warn, 1, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
{warn, false} -> false;
|
||||
{warn, true} -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, Lexical).
|
||||
@@ -40,23 +40,23 @@ record_warn(Meta, Ref, Opts, Lexical) ->
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', _Meta, ['Elixir'|_] = List }, _Aliases, _MacroAliases, _LexicalTracker) ->
|
||||
expand({'__aliases__', _Meta, ['Elixir'|_] = List}, _Aliases, _MacroAliases, _LexicalTracker) ->
|
||||
concat(List);
|
||||
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
expand({'__aliases__', Meta, _} = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
{alias, false} ->
|
||||
expand(Alias, MacroAliases, LexicalTracker);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
{alias, Atom} when is_atom(Atom) ->
|
||||
Atom;
|
||||
false ->
|
||||
expand(Alias, Aliases, LexicalTracker)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
expand({'__aliases__', Meta, [H|T]}, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
Counter = case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, C } -> C;
|
||||
{counter, C} -> C;
|
||||
_ -> nil
|
||||
end,
|
||||
case lookup(Lookup, Aliases, Counter) of
|
||||
@@ -69,7 +69,7 @@ expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H)
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
expand({'__aliases__', _Meta, List}, _Aliases, _LexicalTracker) ->
|
||||
List.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
@@ -80,11 +80,11 @@ ensure_loaded(Meta, Ref, E) ->
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
|
||||
Kind = case lists:member(Ref, ?m(E, context_modules)) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, {Kind, Ref})
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
@@ -129,8 +129,8 @@ to_partial(Arg) when is_binary(Arg) -> Arg.
|
||||
|
||||
lookup(Else, Dict, Counter) ->
|
||||
case lists:keyfind(Else, 1, Dict) of
|
||||
{ Else, { Counter, Value } } -> lookup(Value, Dict, Counter);
|
||||
{ Else, Value } when is_atom(Value) -> lookup(Value, Dict, Counter);
|
||||
{Else, {Counter, Value}} -> lookup(Value, Dict, Counter);
|
||||
{Else, Value} when is_atom(Value) -> lookup(Value, Dict, Counter);
|
||||
_ -> Else
|
||||
end.
|
||||
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_bitstring).
|
||||
-export([translate/3, expand/3]).
|
||||
-export([translate/3, expand/3, has_size/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
case E#elixir_env.context of
|
||||
case ?m(E, context) of
|
||||
match ->
|
||||
{ EArgs, EA } = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
|
||||
{ { '<<>>', Meta, EArgs }, EA };
|
||||
{EArgs, EA} = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
|
||||
{{'<<>>', Meta, EArgs}, EA};
|
||||
_ ->
|
||||
{ EArgs, { EC, EV } } = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], { E, E }),
|
||||
{ { '<<>>', Meta, EArgs }, elixir_env:mergea(EV, EC) }
|
||||
{EArgs, {EC, EV}} = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], {E, E}),
|
||||
{{'<<>>', Meta, EArgs}, elixir_env:mergea(EV, EC)}
|
||||
end.
|
||||
|
||||
expand_bitstr(_Fun, [], Acc, E) ->
|
||||
{ lists:reverse(Acc), E };
|
||||
{lists:reverse(Acc), E};
|
||||
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, E) ->
|
||||
{ ELeft, EL } = Fun(Left, E),
|
||||
{ELeft, EL} = Fun(Left, E),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case E of
|
||||
{ ER, _ } -> ok; %% expand_arg, no assigns
|
||||
_ -> ER = E#elixir_env{context=nil} %% expand_each, revert assigns
|
||||
{ER, _} -> ok; %% expand_arg, no assigns
|
||||
_ -> ER = E#{context := nil} %% expand_each, revert assigns
|
||||
end,
|
||||
|
||||
ERight = expand_bit_info(Meta, Right, ER),
|
||||
expand_bitstr(Fun, T, [{'::',Meta,[ELeft,ERight]}|Acc], EL);
|
||||
|
||||
expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
{ Expr, ES } = Fun(H, E),
|
||||
{Expr, ES} = Fun(H, E),
|
||||
expand_bitstr(Fun, T, [Expr|Acc], ES).
|
||||
|
||||
%% Expand bit info
|
||||
@@ -42,28 +41,28 @@ expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, [Info], E).
|
||||
|
||||
expand_bit_info(Meta, [{ Expr, ExprMeta, Args }|T], Size, Types, E) when is_atom(Expr) ->
|
||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
||||
type ->
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], EE);
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
|
||||
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
|
||||
end,
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, EE);
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, { Expr, ExprMeta, ListArgs }, T, Size, Types, E)
|
||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
||||
expand_bit_info(Meta, [{ size, [], [Int] }|T], Size, Types, E);
|
||||
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
|
||||
|
||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
@@ -90,10 +89,10 @@ expand_bit_type_or_size(unit, [_]) -> type;
|
||||
expand_bit_type_or_size(size, [_]) -> size;
|
||||
expand_bit_type_or_size(_, _) -> none.
|
||||
|
||||
handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:env_to_ex({ ?line(ExprMeta), E })) of
|
||||
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
|
||||
elixir_errors:compile_error(ExprMeta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
@@ -102,6 +101,13 @@ handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
|
||||
%% Translation
|
||||
|
||||
has_size({bin, _, Elements}) ->
|
||||
not lists:any(fun({bin_element, _Line, _Expr, Size, Types}) ->
|
||||
(Types /= default) andalso (Size == default) andalso
|
||||
lists:any(fun(X) -> lists:member(X, Types) end,
|
||||
[bits, bytes, bitstring, binary])
|
||||
end, Elements).
|
||||
|
||||
translate(Meta, Args, S) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
@@ -111,14 +117,14 @@ translate(Meta, Args, S) ->
|
||||
end.
|
||||
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
|
||||
{Final, FinalS} = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{{bin, ?line(Meta), lists:reverse(Final)}, FinalS}.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{ Acc, S };
|
||||
{Acc, S};
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{ Size, Types } = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
@@ -130,11 +136,11 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
|
||||
true ->
|
||||
%% See explanation in elixir_utils:elixir_to_erl/1 to know
|
||||
%% why we can simply convert the binary to a list.
|
||||
{ bin_element, ?line(Meta), { string, 0, binary_to_list(H) }, default, default };
|
||||
{bin_element, ?line(Meta), {string, 0, binary_to_list(H)}, default, default};
|
||||
false ->
|
||||
case types_require_conversion(Types) of
|
||||
true ->
|
||||
{ bin_element, ?line(Meta), { string, 0, elixir_utils:characters_to_list(H) }, default, Types };
|
||||
{bin_element, ?line(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
|
||||
false ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
|
||||
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
|
||||
@@ -151,16 +157,16 @@ build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is
|
||||
['Elixir.Macro':to_string(H)]);
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
{Expr, NS} = Fun(H, S),
|
||||
|
||||
case Expr of
|
||||
{ bin, _, Elements } ->
|
||||
{bin, _, Elements} ->
|
||||
case (Size == default) andalso types_allow_splice(Types, Elements) of
|
||||
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
|
||||
end;
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
|
||||
end.
|
||||
|
||||
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
|
||||
@@ -168,35 +174,50 @@ types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -
|
||||
types_require_conversion([]) -> true;
|
||||
types_require_conversion(_) -> false.
|
||||
|
||||
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([bytes], Elements) -> is_byte_size(Elements, 0);
|
||||
types_allow_splice([binary], Elements) -> is_byte_size(Elements, 0);
|
||||
types_allow_splice([bits], _) -> true;
|
||||
types_allow_splice([bitstring], _) -> true;
|
||||
types_allow_splice(default, _) -> true;
|
||||
types_allow_splice(_, _) -> false.
|
||||
|
||||
has_default_size({ bin_element, _, _, default, _ }) -> true;
|
||||
has_default_size({ bin_element, _, _, _, _ }) -> false.
|
||||
is_byte_size([Element|T], Acc) ->
|
||||
case elem_size(Element) of
|
||||
{unknown, Unit} when Unit rem 8 == 0 -> is_byte_size(T, Acc);
|
||||
{unknown, _Unit} -> false;
|
||||
{Size, Unit} -> is_byte_size(T, Size*Unit + Acc)
|
||||
end;
|
||||
is_byte_size([], Size) ->
|
||||
Size rem 8 == 0.
|
||||
|
||||
elem_size({bin_element, _, _, default, _}) -> {0, 0};
|
||||
elem_size({bin_element, _, _, {integer,_,Size}, Types}) -> {Size, unit_size(Types, 1)};
|
||||
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
|
||||
|
||||
unit_size([binary|T], _) -> unit_size(T, 8);
|
||||
unit_size([{unit, Size}|_], _) -> Size;
|
||||
unit_size([_|T], Guess) -> unit_size(T, Guess);
|
||||
unit_size([], Guess) -> Guess.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_info(Meta, [{ size, _, [Arg] }|T], S) ->
|
||||
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
|
||||
case elixir_translator:translate(Arg, S) of
|
||||
{ { Kind, _, _ } = Size, _ } when Kind == integer; Kind == var ->
|
||||
{ Size, extract_bit_type(Meta, T, S) };
|
||||
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
|
||||
{Size, extract_bit_type(Meta, T, S)};
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
extract_bit_info(Meta, T, S) ->
|
||||
{ default, extract_bit_type(Meta, T, S) }.
|
||||
{default, extract_bit_type(Meta, T, S)}.
|
||||
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|T], S) when is_integer(Arg) ->
|
||||
[{ unit, Arg }|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|_], S) ->
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
|
||||
[{unit, Arg}|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
||||
extract_bit_type(Meta, [{ Other, _, [] }|T], S) ->
|
||||
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
|
||||
{ Escaped, _ } = elixir_quote:escape(Block, false),
|
||||
{Escaped, _} = elixir_quote:escape(Block, false),
|
||||
Args = [Alias, Escaped, [], env()],
|
||||
{ { '.', [], [elixir_module, compile] }, [], Args }.
|
||||
{{'.', [], [elixir_module, compile]}, [], Args}.
|
||||
|
||||
'__info__'(functions) ->
|
||||
[];
|
||||
'__info__'(macros) ->
|
||||
[{ '@', 1 },
|
||||
[{'@', 1},
|
||||
{def,1},
|
||||
{def,2},
|
||||
{defmacro,1},
|
||||
@@ -36,16 +36,16 @@
|
||||
{defp,2}].
|
||||
|
||||
define({Line,E}, Kind, Call, Expr) ->
|
||||
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
|
||||
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
|
||||
{EscapedCall, UC} = elixir_quote:escape(Call, true),
|
||||
{EscapedExpr, UE} = elixir_quote:escape(Expr, true),
|
||||
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
|
||||
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
|
||||
{{'.', [], [elixir_def, store_definition]}, [], Args}.
|
||||
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
case code:is_loaded(?kernel) of
|
||||
{ _, _} -> apply(?kernel, Fun, Args);
|
||||
{_, _} -> apply(?kernel, Fun, Args);
|
||||
false -> Callback()
|
||||
end.
|
||||
|
||||
env() ->
|
||||
{ '__ENV__', [], nil }.
|
||||
{'__ENV__', [], nil}.
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -11,9 +11,9 @@ get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, Pairs } when is_list(Pairs) ->
|
||||
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
{Key, Pairs} when is_list(Pairs) ->
|
||||
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
{Key, nil} when AllowNil ->
|
||||
[];
|
||||
false ->
|
||||
[]
|
||||
@@ -23,17 +23,17 @@ get_pairs(Key, Clauses, AllowNil) ->
|
||||
|
||||
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
|
||||
{Result, NewS} = match(Fun, Args, S#elixir_scope{context=match,
|
||||
match_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
match_vars=MatchVars, backup_vars=BackupVars} };
|
||||
{Result, NewS#elixir_scope{context=Context,
|
||||
match_vars=MatchVars, backup_vars=BackupVars}};
|
||||
match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExpr, SE } = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
|
||||
clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
|
||||
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
@@ -43,50 +43,46 @@ clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
{ { clause, Line, TArgs, TGuards, unblock(TExpr) }, SE }.
|
||||
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
|
||||
extract_guards(Else) -> { Else, [] }.
|
||||
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
|
||||
extract_guards(Else) -> {Else, []}.
|
||||
|
||||
extract_or_guards({ 'when', _, [Left, Right] }) -> [Left|extract_or_guards(Right)];
|
||||
extract_or_guards({'when', _, [Left, Right]}) -> [Left|extract_or_guards(Right)];
|
||||
extract_or_guards(Term) -> [Term].
|
||||
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_utils:split_last(Args),
|
||||
{ Left, extract_or_guards(Right) };
|
||||
extract_splat_guards([{'when', _, [_,_|_] = Args}]) ->
|
||||
{Left, Right} = elixir_utils:split_last(Args),
|
||||
{Left, extract_or_guards(Right)};
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
{Else, []}.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
|
||||
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
|
||||
clauses(Meta, Clauses, Return, #elixir_scope{export_vars=CV} = S) ->
|
||||
{TC, TS} = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
|
||||
{TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)}}.
|
||||
|
||||
do_clauses(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
do_clauses(_Meta, [], _Return, S) ->
|
||||
{[], S};
|
||||
|
||||
% do_clauses(_Meta, [DecoupledClause], S) ->
|
||||
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
% { [TDecoupledClause], TS };
|
||||
|
||||
do_clauses(Meta, DecoupledClauses, S) ->
|
||||
do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX,
|
||||
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } =
|
||||
{TClauses, {TS, ReverseCV}} =
|
||||
lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
@@ -98,7 +94,7 @@ do_clauses(Meta, DecoupledClauses, S) ->
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
|
||||
{FinalVars, FS} = lists:mapfoldl(fun({Key, Val}, Acc) ->
|
||||
normalize_vars(Key, Val, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
@@ -108,9 +104,9 @@ do_clauses(Meta, DecoupledClauses, S) ->
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
case generate_match_vars(FinalVars, ClauseVars, [], []) of
|
||||
{ [], [] } ->
|
||||
{[], []} ->
|
||||
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
|
||||
{ Left, Right } ->
|
||||
{Left, Right} ->
|
||||
MatchExpr = generate_match(Line, Left, Right),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
@@ -118,19 +114,19 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
{FinalClauseExprs, FS} = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
{match, _, {var, _, UserVarName} = UserVar, _} when UserVarName /= '_' ->
|
||||
{[UserVar,MatchExpr,Final|RawClauseExprs], S};
|
||||
_ ->
|
||||
{ VarName, _, SS } = elixir_scope:build_var('_', S),
|
||||
StorageVar = { var, Line, VarName },
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
{VarName, _, SS} = elixir_scope:build_var('_', S),
|
||||
StorageVar = {var, Line, VarName},
|
||||
StorageExpr = {match, Line, StorageVar, Final},
|
||||
{[StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS}
|
||||
end;
|
||||
false ->
|
||||
{ [Final,MatchExpr|RawClauseExprs], S }
|
||||
{[Final,MatchExpr|RawClauseExprs], S}
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
@@ -138,22 +134,22 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
end;
|
||||
|
||||
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
{ lists:reverse(Acc), S }.
|
||||
{lists:reverse(Acc), S}.
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate(Condition, S),
|
||||
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
|
||||
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -167,7 +163,7 @@ has_match_tuple({'case', _, _, _}) ->
|
||||
true;
|
||||
has_match_tuple({match, _, _, _}) ->
|
||||
true;
|
||||
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
||||
has_match_tuple({'fun', _, {clauses, _}}) ->
|
||||
false;
|
||||
has_match_tuple(H) when is_tuple(H) ->
|
||||
has_match_tuple(tuple_to_list(H));
|
||||
@@ -183,39 +179,39 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ ok, { PreValue, _ } } -> { var, 0, PreValue };
|
||||
error -> { atom, 0, nil }
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
error -> {atom, 0, nil}
|
||||
end,
|
||||
|
||||
{ { Key, Value, Expr }, VS }.
|
||||
{{Key, Value, Expr}, VS}.
|
||||
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
|
||||
generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, Value } ->
|
||||
{ok, Value} ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, Clause } ->
|
||||
{ok, Clause} ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left],
|
||||
[{ var, 0, element(1, Clause) }|Right]);
|
||||
[{var, 0, element(1, Value)}|Left],
|
||||
[{var, 0, element(1, Clause)}|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
|
||||
[{var, 0, element(1, Value)}|Left], [Expr|Right])
|
||||
end;
|
||||
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
{ Left, Right }.
|
||||
{Left, Right}.
|
||||
|
||||
generate_match(Line, [Left], [Right]) ->
|
||||
{ match, Line, Left, Right };
|
||||
{match, Line, Left, Right};
|
||||
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
{match, Line, {tuple, Line, LeftVars}, {tuple, Line, RightVars}}.
|
||||
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock({'block', _, Exprs}) -> Exprs;
|
||||
unblock(Exprs) -> [Exprs].
|
||||
|
||||
@@ -30,121 +30,121 @@ init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
{ok, #elixir_code_server{}}.
|
||||
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
handle_call({acquire, Path}, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ reply, loaded, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
|
||||
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
|
||||
{ok, true} ->
|
||||
{reply, loaded, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
|
||||
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
|
||||
error ->
|
||||
Queued = orddict:store(Path, { make_ref(), [] }, Current),
|
||||
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
|
||||
Queued = orddict:store(Path, {make_ref(), []}, Current),
|
||||
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
|
||||
end;
|
||||
|
||||
handle_call(loaded, _From, Config) ->
|
||||
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
|
||||
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
|
||||
|
||||
handle_call(at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config };
|
||||
{reply, Config#elixir_code_server.at_exit, Config};
|
||||
|
||||
handle_call(flush_at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]} };
|
||||
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
|
||||
|
||||
handle_call(argv, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.argv, Config };
|
||||
{reply, Config#elixir_code_server.argv, Config};
|
||||
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
{reply, Config#elixir_code_server.compiler_options, Config};
|
||||
|
||||
handle_call({ compilation_status, CompilerPid }, _From, Config) ->
|
||||
handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
||||
{ reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew} };
|
||||
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
{ [H|T], Counter } ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
|
||||
{ [], Counter } ->
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}} }
|
||||
{[H|T], Counter} ->
|
||||
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
|
||||
{[], Counter} ->
|
||||
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
|
||||
end;
|
||||
|
||||
handle_call(erl_compiler_options, _From, Config) ->
|
||||
case Config#elixir_code_server.erl_compiler_options of
|
||||
nil ->
|
||||
Opts = erl_compiler_options(),
|
||||
{ reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts} };
|
||||
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
|
||||
Opts ->
|
||||
{ reply, Opts, Config }
|
||||
{reply, Opts, Config}
|
||||
end;
|
||||
|
||||
handle_call(Request, _From, Config) ->
|
||||
{ stop, { badcall, Request }, Config }.
|
||||
{stop, {badcall, Request}, Config}.
|
||||
|
||||
handle_cast({ at_exit, AtExit }, Config) ->
|
||||
{ noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
|
||||
handle_cast({at_exit, AtExit}, Config) ->
|
||||
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
|
||||
|
||||
handle_cast({ argv, Argv }, Config) ->
|
||||
{ noreply, Config#elixir_code_server{argv=Argv} };
|
||||
handle_cast({argv, Argv}, Config) ->
|
||||
{noreply, Config#elixir_code_server{argv=Argv}};
|
||||
|
||||
handle_cast({ compiler_options, Options }, Config) ->
|
||||
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} };
|
||||
{noreply, Config#elixir_code_server{compiler_options=Final}};
|
||||
|
||||
handle_cast({ register_warning, CompilerPid }, Config) ->
|
||||
handle_cast({register_warning, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
|
||||
_ -> { noreply, Config }
|
||||
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
_ -> {noreply, Config}
|
||||
end;
|
||||
|
||||
handle_cast({ reset_warnings, CompilerPid }, Config) ->
|
||||
handle_cast({reset_warnings, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
|
||||
{ noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
|
||||
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
handle_cast({loaded, Path}, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ noreply, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! { elixir_code_server, Ref, loaded } || { Pid, _Tag } <- lists:reverse(List)],
|
||||
{ok, true} ->
|
||||
{noreply, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} };
|
||||
{noreply, Config#elixir_code_server{loaded=Done}};
|
||||
error ->
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} }
|
||||
{noreply, Config#elixir_code_server{loaded=Done}}
|
||||
end;
|
||||
|
||||
handle_cast({ unload_files, Files }, Config) ->
|
||||
handle_cast({unload_files, Files}, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
|
||||
{noreply, Config#elixir_code_server{loaded=Unloaded}};
|
||||
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
||||
|
||||
handle_cast(Request, Config) ->
|
||||
{ stop, { badcast, Request }, Config }.
|
||||
{stop, {badcast, Request}, Config}.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
{noreply, Config}.
|
||||
|
||||
terminate(_Reason, _Config) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
{ok, Config}.
|
||||
|
||||
module_tuple(I) ->
|
||||
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3, format_error/1]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
@@ -9,7 +9,7 @@ get_opt(Key) ->
|
||||
Dict = elixir_code_server:call(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{ Key, Value } -> Value
|
||||
{Key, Value} -> Value
|
||||
end.
|
||||
|
||||
%% Compilation entry points.
|
||||
@@ -26,7 +26,7 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
|
||||
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -35,7 +35,7 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
File = filename:absname(Relative),
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
{ok, Bin} = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
@@ -46,35 +46,35 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Vars, E) ->
|
||||
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
|
||||
case (?m(E, module) == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, E);
|
||||
false -> code_loading_compilation(Forms, Vars, E)
|
||||
end.
|
||||
|
||||
eval_compilation(Forms, Vars, E) ->
|
||||
Binding = [{ Key, Value } || { _Name, _Kind, Key, Value } <- Vars],
|
||||
{ Result, _Binding, EE, _S } = elixir:eval_forms(Forms, Binding, E),
|
||||
{ Result, EE }.
|
||||
Binding = [{Key, Value} || {_Name, _Kind, Key, Value} <- Vars],
|
||||
{Result, _Binding, EE, _S} = elixir:eval_forms(Forms, Binding, E),
|
||||
{Result, EE}.
|
||||
|
||||
code_loading_compilation(Forms, Vars, #elixir_env{line=Line} = E) ->
|
||||
Dict = [{ { Name, Kind }, { Value, 0 } } || { Name, Kind, Value, _ } <- Vars],
|
||||
code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
|
||||
S = elixir_env:env_to_scope_with_vars(E, Dict),
|
||||
{ Expr, EE, _S } = elixir:quoted_to_erl(Forms, E, S),
|
||||
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
|
||||
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
Fun = code_fun(E#elixir_env.module),
|
||||
Form = code_mod(Fun, Expr, Line, E#elixir_env.file, Module, Vars),
|
||||
Args = list_to_tuple([V || { _, _, _, V } <- Vars]),
|
||||
{Module, I} = retrieve_module_name(),
|
||||
Fun = code_fun(?m(E, module)),
|
||||
Form = code_mod(Fun, Expr, Line, ?m(E, file), Module, Vars),
|
||||
Args = list_to_tuple([V || {_, _, _, V} <- Vars]),
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
|
||||
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
Purgeable =
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } -> true;
|
||||
{ok, {_, [{labeled_locals, []}]}} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
@@ -89,34 +89,34 @@ dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ Res, E }.
|
||||
{Res, E}.
|
||||
|
||||
code_fun(nil) -> '__FILE__';
|
||||
code_fun(_) -> '__MODULE__'.
|
||||
|
||||
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
|
||||
Tuple = {tuple, Line, [{var, Line, K} || {_, _, K, _} <- Vars]},
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(Relative), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 1 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, 1, [
|
||||
{ clause, Line, [Tuple], [], [Expr] }
|
||||
] },
|
||||
{ function, Line, '__RELATIVE__', 0, [
|
||||
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
|
||||
] }
|
||||
{attribute, Line, file, {elixir_utils:characters_to_list(Relative), 1}},
|
||||
{attribute, Line, module, Module},
|
||||
{attribute, Line, export, [{Fun, 1}, {'__RELATIVE__', 0}]},
|
||||
{function, Line, Fun, 1, [
|
||||
{clause, Line, [Tuple], [], [Expr]}
|
||||
]},
|
||||
{function, Line, '__RELATIVE__', 0, [
|
||||
{clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)]}
|
||||
]}
|
||||
].
|
||||
|
||||
retrieve_module_name() ->
|
||||
elixir_code_server:call(retrieve_module_name).
|
||||
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
elixir_code_server:cast({return_module_name, I}).
|
||||
|
||||
allows_fast_compilation({ '__block__', _, Exprs }) ->
|
||||
allows_fast_compilation({'__block__', _, Exprs}) ->
|
||||
lists:all(fun allows_fast_compilation/1, Exprs);
|
||||
allows_fast_compilation({defmodule,_,_}) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
@@ -137,15 +137,11 @@ module(Forms, File, Opts, Callback) ->
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
|
||||
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
@@ -154,28 +150,14 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
compiler_options(Forms, Options) ->
|
||||
EnvOptions = elixir_code_server:call(erl_compiler_options),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
{attribute, 0, compile, no_auto_import}.
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
|
||||
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
core_file(File) ->
|
||||
@@ -192,12 +174,14 @@ core_file(File) ->
|
||||
core_main() ->
|
||||
[
|
||||
<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/record/backend.ex">>,
|
||||
<<"lib/elixir/lib/record/deprecated.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
@@ -210,6 +194,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/collectable.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
@@ -217,7 +202,6 @@ core_main() ->
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
|
||||
@@ -228,12 +212,8 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
exit({nocompile, "compilation failed but no error was raised"});
|
||||
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
|
||||
@@ -13,24 +13,24 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
|
||||
|
||||
init(Counter) ->
|
||||
{ ok, Counter }.
|
||||
{ok, Counter}.
|
||||
|
||||
handle_call(next, _From, Counter) ->
|
||||
{ reply, Counter, bump(Counter) };
|
||||
{reply, Counter, bump(Counter)};
|
||||
handle_call(Request, _From, Counter) ->
|
||||
{ stop, { badcall, Request }, Counter }.
|
||||
{stop, {badcall, Request}, Counter}.
|
||||
|
||||
handle_cast(Request, Counter) ->
|
||||
{ stop, { badcast, Request }, Counter }.
|
||||
{stop, {badcast, Request}, Counter}.
|
||||
|
||||
handle_info(_Request, Counter) ->
|
||||
{ noreply, Counter }.
|
||||
{noreply, Counter}.
|
||||
|
||||
terminate(_Reason, _Counter) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Counter, _Extra) ->
|
||||
{ ok, Counter }.
|
||||
{ok, Counter}.
|
||||
|
||||
bump(Counter) when Counter < ?limit ->
|
||||
Counter + 1;
|
||||
|
||||
+102
-93
@@ -18,8 +18,8 @@ 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]) }),
|
||||
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
|
||||
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
|
||||
reset_last(Module),
|
||||
ok.
|
||||
|
||||
@@ -29,14 +29,14 @@ cleanup(Module) ->
|
||||
|
||||
%% Reset the last item. Useful when evaling code.
|
||||
reset_last(Module) ->
|
||||
ets:insert(table(Module), { last, [] }).
|
||||
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)] };
|
||||
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
@@ -49,26 +49,26 @@ delete_definition(Module, Tuple) ->
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
E = (elixir_locals:get_cached_env(Pos))#elixir_env{line=Line},
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
|
||||
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
|
||||
|
||||
{ Name, Args } = case NameAndArgs of
|
||||
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
|
||||
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
|
||||
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
|
||||
{Name, Args} = case NameAndArgs of
|
||||
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
|
||||
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
|
||||
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
|
||||
end,
|
||||
|
||||
%% Now that we have verified the call format,
|
||||
%% extract meta information like file and context.
|
||||
{ _, Meta, _ } = Call,
|
||||
{_, Meta, _} = Call,
|
||||
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
||||
|
||||
%% Check if there is a file information in the definition.
|
||||
%% If so, we assume this come from another source and we need
|
||||
%% to linify taking into account keep line numbers.
|
||||
{ File, Key } = case lists:keyfind(file, 1, Meta) of
|
||||
{ file, Bin } when is_binary(Bin) -> { Bin, keep };
|
||||
_ -> { nil, line }
|
||||
{File, Key} = case lists:keyfind(file, 1, Meta) of
|
||||
{file, Bin} when is_binary(Bin) -> {Bin, keep};
|
||||
_ -> {nil, line}
|
||||
end,
|
||||
|
||||
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
||||
@@ -79,20 +79,19 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, File, E).
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
E = ER#elixir_env{function=Tuple},
|
||||
Tuple = {Name, Arity},
|
||||
E = ER#{function := Tuple},
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
|
||||
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = E#elixir_env.file,
|
||||
File = ?m(E, file),
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
@@ -104,7 +103,8 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
{Name, Arity}.
|
||||
|
||||
%% @on_definition
|
||||
|
||||
@@ -113,11 +113,19 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
Env = elixir_env:linify({Line, E}),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks]
|
||||
end.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid -> Pid ! {struct_available, Module}
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
@@ -126,69 +134,70 @@ retrieve_location(Line, File, Module) ->
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
|
||||
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
X ->
|
||||
X when is_binary(X) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ X, 0 }
|
||||
{elixir_utils:characters_to_list(X), 0}
|
||||
end.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #elixir_env{function=Function}) ->
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
AllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
EAllArgs = case is_macro(Kind) of
|
||||
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
AllArgs, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, Super }.
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
Function = {function, Line, Name, Arity, Clauses},
|
||||
{Function, Defaults, Super}.
|
||||
|
||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
{ [], false };
|
||||
{[], false};
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind});
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
|
||||
{TClause, TS} = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = { 'match', Line,
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
{ [FClause], TS#elixir_scope.super }.
|
||||
{[FClause], TS#elixir_scope.super}.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
expr_from_body(_Line, [{do, Expr}]) -> Expr;
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -205,18 +214,18 @@ unwrap_definitions(Module) ->
|
||||
|
||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
|
||||
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
|
||||
case Clauses of
|
||||
[] -> { false, Exports, Private, Def, Defmacro };
|
||||
[] -> {false, Exports, Private, Def, Defmacro};
|
||||
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
{NewFunctions, NewTail} = case NewFun of
|
||||
false ->
|
||||
NewAll = All,
|
||||
{ Functions, Tail };
|
||||
{Functions, Tail};
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
||||
@@ -225,49 +234,49 @@ unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functio
|
||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
{All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
|
||||
|
||||
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
|
||||
unwrap_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = { ?elixir_macro(Name), Arity + 1 },
|
||||
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
|
||||
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
|
||||
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = {elixir_utils:macro_name(Name), Arity + 1},
|
||||
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
|
||||
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ Fun, Exports, [Info|Private], Def, Defmacro };
|
||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
||||
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
|
||||
{Fun, Exports, [Info|Private], Def, Defmacro};
|
||||
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ false, Exports, [Info|Private], Def, Defmacro }.
|
||||
%% {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, _, _, nil, _}, Clauses, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
] }.
|
||||
{Functions, [
|
||||
{function, Line, Name, Arity, Clauses},
|
||||
{attribute, Line, file, Location} | Tail
|
||||
]}.
|
||||
|
||||
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
{ function, Line, Name, length(Args) - 1, [Clause] };
|
||||
{function, Line, Name, length(Args) - 1, [Clause]};
|
||||
|
||||
default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
||||
{ function, Line, Name, length(Args), [Clause] }.
|
||||
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
|
||||
{function, Line, Name, length(Args), [Clause]}.
|
||||
|
||||
%% Store each definition in the table.
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Tuple = { Name, Arity },
|
||||
Tuple = {Name, Arity},
|
||||
case ets:lookup(Table, Tuple) of
|
||||
[{ Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults }] ->
|
||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
@@ -280,16 +289,16 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults
|
||||
end,
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } }),
|
||||
ets:insert(CTable, [{ Tuple, Clause } || Clause <- Clauses ]),
|
||||
ets:insert(Table, { Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults }).
|
||||
Check andalso ets:insert(Table, {last, {Name, Arity}}),
|
||||
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
|
||||
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
|
||||
|
||||
%% Validations
|
||||
|
||||
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
|
||||
{changed_kind, {Name, Arity, StoredKind, Kind}}).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
@@ -297,29 +306,29 @@ check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile)
|
||||
[] -> [];
|
||||
_ ->
|
||||
Relative = elixir_utils:relative_to_cwd(StoredFile),
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ ungrouped_clause, { Kind, Name, Arity, StoredLine, Relative } } })
|
||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
|
||||
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
|
||||
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 } }).
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
|
||||
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
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_env{file=File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_alias, Atom });
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -329,17 +338,17 @@ assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
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({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
|
||||
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 ->
|
||||
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]);
|
||||
"define a function head with the 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]);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user